545 lines
25 KiB
TypeScript
545 lines
25 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { IndianRupee, Calendar, Eye, Send, FileCheck, Loader2 } from 'lucide-react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
|
import { Badge } from '../ui/badge';
|
|
import { Button } from '../ui/button';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
|
import { User } from '../../lib/mock-data';
|
|
import { API } from '../../api/API';
|
|
import { toast } from 'sonner';
|
|
|
|
interface FnFPageProps {
|
|
currentUser: User | null;
|
|
onViewDetails: (id: string) => void;
|
|
}
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'New':
|
|
return 'bg-blue-100 text-blue-700 border-blue-300';
|
|
case 'In Progress':
|
|
return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
|
case 'Under Review':
|
|
return 'bg-orange-100 text-orange-700 border-orange-300';
|
|
case 'Completed':
|
|
return 'bg-green-100 text-green-700 border-green-300';
|
|
default:
|
|
return 'bg-slate-100 text-slate-700 border-slate-300';
|
|
}
|
|
};
|
|
|
|
const getTypeColor = (type: string) => {
|
|
return type === 'Resignation'
|
|
? 'bg-amber-100 text-amber-700 border-amber-300'
|
|
: 'bg-red-100 text-red-700 border-red-300';
|
|
};
|
|
|
|
export function FnFPage({ currentUser, onViewDetails }: FnFPageProps) {
|
|
const [settlements, setSettlements] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const canSendToStakeholders = currentUser &&
|
|
['DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Super Admin'].includes(currentUser.role);
|
|
|
|
useEffect(() => {
|
|
fetchSettlements();
|
|
}, []);
|
|
|
|
const fetchSettlements = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await API.getFnFSettlements();
|
|
const data = response.data as any;
|
|
if (data.success) {
|
|
setSettlements(data.settlements || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('Fetch settlements error:', error);
|
|
toast.error('Failed to fetch settlement cases');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSendToStakeholders = (caseId: string) => {
|
|
console.log('Sending to stakeholders for case:', caseId);
|
|
toast.success('Notifications sent to all stakeholders');
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-12">
|
|
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Helper to map backend data to UI-friendly format
|
|
const getMappedData = (s: any) => ({
|
|
id: s.id,
|
|
caseNumber: s.id.substring(0, 8).toUpperCase(),
|
|
status: s.status,
|
|
requestType: s.resignationId ? 'Resignation' : 'Termination',
|
|
dealerName: s.outlet?.dealer?.name || 'N/A',
|
|
dealerCode: s.outlet?.code || 'N/A',
|
|
dealershipName: s.outlet?.name || 'N/A',
|
|
location: s.outlet?.city || s.outlet?.location || 'N/A',
|
|
originalRequestId: s.resignation?.resignationId || s.terminationRequest?.id || 'N/A',
|
|
submittedOn: new Date(s.createdAt).toLocaleDateString(),
|
|
financeReportStatus: s.status === 'Calculated' || s.status === 'Settled' ? 'Completed' : 'Pending',
|
|
totalRecoveryAmount: parseFloat(s.totalReceivables) || 0,
|
|
totalPayableAmount: parseFloat(s.totalPayables) || 0,
|
|
completedOn: s.settlementDate ? new Date(s.settlementDate).toLocaleDateString() : null,
|
|
departmentResponses: s.lineItems || []
|
|
});
|
|
|
|
const displaySettlements: any[] = settlements.map(getMappedData);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>New Cases</CardDescription>
|
|
<CardTitle className="text-3xl text-blue-600">
|
|
{displaySettlements.filter(c => c.status === 'Initiated' || c.status === 'New').length}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Just Arrived</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>In Progress</CardDescription>
|
|
<CardTitle className="text-3xl text-yellow-600">
|
|
{displaySettlements.filter(c => c.status === 'In Progress').length}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Awaiting Response</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Under Review</CardDescription>
|
|
<CardTitle className="text-3xl text-orange-600">
|
|
{displaySettlements.filter(c => c.status === 'Under Review' || c.status === 'Calculated').length}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Discussion Ongoing</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Completed</CardDescription>
|
|
<CardTitle className="text-3xl text-green-600">
|
|
{displaySettlements.filter(c => c.status === 'Completed' || c.status === 'Settled').length}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Finalized</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>All Cases</CardDescription>
|
|
<CardTitle className="text-3xl">{displaySettlements.length}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Total</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Full & Final Settlement Cases</CardTitle>
|
|
<CardDescription>
|
|
Manage dealer exit dues clearance and settlement
|
|
{currentUser && ` • Current Role: ${currentUser.role}`}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs defaultValue="all" className="w-full">
|
|
<TabsList>
|
|
<TabsTrigger value="new">New Cases</TabsTrigger>
|
|
<TabsTrigger value="all">All Cases</TabsTrigger>
|
|
<TabsTrigger value="progress">In Progress</TabsTrigger>
|
|
<TabsTrigger value="review">Under Review</TabsTrigger>
|
|
<TabsTrigger value="completed">Completed</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* New Cases Tab */}
|
|
<TabsContent value="new" className="mt-6">
|
|
<div className="space-y-4">
|
|
{displaySettlements
|
|
.filter(c => c.status === 'New' || c.status === 'Initiated')
|
|
.map((fnfCase) => (
|
|
<Card key={fnfCase.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-blue-100 rounded-lg">
|
|
<FileCheck className="w-6 h-6 text-blue-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{fnfCase.caseNumber}</h3>
|
|
<Badge className={getStatusColor(fnfCase.status)}>
|
|
{fnfCase.status}
|
|
</Badge>
|
|
<Badge className={getTypeColor(fnfCase.requestType)}>
|
|
{fnfCase.requestType}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{fnfCase.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Dealer Code</p>
|
|
<p>{fnfCase.dealerCode}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Dealership Name</p>
|
|
<p>{fnfCase.dealershipName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{fnfCase.location}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Original Request ID</p>
|
|
<p>{fnfCase.originalRequestId}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<div className="flex items-center gap-1">
|
|
<Calendar className="w-4 h-4 text-slate-500" />
|
|
<p>{fnfCase.submittedOn}</p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Finance Report</p>
|
|
<p>{fnfCase.financeReportStatus}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 ml-4">
|
|
{canSendToStakeholders && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="text-blue-600 border-blue-300 hover:bg-blue-50"
|
|
onClick={() => handleSendToStakeholders(fnfCase.id)}
|
|
>
|
|
<Send className="w-4 h-4 mr-2" />
|
|
Send to Stakeholders
|
|
</Button>
|
|
)}
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(fnfCase.id)}
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{displaySettlements.filter((c: any) => c.status === 'New' || c.status === 'Initiated').length === 0 && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<FileCheck className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No new cases to display</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* All Cases Tab */}
|
|
<TabsContent value="all" className="mt-6">
|
|
<div className="space-y-4">
|
|
{displaySettlements.map((fnfCase) => (
|
|
<Card key={fnfCase.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className={`p-3 rounded-lg ${
|
|
fnfCase.status === 'New' ? 'bg-blue-100' :
|
|
fnfCase.status === 'In Progress' ? 'bg-yellow-100' :
|
|
fnfCase.status === 'Under Review' ? 'bg-orange-100' :
|
|
'bg-green-100'
|
|
}`}>
|
|
<IndianRupee className={`w-6 h-6 ${
|
|
fnfCase.status === 'New' ? 'text-blue-600' :
|
|
fnfCase.status === 'In Progress' ? 'text-yellow-600' :
|
|
fnfCase.status === 'Under Review' ? 'text-orange-600' :
|
|
'text-green-600'
|
|
}`} />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{fnfCase.caseNumber}</h3>
|
|
<Badge className={getStatusColor(fnfCase.status)}>
|
|
{fnfCase.status}
|
|
</Badge>
|
|
<Badge className={getTypeColor(fnfCase.requestType)}>
|
|
{fnfCase.requestType}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{fnfCase.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Dealership Name</p>
|
|
<p>{fnfCase.dealershipName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{fnfCase.location}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<p>{fnfCase.submittedOn}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 ml-4">
|
|
{canSendToStakeholders && fnfCase.status === 'New' && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="text-blue-600 border-blue-300 hover:bg-blue-50"
|
|
onClick={() => handleSendToStakeholders(fnfCase.id)}
|
|
>
|
|
<Send className="w-4 h-4 mr-2" />
|
|
Send to Stakeholders
|
|
</Button>
|
|
)}
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(fnfCase.id)}
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* In Progress Tab */}
|
|
<TabsContent value="progress" className="mt-6">
|
|
<div className="space-y-4">
|
|
{displaySettlements
|
|
.filter(c => c.status === 'In Progress')
|
|
.map((fnfCase) => (
|
|
<Card key={fnfCase.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-yellow-100 rounded-lg">
|
|
<IndianRupee className="w-6 h-6 text-yellow-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{fnfCase.caseNumber}</h3>
|
|
<Badge className={getStatusColor(fnfCase.status)}>
|
|
{fnfCase.status}
|
|
</Badge>
|
|
<Badge className={getTypeColor(fnfCase.requestType)}>
|
|
{fnfCase.requestType}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{fnfCase.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Departments Responded</p>
|
|
<p>
|
|
{fnfCase.departmentResponses.filter((d: any) => d.status !== 'Pending').length} / {fnfCase.departmentResponses.length}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<p>{fnfCase.submittedOn}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(fnfCase.id)}
|
|
className="ml-4"
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{displaySettlements.filter((c: any) => c.status === 'In Progress').length === 0 && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<IndianRupee className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No cases in progress</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Under Review Tab */}
|
|
<TabsContent value="review" className="mt-6">
|
|
<div className="space-y-4">
|
|
{displaySettlements
|
|
.filter(c => c.status === 'Under Review' || c.status === 'Calculated')
|
|
.map((fnfCase) => (
|
|
<Card key={fnfCase.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-orange-100 rounded-lg">
|
|
<IndianRupee className="w-6 h-6 text-orange-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{fnfCase.caseNumber}</h3>
|
|
<Badge className={getStatusColor(fnfCase.status)}>
|
|
{fnfCase.status}
|
|
</Badge>
|
|
<Badge className={getTypeColor(fnfCase.requestType)}>
|
|
{fnfCase.requestType}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{fnfCase.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Recovery Amount</p>
|
|
<p className="text-red-600">₹{fnfCase.totalRecoveryAmount?.toLocaleString()}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Payable Amount</p>
|
|
<p className="text-green-600">₹{fnfCase.totalPayableAmount?.toLocaleString()}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Finance Status</p>
|
|
<p>{fnfCase.financeReportStatus}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(fnfCase.id)}
|
|
className="ml-4"
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{displaySettlements.filter(c => c.status === 'Under Review' || c.status === 'Calculated').length === 0 && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<IndianRupee className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No cases under review</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Completed Tab */}
|
|
<TabsContent value="completed" className="mt-6">
|
|
<div className="space-y-4">
|
|
{displaySettlements
|
|
.filter(c => c.status === 'Completed' || c.status === 'Settled')
|
|
.map((fnfCase) => (
|
|
<Card key={fnfCase.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-green-100 rounded-lg">
|
|
<FileCheck className="w-6 h-6 text-green-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{fnfCase.caseNumber}</h3>
|
|
<Badge className={getStatusColor(fnfCase.status)}>
|
|
{fnfCase.status}
|
|
</Badge>
|
|
<Badge className={getTypeColor(fnfCase.requestType)}>
|
|
{fnfCase.requestType}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{fnfCase.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Completed On</p>
|
|
<p>{fnfCase.completedOn || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<p>{fnfCase.submittedOn}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(fnfCase.id)}
|
|
className="ml-4"
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{displaySettlements.filter(c => c.status === 'Completed' || c.status === 'Settled').length === 0 && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<FileCheck className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No completed cases</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|