Re_Figma_Code/src/pages/Form16/Form16NonSubmittedDealers.tsx

335 lines
15 KiB
TypeScript

import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { Bell, Mail, Phone, Calendar, User, Loader2 } from 'lucide-react';
import { useState, useEffect } from 'react';
import { toast } from 'sonner';
import { listNonSubmittedDealers, notifyNonSubmittedDealer, type NonSubmittedDealerItem } from '@/services/form16Api';
function formatDisplayDate(isoDate: string | null): string {
if (!isoDate) return '—';
try {
const d = new Date(isoDate + 'Z');
const day = d.getUTCDate();
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const month = months[d.getUTCMonth()];
const year = d.getUTCFullYear();
return `${day}-${month}-${year}`;
} catch {
return isoDate;
}
}
const FY_OPTIONS = [
{ value: '2024-25', label: '2024-25' },
{ value: '2023-24', label: '2023-24' },
{ value: '2022-23', label: '2022-23' },
{ value: '', label: 'All Years' },
];
export function Form16NonSubmittedDealers() {
const [notifyingDealer, setNotifyingDealer] = useState<string | null>(null);
const [dealers, setDealers] = useState<NonSubmittedDealerItem[]>([]);
const [summary, setSummary] = useState<{
totalDealers: number;
nonSubmittedCount: number;
neverSubmittedCount: number;
overdue90Count: number;
}>({ totalDealers: 0, nonSubmittedCount: 0, neverSubmittedCount: 0, overdue90Count: 0 });
const [loading, setLoading] = useState(true);
const [financialYearFilter, setFinancialYearFilter] = useState<string>('2024-25');
useEffect(() => {
fetchNonSubmittedDealers();
}, [financialYearFilter]);
const fetchNonSubmittedDealers = async () => {
try {
setLoading(true);
const response = await listNonSubmittedDealers(
financialYearFilter || undefined
);
setDealers(response.dealers);
setSummary(response.summary);
} catch (error) {
console.error('Error fetching non-submitted dealers:', error);
toast.error('Failed to load non-submitted dealers');
setDealers([]);
setSummary({ totalDealers: 0, nonSubmittedCount: 0, neverSubmittedCount: 0, overdue90Count: 0 });
} finally {
setLoading(false);
}
};
const handleNotifyDealer = async (dealer: NonSubmittedDealerItem) => {
setNotifyingDealer(dealer.id);
try {
await notifyNonSubmittedDealer(dealer.dealerCode, financialYearFilter || undefined);
toast.success(`Notification sent to ${dealer.dealerName}`, {
description: `Reminder sent for missing quarters: ${dealer.missingQuarters.join(', ')}. Last notified column updated.`,
});
await fetchNonSubmittedDealers();
} catch (err) {
console.error('Notify dealer error:', err);
toast.error('Failed to send notification');
} finally {
setNotifyingDealer(null);
}
};
return (
<div className="space-y-6 min-h-screen bg-gray-50 p-4 md:p-6 w-full">
<div className="w-full min-w-0">
<div className="flex items-center justify-between flex-wrap gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">
Non-Submitted Dealers
</h1>
<p className="text-sm text-gray-600">
Dealers who have not submitted Form 16A for one or more quarters
</p>
</div>
<div className="flex items-center gap-2">
<select
value={financialYearFilter}
onChange={(e) => setFinancialYearFilter(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 bg-white"
>
{FY_OPTIONS.map((opt) => (
<option key={opt.value || 'all'} value={opt.value}>
{opt.value ? `FY ${opt.label}` : opt.label}
</option>
))}
</select>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardHeader className="pb-3">
<CardDescription>Non-Submitted / Total Dealers</CardDescription>
<CardTitle className="text-3xl text-red-600">
{loading
? '...'
: `${summary.nonSubmittedCount}/${summary.totalDealers}`}
</CardTitle>
</CardHeader>
</Card>
<Card>
<CardHeader className="pb-3">
<CardDescription>Never Submitted</CardDescription>
<CardTitle className="text-3xl text-amber-600">
{loading ? '...' : summary.neverSubmittedCount}
</CardTitle>
</CardHeader>
</Card>
<Card>
<CardHeader className="pb-3">
<CardDescription>Overdue (90+ days)</CardDescription>
<CardTitle className="text-3xl text-orange-600">
{loading ? '...' : summary.overdue90Count}
</CardTitle>
</CardHeader>
</Card>
</div>
{/* Dealers Table */}
<Card>
<CardHeader>
<CardTitle>Dealer List</CardTitle>
<CardDescription>
Dealers with missing Form 16A submissions
</CardDescription>
</CardHeader>
<CardContent>
<div className="rounded-lg border overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Dealer Details</TableHead>
<TableHead>Contact Information</TableHead>
<TableHead>Location</TableHead>
<TableHead>Missing Quarters</TableHead>
<TableHead>Last Submission</TableHead>
<TableHead>Last Notified</TableHead>
<TableHead className="text-right">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-12">
<Loader2 className="w-6 h-6 animate-spin mx-auto mb-2 text-teal-600" />
<p className="text-gray-500">Loading dealers...</p>
</TableCell>
</TableRow>
) : dealers.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-12 text-gray-500">
<p>No dealers with missing quarters found</p>
<p className="text-sm mt-1">
All dealers have submitted Form 16A for the selected period
</p>
</TableCell>
</TableRow>
) : (
dealers.map((dealer) => (
<TableRow
key={dealer.id}
className="hover:bg-gray-50 transition-colors"
>
<TableCell>
<div>
<p className="text-sm font-medium">{dealer.dealerName}</p>
<p className="text-xs text-gray-500">{dealer.dealerCode}</p>
</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="flex items-center gap-1.5 text-xs text-gray-600">
<Mail className="w-3.5 h-3.5 shrink-0" />
<span>{dealer.email || '—'}</span>
</div>
<div className="flex items-center gap-1.5 text-xs text-gray-600">
<Phone className="w-3.5 h-3.5 shrink-0" />
<span>{dealer.phone || '—'}</span>
</div>
</div>
</TableCell>
<TableCell className="text-sm">{dealer.location || '—'}</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{dealer.missingQuarters.map((quarter, index) => (
<Badge
key={index}
variant="destructive"
className="text-xs"
>
{quarter}
</Badge>
))}
</div>
</TableCell>
<TableCell>
{dealer.lastSubmissionDate ? (
<div>
<p className="text-sm">{formatDisplayDate(dealer.lastSubmissionDate)}</p>
<p
className={`text-xs ${
dealer.daysSinceLastSubmission != null &&
dealer.daysSinceLastSubmission > 90
? 'text-red-600'
: 'text-gray-500'
}`}
>
{dealer.daysSinceLastSubmission} days ago
</p>
</div>
) : (
<Badge
variant="outline"
className="text-xs bg-red-50 text-red-700 border-red-200"
>
Never Submitted
</Badge>
)}
</TableCell>
<TableCell>
{dealer.lastNotifiedDate ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="space-y-1 cursor-help">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5 text-xs text-gray-600">
<Calendar className="w-3.5 h-3.5 shrink-0" />
<span>{formatDisplayDate(dealer.lastNotifiedDate)}</span>
</div>
{dealer.notificationCount > 1 && (
<Badge
variant="outline"
className="text-xs bg-blue-50 text-blue-700 border-blue-200"
>
{dealer.notificationCount}x
</Badge>
)}
</div>
<div className="flex items-center gap-1.5 text-xs text-gray-600">
<User className="w-3.5 h-3.5 shrink-0" />
<span>{dealer.lastNotifiedBy}</span>
</div>
</div>
</TooltipTrigger>
<TooltipContent side="left" className="max-w-xs">
<div className="space-y-2">
<p className="text-xs font-medium">
Notification History ({dealer.notificationCount})
</p>
<div className="space-y-1.5">
{dealer.notificationHistory?.map((notification, idx) => (
<div
key={idx}
className="text-xs border-l-2 border-blue-400 pl-2 py-0.5"
>
<div className="flex justify-between gap-3">
<span className="text-gray-600">
{notification.date}
</span>
<Badge
variant="outline"
className="text-xs h-4 px-1"
>
{notification.method}
</Badge>
</div>
<p className="text-gray-500 text-xs">
by {notification.notifiedBy}
</p>
</div>
))}
</div>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<Badge
variant="outline"
className="text-xs bg-gray-50 text-gray-600 border-gray-200"
>
Never Notified
</Badge>
)}
</TableCell>
<TableCell className="text-right">
<Button
size="sm"
onClick={() => handleNotifyDealer(dealer)}
disabled={notifyingDealer === dealer.id}
className="bg-teal-600 hover:bg-teal-700 text-white"
>
<Bell className="w-4 h-4 mr-1" />
{notifyingDealer === dealer.id ? 'Sending...' : 'Notify'}
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
</div>
);
}