881 lines
39 KiB
TypeScript
881 lines
39 KiB
TypeScript
import { DollarSign, FileText, CheckCircle, Plus, Trash2, Save, Calculator, Clock, TrendingUp, TrendingDown, ShieldCheck } 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 { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
|
|
import { Input } from '../ui/input';
|
|
import { Label } from '../ui/label';
|
|
import { Textarea } from '../ui/textarea';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table';
|
|
import { useState, useEffect } from 'react';
|
|
import { User } from '../../lib/mock-data';
|
|
import { toast } from 'sonner';
|
|
import { onboardingService } from '../../services/onboarding.service';
|
|
|
|
interface FinanceDashboardProps {
|
|
currentUser: User | null;
|
|
onNavigate?: (view: string) => void;
|
|
onViewPaymentDetails?: (applicationId: string) => void;
|
|
onViewAuditDetails?: (applicationId: string) => void;
|
|
onViewFnFDetails?: (fnfId: string) => void;
|
|
}
|
|
|
|
// Mock data for F&F cases (Keeping as static for now as per original)
|
|
const mockFnFCases = [
|
|
{
|
|
id: 'RES-001',
|
|
dealerName: 'Amit Sharma Motors',
|
|
dealerCode: 'DL-MH-001',
|
|
type: 'Resignation',
|
|
location: 'Mumbai, Maharashtra',
|
|
status: 'Pending Finance Summary',
|
|
submittedOn: '2025-10-08',
|
|
departmentsResponded: 16,
|
|
totalDepartments: 16,
|
|
hasFinanceSummary: false
|
|
},
|
|
{
|
|
id: 'TERM-002',
|
|
dealerName: 'Sanjay Enterprises',
|
|
dealerCode: 'DL-TG-033',
|
|
type: 'Termination',
|
|
location: 'Hyderabad, Telangana',
|
|
status: 'Finance Summary Completed',
|
|
submittedOn: '2025-09-20',
|
|
departmentsResponded: 16,
|
|
totalDepartments: 16,
|
|
hasFinanceSummary: true,
|
|
netAmount: -270000,
|
|
completedOn: '2025-10-10'
|
|
}
|
|
];
|
|
|
|
interface FinanceLineItem {
|
|
id: string;
|
|
department: string;
|
|
description: string;
|
|
type: 'payable' | 'recovery';
|
|
amount: number;
|
|
}
|
|
|
|
export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAuditDetails, onViewFnFDetails }: FinanceDashboardProps) {
|
|
const [applications, setApplications] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, []);
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const data = await onboardingService.getApplications();
|
|
// Filter for applications relevant to finance
|
|
const financeApps = data.filter((app: any) => {
|
|
const s = app.overallStatus || app.status;
|
|
const stage = app.currentStage;
|
|
return [
|
|
'LOI In Progress', 'LOI Issued', 'LOA Pending', 'Dealer Code Generation',
|
|
'LOI_APPROVAL', 'LOA_APPROVAL', 'PAYMENT_VERIFICATION', 'SECURITY_DEPOSIT',
|
|
'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'
|
|
].includes(s) || stage === 'Finance';
|
|
});
|
|
setApplications(financeApps);
|
|
} catch (error) {
|
|
console.error('Fetch error:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const [fnfDialog, setFnfDialog] = useState(false);
|
|
const [fnfDetailsDialog, setFnfDetailsDialog] = useState(false);
|
|
const [selectedFnF, setSelectedFnF] = useState<any>(null);
|
|
const [lineItems, setLineItems] = useState<FinanceLineItem[]>([]);
|
|
const [newLineItem, setNewLineItem] = useState({
|
|
department: '',
|
|
description: '',
|
|
type: 'recovery' as 'payable' | 'recovery',
|
|
amount: ''
|
|
});
|
|
const [finalRemarks, setFinalRemarks] = useState('');
|
|
|
|
|
|
const handleAddLineItem = () => {
|
|
if (!newLineItem.department || !newLineItem.description || !newLineItem.amount) {
|
|
toast.error('Please fill in all line item fields');
|
|
return;
|
|
}
|
|
|
|
const item: FinanceLineItem = {
|
|
id: Date.now().toString(),
|
|
department: newLineItem.department,
|
|
description: newLineItem.description,
|
|
type: newLineItem.type,
|
|
amount: parseFloat(newLineItem.amount)
|
|
};
|
|
|
|
setLineItems([...lineItems, item]);
|
|
setNewLineItem({ department: '', description: '', type: 'recovery', amount: '' });
|
|
toast.success('Line item added');
|
|
};
|
|
|
|
const handleRemoveLineItem = (id: string) => {
|
|
setLineItems(lineItems.filter(item => item.id !== id));
|
|
toast.info('Line item removed');
|
|
};
|
|
|
|
const calculateTotals = () => {
|
|
const totalRecovery = lineItems
|
|
.filter(item => item.type === 'recovery')
|
|
.reduce((sum, item) => sum + item.amount, 0);
|
|
|
|
const totalPayable = lineItems
|
|
.filter(item => item.type === 'payable')
|
|
.reduce((sum, item) => sum + item.amount, 0);
|
|
|
|
const netAmount = totalPayable - totalRecovery;
|
|
|
|
return { totalRecovery, totalPayable, netAmount };
|
|
};
|
|
|
|
const handleSubmitFinanceSummary = () => {
|
|
if (lineItems.length === 0) {
|
|
toast.error('Please add at least one line item');
|
|
return;
|
|
}
|
|
if (!finalRemarks) {
|
|
toast.error('Please add final remarks');
|
|
return;
|
|
}
|
|
|
|
toast.success('Finance summary submitted successfully');
|
|
setFnfDialog(false);
|
|
setSelectedFnF(null);
|
|
setLineItems([]);
|
|
setFinalRemarks('');
|
|
};
|
|
|
|
|
|
const getRelevantPaymentStatus = (app: any) => {
|
|
if (!app.securityDeposits || app.securityDeposits.length === 0) return 'Awaiting Payment';
|
|
|
|
const s = app.overallStatus || app.status || '';
|
|
const relevantType = (s.includes('LOI') || s === 'PAYMENT_VERIFICATION') ? 'INITIAL' : 'FINAL';
|
|
const deposit = app.securityDeposits.find((d: any) => d.depositType === relevantType);
|
|
|
|
return deposit ? deposit.status : 'Awaiting Payment';
|
|
};
|
|
|
|
const pendingOnboarding = applications.filter(app => getRelevantPaymentStatus(app) !== 'Verified');
|
|
const verifiedOnboarding = applications.filter(app => getRelevantPaymentStatus(app) === 'Verified');
|
|
const pendingAudits = applications.filter(app => app.status === 'LOI_APPROVAL' || app.overallStatus === 'LOI In Progress');
|
|
const pendingFnF = mockFnFCases.filter(f => !f.hasFinanceSummary);
|
|
const completedFnF = mockFnFCases.filter(f => f.hasFinanceSummary);
|
|
|
|
const { totalRecovery, totalPayable, netAmount } = calculateTotals();
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-20 text-amber-600">
|
|
<Clock className="w-8 h-8 animate-spin mr-3" />
|
|
<span>Loading Finance Data...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div>
|
|
<h1 className="text-3xl mb-2">Finance Dashboard</h1>
|
|
<p className="text-slate-600">
|
|
Verify payments and create financial settlement summaries
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-3">
|
|
<Card
|
|
className="cursor-pointer hover:shadow-lg transition-shadow border-amber-200 bg-amber-50/20"
|
|
onClick={() => {
|
|
if (pendingAudits.length > 0 && onViewAuditDetails) {
|
|
onViewAuditDetails(pendingAudits[0].applicationId || pendingAudits[0].id);
|
|
} else {
|
|
onNavigate?.('finance-onboarding');
|
|
}
|
|
}}
|
|
>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription className="text-amber-600 font-bold">Pending Audits</CardDescription>
|
|
<CardTitle className="text-3xl text-amber-600">{pendingAudits.length}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600 text-xs font-medium">FDD Sign-offs</p>
|
|
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2 text-xs">
|
|
Review Now →
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card
|
|
className="cursor-pointer hover:shadow-lg transition-shadow border-yellow-200"
|
|
onClick={() => onNavigate?.('finance-onboarding')}
|
|
>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Pending Verification</CardDescription>
|
|
<CardTitle className="text-3xl text-yellow-600">{pendingOnboarding.length}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Onboarding Payments</p>
|
|
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
|
View All →
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card
|
|
className="cursor-pointer hover:shadow-lg transition-shadow border-green-200"
|
|
onClick={() => onNavigate?.('finance-onboarding')}
|
|
>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Verified</CardDescription>
|
|
<CardTitle className="text-3xl text-green-600">{verifiedOnboarding.length}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Total Validated</p>
|
|
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
|
View All →
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card
|
|
className="cursor-pointer hover:shadow-lg transition-shadow border-orange-200"
|
|
onClick={() => onNavigate?.('finance-fnf')}
|
|
>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Pending F&F Summary</CardDescription>
|
|
<CardTitle className="text-3xl text-orange-600">{pendingFnF.length}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Offboarding Cases</p>
|
|
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
|
View All →
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card
|
|
className="cursor-pointer hover:shadow-lg transition-shadow border-green-200"
|
|
onClick={() => onNavigate?.('finance-fnf')}
|
|
>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>F&F Completed</CardDescription>
|
|
<CardTitle className="text-3xl text-green-600">{completedFnF.length}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Settlements Done</p>
|
|
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
|
View All →
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Main Tabs */}
|
|
<Tabs defaultValue="onboarding" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<TabsTrigger value="onboarding">
|
|
<DollarSign className="w-4 h-4 mr-2" />
|
|
Onboarding
|
|
</TabsTrigger>
|
|
<TabsTrigger value="fnf">
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
F&F Settlement
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Onboarding Tab */}
|
|
<TabsContent value="onboarding" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Payment Verification</CardTitle>
|
|
<CardDescription>
|
|
Verify dealer advance payments for onboarding applications
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs defaultValue="pending" className="w-full">
|
|
<TabsList>
|
|
<TabsTrigger value="pending">
|
|
Pending ({pendingOnboarding.length})
|
|
</TabsTrigger>
|
|
<TabsTrigger value="verified">
|
|
Verified ({verifiedOnboarding.length})
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="pending" className="mt-6">
|
|
<div className="space-y-4">
|
|
{pendingOnboarding.map((app) => (
|
|
<Card key={app.id} className="border-yellow-200 bg-yellow-50/30">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<h3 className="text-lg font-bold">{app.applicationId || app.id}</h3>
|
|
<Badge className="bg-yellow-100 text-yellow-700 border-yellow-300">
|
|
{app.status}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Applicant Name</p>
|
|
<p className="font-medium">{app.applicantName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{app.city || app.preferredLocation}, {app.state}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Stage</p>
|
|
<p className="text-amber-700 font-bold">
|
|
{app.status === 'LOI_APPROVAL' || app.status === 'PAYMENT_VERIFICATION' ? 'Initial Deposit' : 'Final Deposit'}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Email</p>
|
|
<p className="truncate">{app.email}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4 flex flex-col gap-2">
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => {
|
|
if (onViewPaymentDetails) {
|
|
onViewPaymentDetails(app.applicationId || app.id);
|
|
}
|
|
}}
|
|
>
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
{app.status === 'LOI_APPROVAL' || app.overallStatus === 'LOI In Progress' ? (
|
|
<Button
|
|
size="sm"
|
|
className="bg-amber-600 hover:bg-amber-700 font-bold"
|
|
onClick={() => {
|
|
if (onViewAuditDetails) {
|
|
onViewAuditDetails(app.applicationId || app.id);
|
|
}
|
|
}}
|
|
>
|
|
<ShieldCheck className="w-4 h-4 mr-2" />
|
|
Review Audit
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
size="sm"
|
|
className="bg-green-600 hover:bg-green-700 font-bold"
|
|
onClick={() => {
|
|
if (onViewPaymentDetails) {
|
|
onViewPaymentDetails(app.applicationId || app.id);
|
|
}
|
|
}}
|
|
>
|
|
<CheckCircle className="w-4 h-4 mr-2" />
|
|
Verify Payment
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{pendingOnboarding.length === 0 && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<CheckCircle className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No pending payment verifications</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="verified" className="mt-6">
|
|
<div className="space-y-4">
|
|
{verifiedOnboarding.map((app) => (
|
|
<Card key={app.id} className="border-green-200 bg-green-50/30">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<h3 className="text-lg font-bold">{app.applicationId || app.id}</h3>
|
|
<Badge className="bg-green-100 text-green-700 border-green-300">
|
|
{getRelevantPaymentStatus(app)}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Applicant Name</p>
|
|
<p className="font-medium">{app.applicantName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{app.city || app.preferredLocation}, {app.state}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Status</p>
|
|
<p className="text-green-700 font-bold">Payment Verified</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Email</p>
|
|
<p className="truncate">{app.email}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4">
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => {
|
|
if (onViewPaymentDetails) {
|
|
onViewPaymentDetails(app.applicationId || app.id);
|
|
}
|
|
}}
|
|
>
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* F&F Settlement Tab */}
|
|
<TabsContent value="fnf" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>F&F Financial Summary</CardTitle>
|
|
<CardDescription>
|
|
Create financial settlement summaries for resignation and termination cases
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs defaultValue="pending" className="w-full">
|
|
<TabsList>
|
|
<TabsTrigger value="pending">
|
|
Pending Summary ({pendingFnF.length})
|
|
</TabsTrigger>
|
|
<TabsTrigger value="completed">
|
|
Completed ({completedFnF.length})
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="pending" className="mt-6">
|
|
<div className="space-y-4">
|
|
{pendingFnF.map((fnf) => (
|
|
<Card key={fnf.id} className="border-orange-200 bg-orange-50/30">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<h3 className="text-lg font-bold">{fnf.id}</h3>
|
|
<Badge variant="outline">{fnf.type}</Badge>
|
|
<Badge className="bg-orange-100 text-orange-700 border-orange-300">
|
|
{fnf.status}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 text-sm mb-3">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p className="font-medium">{fnf.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Dealer Code</p>
|
|
<p>{fnf.dealerCode}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{fnf.location}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<CheckCircle className="w-4 h-4 text-green-600" />
|
|
<span className="text-green-700">{fnf.departmentsResponded}/{fnf.totalDepartments} Departments Responded</span>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4 flex flex-col gap-2">
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => {
|
|
setSelectedFnF(fnf);
|
|
setFnfDetailsDialog(true);
|
|
}}
|
|
>
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
className="bg-blue-600 hover:bg-blue-700"
|
|
onClick={() => {
|
|
setSelectedFnF(fnf);
|
|
setLineItems([]);
|
|
setFinalRemarks('');
|
|
setFnfDialog(true);
|
|
}}
|
|
>
|
|
<Calculator className="w-4 h-4 mr-2" />
|
|
Create Summary
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{pendingFnF.length === 0 && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<FileText className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No F&F cases pending financial summary</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="completed" className="mt-6">
|
|
<div className="space-y-4">
|
|
{completedFnF.map((fnf) => (
|
|
<Card key={fnf.id} className="border-green-200 bg-green-50/30">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<h3 className="text-lg font-bold">{fnf.id}</h3>
|
|
<Badge variant="outline">{fnf.type}</Badge>
|
|
<Badge className="bg-green-100 text-green-700 border-green-300">
|
|
{fnf.status}
|
|
</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 className="font-medium">{fnf.dealerName}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Dealer Code</p>
|
|
<p>{fnf.dealerCode}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Net Settlement</p>
|
|
<p className={fnf.netAmount && fnf.netAmount >= 0 ? 'text-green-700' : 'text-red-700'}>
|
|
₹{fnf.netAmount ? Math.abs(fnf.netAmount).toLocaleString('en-IN') : '0'}
|
|
</p>
|
|
<p className="text-xs text-slate-500">
|
|
{fnf.netAmount && fnf.netAmount >= 0 ? 'Payable to Dealer' : 'Recovery from Dealer'}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Completed On</p>
|
|
<p>{fnf.completedOn}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4">
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => {
|
|
if (onViewFnFDetails) {
|
|
onViewFnFDetails(fnf.id);
|
|
}
|
|
}}
|
|
>
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
{/* F&F Summary Creation Dialog */}
|
|
<Dialog open={fnfDialog} onOpenChange={setFnfDialog}>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Create Financial Settlement Summary</DialogTitle>
|
|
<DialogDescription>
|
|
{selectedFnF?.id} - {selectedFnF?.dealerName} ({selectedFnF?.dealerCode})
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-6">
|
|
{/* Add Line Item Section */}
|
|
<div className="border rounded-lg p-4 bg-slate-50">
|
|
<h3 className="text-sm font-bold mb-4">Add Financial Line Item</h3>
|
|
<div className="grid grid-cols-12 gap-3">
|
|
<div className="col-span-3">
|
|
<Label className="text-xs">Department</Label>
|
|
<Input
|
|
placeholder="e.g., Warranty"
|
|
value={newLineItem.department}
|
|
onChange={(e) => setNewLineItem({...newLineItem, department: e.target.value})}
|
|
/>
|
|
</div>
|
|
<div className="col-span-4">
|
|
<Label className="text-xs">Description</Label>
|
|
<Input
|
|
placeholder="e.g., Pending warranty claims"
|
|
value={newLineItem.description}
|
|
onChange={(e) => setNewLineItem({...newLineItem, description: e.target.value})}
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<Label className="text-xs">Type</Label>
|
|
<Select value={newLineItem.type} onValueChange={(value: 'payable' | 'recovery') => setNewLineItem({...newLineItem, type: value})}>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="recovery">Recovery</SelectItem>
|
|
<SelectItem value="payable">Payable</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<Label className="text-xs">Amount (₹)</Label>
|
|
<Input
|
|
type="number"
|
|
placeholder="0"
|
|
value={newLineItem.amount}
|
|
onChange={(e) => setNewLineItem({...newLineItem, amount: e.target.value})}
|
|
/>
|
|
</div>
|
|
<div className="col-span-1 flex items-end">
|
|
<Button
|
|
size="sm"
|
|
onClick={handleAddLineItem}
|
|
className="w-full bg-amber-600 hover:bg-amber-700"
|
|
>
|
|
<Plus className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Line Items Table */}
|
|
{lineItems.length > 0 && (
|
|
<div className="border rounded-lg overflow-hidden">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Department</TableHead>
|
|
<TableHead>Description</TableHead>
|
|
<TableHead className="text-center">Type</TableHead>
|
|
<TableHead className="text-right">Amount</TableHead>
|
|
<TableHead className="w-16"></TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{lineItems.map((item) => (
|
|
<TableRow key={item.id}>
|
|
<TableCell>{item.department}</TableCell>
|
|
<TableCell>{item.description}</TableCell>
|
|
<TableCell className="text-center">
|
|
<Badge className={item.type === 'recovery' ? 'bg-red-100 text-red-700 border-red-200' : 'bg-green-100 text-green-700 border-green-200'}>
|
|
{item.type === 'recovery' ? 'Recovery' : 'Payable'}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className={`text-right font-medium ${item.type === 'recovery' ? 'text-red-700' : 'text-green-700'}`}>
|
|
₹{item.amount.toLocaleString('en-IN')}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => handleRemoveLineItem(item.id)}
|
|
>
|
|
<Trash2 className="w-4 h-4 text-red-600" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
|
|
{/* Financial Summary */}
|
|
{lineItems.length > 0 && (
|
|
<div className="border rounded-lg p-4 bg-slate-900 text-white">
|
|
<h3 className="text-sm font-bold mb-4">Financial Settlement Summary</h3>
|
|
<div className="grid grid-cols-3 gap-8">
|
|
<div>
|
|
<p className="text-xs text-slate-400 uppercase tracking-wider mb-1">Total Payable</p>
|
|
<p className="text-3xl font-bold text-green-400">₹{totalPayable.toLocaleString('en-IN')}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-400 uppercase tracking-wider mb-1">Total Recovery</p>
|
|
<p className="text-3xl font-bold text-red-400">₹{totalRecovery.toLocaleString('en-IN')}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-400 uppercase tracking-wider mb-1">Net Settlement</p>
|
|
<p className={`text-3xl font-bold ${netAmount >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
|
₹{Math.abs(netAmount).toLocaleString('en-IN')}
|
|
</p>
|
|
<p className="text-xs text-slate-400 mt-1 italic">
|
|
{netAmount >= 0 ? 'Payable to Dealer' : 'Recoverable from Dealer'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Final Remarks */}
|
|
<div className="space-y-2">
|
|
<Label className="font-bold">Summary Remarks <span className="text-red-500">*</span></Label>
|
|
<Textarea
|
|
placeholder="Add your final reconciliation remarks here..."
|
|
rows={4}
|
|
value={finalRemarks}
|
|
onChange={(e) => setFinalRemarks(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="border-t pt-4">
|
|
<Button variant="outline" onClick={() => setFnfDialog(false)}>
|
|
Discard
|
|
</Button>
|
|
<Button
|
|
className="bg-green-600 hover:bg-green-700"
|
|
onClick={handleSubmitFinanceSummary}
|
|
disabled={lineItems.length === 0}
|
|
>
|
|
<Save className="w-4 h-4 mr-2" />
|
|
Finalize & Submit Summary
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* F&F Details View Dialog */}
|
|
<Dialog open={fnfDetailsDialog} onOpenChange={setFnfDetailsDialog}>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Full Settlement Details</DialogTitle>
|
|
<DialogDescription>
|
|
{selectedFnF?.id} - {selectedFnF?.dealerName}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{selectedFnF && (
|
|
<div className="space-y-6">
|
|
<Card className="bg-slate-50 border-none">
|
|
<CardContent className="pt-6 grid grid-cols-3 gap-6">
|
|
<div>
|
|
<p className="text-xs text-slate-500 uppercase">Dealer Code</p>
|
|
<p className="text-lg font-bold">{selectedFnF.dealerCode}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-500 uppercase">Location</p>
|
|
<p className="text-lg font-bold">{selectedFnF.location}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-500 uppercase">Status</p>
|
|
<Badge variant="outline" className="bg-amber-100 text-amber-700 border-amber-200">
|
|
{selectedFnF.status}
|
|
</Badge>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Card className="border-blue-100">
|
|
<CardHeader className="bg-blue-50/50 pb-2">
|
|
<CardTitle className="text-sm font-bold flex items-center gap-2">
|
|
<TrendingUp className="w-4 h-4 text-blue-600" />
|
|
Receivables Check
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-4 space-y-3">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Inventory Value</span>
|
|
<span className="font-bold">₹12,45,000</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Security Deposit</span>
|
|
<span className="font-bold">₹2,00,000</span>
|
|
</div>
|
|
<div className="border-t pt-2 flex justify-between font-bold text-blue-700">
|
|
<span>Total Payables</span>
|
|
<span>₹14,45,000</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-red-100">
|
|
<CardHeader className="bg-red-50/50 pb-2">
|
|
<CardTitle className="text-sm font-bold flex items-center gap-2">
|
|
<TrendingDown className="w-4 h-4 text-red-600" />
|
|
Recoveries Check
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-4 space-y-3">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Outstanding Invoices</span>
|
|
<span className="font-bold">₹8,50,000</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Spares & Oil Dues</span>
|
|
<span className="font-bold">₹1,20,000</span>
|
|
</div>
|
|
<div className="border-t pt-2 flex justify-between font-bold text-red-700">
|
|
<span>Total Recoveries</span>
|
|
<span>₹9,70,000</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="p-4 bg-green-900 text-white rounded-lg flex items-center justify-between">
|
|
<div>
|
|
<p className="text-xs text-green-300 uppercase font-bold">Estimated Net Settlement</p>
|
|
<p className="text-2xl font-bold">₹4,75,000 <span className="text-sm font-normal text-green-200 ml-2">(Payable to Dealer)</span></p>
|
|
</div>
|
|
<Button className="bg-green-600 hover:bg-green-500">
|
|
Generate PDF
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<DialogFooter>
|
|
<Button onClick={() => setFnfDetailsDialog(false)}>Close Overview</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|