diff --git a/src/api/API.ts b/src/api/API.ts index 2fe5a79..65a1278 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -122,7 +122,9 @@ export const API = { // Resignation getResignationById: (id: string) => client.get(`/resignation/${id}`), - updateClearance: (id: string, data: any) => client.post(`/resignation/${id}/clearance`, data), + updateClearance: (id: string, data: any) => client.put(`/resignation/${id}/clearance`, data, { + headers: data instanceof FormData ? { 'Content-Type': 'multipart/form-data' } : {} + }), updateResignationStatus: (id: string, data: any) => client.post(`/resignation/${id}/status`, data), // Termination @@ -144,6 +146,8 @@ export const API = { createTermination: (data: any) => client.post('/termination', data), updateTermination: (id: string, data: any) => client.post(`/termination/${id}/status`, data), + getOnboardingPayments: () => client.get('/settlement/onboarding'), + updatePayment: (id: string, data: any) => client.put(`/settlement/payments/${id}`, data), getFnFSettlements: () => client.get('/settlement/fnf'), getFnFSettlementById: (id: string) => client.get(`/settlement/fnf/${id}`), calculateFnF: (id: string) => client.post(`/settlement/fnf/${id}/calculate`), diff --git a/src/components/applications/ApplicationDetails.tsx b/src/components/applications/ApplicationDetails.tsx index 5887865..23dc554 100644 --- a/src/components/applications/ApplicationDetails.tsx +++ b/src/components/applications/ApplicationDetails.tsx @@ -1183,18 +1183,17 @@ export const ApplicationDetails = () => { name: 'Statutory Documents', color: 'green', stages: [ - { id: '11b-1', name: 'GST Certificate', status: isDocumentUploaded('Statutory GST') || isDocumentUploaded('GST Certificate') ? 'completed' : 'active', description: 'GST details' }, - { id: '11b-2', name: 'PAN Card', status: isDocumentUploaded('Statutory PAN') || isDocumentUploaded('PAN Card') ? 'completed' : 'active', description: 'PAN details' }, - { id: '11b-3', name: 'Nodal Agreement', status: isDocumentUploaded('Statutory Nodal') || isDocumentUploaded('Nodal Agreement') ? 'completed' : 'active', description: 'Nodal details' }, - { id: '11b-4', name: 'Cancelled Check', status: isDocumentUploaded('Statutory Check') || isDocumentUploaded('Cancelled Check') ? 'completed' : 'active', description: 'Bank verification' }, - { id: '11b-5', name: 'Partnership Deed', status: isDocumentUploaded('Statutory Partnership') || isDocumentUploaded('Partnership Deed') ? 'completed' : 'active', description: 'Legal constitution' }, - { id: '11b-6', name: 'Firm Registration', status: isDocumentUploaded('Statutory Firm Reg') || isDocumentUploaded('Firm Registration') ? 'completed' : 'active', description: 'RoC/Firm reg' }, - { id: '11b-7', name: 'Virtual Code', status: isDocumentUploaded('Statutory Virtual Code') || isDocumentUploaded('Virtual Code Confirmation') ? 'completed' : 'active', description: 'Oracle setup' }, - { id: '11b-8', name: 'Domain ID', status: isDocumentUploaded('Statutory Domain') || isDocumentUploaded('Domain ID Setup') ? 'completed' : 'active', description: 'Email setup' }, - { id: '11b-9', name: 'MSD Configuration', status: isDocumentUploaded('Statutory MSD') || isDocumentUploaded('MSD Configuration') ? 'completed' : 'active', description: 'Multiple Security Deposit' }, - { id: '11b-10', name: 'LOI Acknowledgement', status: isDocumentUploaded('Statutory LOI Ack') || isDocumentUploaded('LOI Acknowledgement') ? 'completed' : 'active', description: 'LOI Signed copy' }, - { id: '11b-11', name: 'Board Resolution', status: isDocumentUploaded('Board Resolution') || isDocumentUploaded('Authorization Proof') ? 'completed' : 'active', description: 'Legal authorization' }, - { id: '11b-12', name: 'Consolidated Approval', status: application.statutoryStatus === 'COMPLETED' ? 'completed' : 'active', description: 'Managerial sign-off' } + { id: '11b-1', name: 'GST', status: isDocumentUploaded('GST Certificate') || isDocumentUploaded('GST') ? 'completed' : 'active', description: 'GST certificate' }, + { id: '11b-2', name: 'PAN', status: isDocumentUploaded('PAN Card') || isDocumentUploaded('PAN') ? 'completed' : 'active', description: 'PAN card' }, + { id: '11b-3', name: 'Nodal Agreement', status: isDocumentUploaded('Nodal Agreement') ? 'completed' : 'active', description: 'Nodal agreement document' }, + { id: '11b-4', name: 'Cancelled Check', status: isDocumentUploaded('Cancelled Check') ? 'completed' : 'active', description: 'Cancelled check copy' }, + { id: '11b-5', name: 'Partnership Deed/LLP/MOA/AOA/COI', status: isDocumentUploaded('Partnership Deed/LLP/MOA/AOA/COI') || isDocumentUploaded('Partnership Deed') ? 'completed' : 'active', description: 'Business entity documents' }, + { id: '11b-6', name: 'Firm Registration Certificate', status: isDocumentUploaded('Firm Registration Certificate') || isDocumentUploaded('Firm Registration') ? 'completed' : 'active', description: 'Firm registration certificate' }, + { id: '11b-7', name: 'Rental agreement/ Lease agreement / Own/ Land agreement', status: isDocumentUploaded('Rental agreement/ Lease agreement / Own/ Land agreement') || isDocumentUploaded('Property Document') ? 'completed' : 'active', description: 'Property agreement document' }, + { id: '11b-8', name: 'Virtual Code', status: isDocumentUploaded('Virtual Code') || isDocumentUploaded('Virtual Code Confirmation') ? 'completed' : 'active', description: 'Virtual code availability' }, + { id: '11b-9', name: 'Domain ID', status: isDocumentUploaded('Domain ID') || isDocumentUploaded('Domain ID Setup') ? 'completed' : 'active', description: 'Domain ID setup' }, + { id: '11b-10', name: 'MSD Configuration', status: isDocumentUploaded('MSD Configuration') ? 'completed' : 'active', description: 'Microsoft Dynamics configuration' }, + { id: '11b-11', name: 'LOI Acknowledgement Copy', status: isDocumentUploaded('LOI Acknowledgement Copy') || isDocumentUploaded('LOI Acknowledgement') ? 'completed' : 'active', description: 'LOI acknowledgement copy' } ] } ] diff --git a/src/components/applications/ConstitutionalChangeDetails.tsx b/src/components/applications/ConstitutionalChangeDetails.tsx index d0befe4..f510b83 100644 --- a/src/components/applications/ConstitutionalChangeDetails.tsx +++ b/src/components/applications/ConstitutionalChangeDetails.tsx @@ -13,6 +13,7 @@ import { User as UserType } from '../../lib/mock-data'; import { toast } from 'sonner'; import { API } from '../../api/API'; import { useNavigate } from 'react-router-dom'; +import { formatDateTime } from '../ui/utils'; interface ConstitutionalChangeDetailsProps { requestId: string; @@ -20,34 +21,30 @@ interface ConstitutionalChangeDetailsProps { currentUser: UserType | null; } -// Workflow stages as per the process flow +// Workflow stages as per the process flow (SRS 12.2.4) const workflowStages = [ - { id: 1, name: 'Request Created', key: 'created', role: 'Dealer' }, - { id: 2, name: 'ASM Review', key: 'asm', role: 'ASM' }, - { id: 3, name: 'RBM Review', key: 'rbm', role: 'RBM' }, - { id: 4, name: 'DD ZM Review', key: 'dd-zm', role: 'DD-ZM' }, - { id: 5, name: 'ZBH Review', key: 'zbh', role: 'ZBH' }, - { id: 6, name: 'DD Lead Review', key: 'dd-lead', role: 'DD Lead' }, - { id: 7, name: 'FDD Review', key: 'fdd', role: 'FDD' }, - { id: 8, name: 'DD Head Review', key: 'dd-head', role: 'DD Head' }, - { id: 9, name: 'NBH Review', key: 'nbh', role: 'NBH' }, - { id: 10, name: 'Docs Collection by DD H.O', key: 'docs-collection', role: 'DD H.O' }, - { id: 11, name: 'New Code Creation', key: 'code-creation', role: 'DD Admin' }, - { id: 12, name: 'New LOA Issuance', key: 'loa-issuance', role: 'DD Admin' }, - { id: 13, name: 'Closure of Request', key: 'closure', role: 'System' } + { id: 1, name: 'Submitted', key: 'submitted', role: 'Dealer' }, + { id: 2, name: 'ASM Review', key: 'asm-review', role: 'ASM' }, + { id: 3, name: 'ZM/RBM Review', key: 'zm-rbm-review', role: 'ZM/RBM' }, + { id: 4, name: 'ZBH Review', key: 'zbh-review', role: 'ZBH' }, + { id: 5, name: 'DD Lead Review', key: 'lead-review', role: 'DD Lead' }, + { id: 6, name: 'DD Head Review', key: 'head-review', role: 'DD Head' }, + { id: 7, name: 'NBH Approval', key: 'nbh-approval', role: 'NBH' }, + { id: 8, name: 'Legal Review', key: 'legal-review', role: 'Legal Team' }, + { id: 9, name: 'Completed', key: 'completed', role: 'System' } ]; // Document requirements mapping (same as in ConstitutionalChangePage) const documentRequirements: Record = { 'Partnership': [1, 2, 3, 4, 8, 9, 10, 16], - 'LLP': [1, 2, 3, 7, 8, 9, 10, 16], + 'LLP': [1, 2, 3, 7, 8, 9, 10, 11, 16], 'Pvt Ltd': [1, 2, 3, 5, 6, 7, 8, 10, 16], 'Proprietorship': [1, 2, 3, 10, 16] }; const documentNames: Record = { - 1: 'GST', - 2: 'Firm Pan Copy', + 1: 'GST Certificate', + 2: 'Firm PAN Copy', 3: 'Self attested KYC\'s', 4: 'Partnership Agreement (Notarised)', 5: 'MOA (Applicable for Only Pvt.Ltd)', @@ -71,6 +68,7 @@ const getTypeColor = (type: string) => { case 'Proprietorship': return 'bg-purple-100 text-purple-700 border-purple-300'; case 'Partnership': return 'bg-blue-100 text-blue-700 border-blue-300'; case 'LLP': return 'bg-indigo-100 text-indigo-700 border-indigo-300'; + case 'Private Limited': case 'Pvt Ltd': return 'bg-cyan-100 text-cyan-700 border-cyan-300'; default: return 'bg-slate-100 text-slate-700 border-slate-300'; } @@ -78,7 +76,7 @@ const getTypeColor = (type: string) => { const getStatusColor = (status: string) => { if (status === 'Completed' || status === 'Verified') return 'bg-green-100 text-green-700 border-green-300'; - if (status.includes('Review') || status.includes('Pending') || status === 'In Progress') return 'bg-yellow-100 text-yellow-700 border-yellow-300'; + if (status.includes('Review') || status.includes('Pending') || status === 'In Progress' || status === 'Submitted') return 'bg-yellow-100 text-yellow-700 border-yellow-300'; if (status.includes('Rejected')) return 'bg-red-100 text-red-700 border-red-300'; return 'bg-slate-100 text-slate-700 border-slate-300'; }; @@ -136,20 +134,18 @@ export function ConstitutionalChangeDetails({ requestId, onBack }: Constitutiona // Get required documents for this request const requiredDocs = documentRequirements[request.changeType] || []; - // Calculate current stage index + // Calculate current stage index mapping to backend stages const getCurrentStageIndex = () => { const stageMap: Record = { - 'Dealer': 1, - 'ASM': 2, - 'RBM': 3, - 'DD-ZM': 4, - 'ZBH': 5, - 'DD Lead': 6, - 'FDD': 7, - 'DD Head': 8, - 'NBH': 9, - 'DD H.O': 10, - 'Closed': 13 + 'Submitted': 1, + 'ASM Review': 2, + 'ZM/RBM Review': 3, + 'ZBH Review': 4, + 'DD Lead Review': 5, + 'DD Head Review': 6, + 'NBH Approval': 7, + 'Legal Review': 8, + 'Completed': 9 }; return stageMap[request.currentStage] || 1; }; @@ -243,7 +239,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack }: Constitutiona

Request Information

-

Submitted: {new Date(request.createdAt).toLocaleDateString()}

+

Submitted: {formatDateTime(request.createdAt)}

By: {request.dealer?.fullName || 'Dealer'}

Current Stage: {request.currentStage}

@@ -469,7 +465,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack }: Constitutiona {doc.fileName || doc.name} - {new Date(doc.uploadedOn || doc.createdAt).toLocaleDateString()} + {formatDateTime(doc.uploadedOn || doc.createdAt)} {doc.uploadedBy || 'Dealer'} @@ -533,7 +529,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack }: Constitutiona

{entry.comments || entry.remarks || 'No remarks provided'}

-

{new Date(entry.date || entry.createdAt || entry.timestamp).toLocaleString()}

+

{formatDateTime(entry.date || entry.createdAt || entry.timestamp)}

))} diff --git a/src/components/applications/ConstitutionalChangePage.tsx b/src/components/applications/ConstitutionalChangePage.tsx index e6191fb..88665b8 100644 --- a/src/components/applications/ConstitutionalChangePage.tsx +++ b/src/components/applications/ConstitutionalChangePage.tsx @@ -13,6 +13,7 @@ import { useState, useEffect } from 'react'; import { User as UserType } from '../../lib/mock-data'; import { toast } from 'sonner'; import { API } from '../../api/API'; +import { formatDateTime } from '../ui/utils'; interface ConstitutionalChangePageProps { currentUser: UserType | null; @@ -465,7 +466,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
-
{new Date(request.createdAt).toLocaleDateString()}
+
{formatDateTime(request.createdAt)}
By {request.dealer?.fullName || 'Dealer'}
@@ -677,7 +678,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange -
{new Date(request.createdAt).toLocaleDateString()}
+
{formatDateTime(request.createdAt)}
+ )} + + ))} @@ -1606,6 +1672,12 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr + + setPreviewDocument(null)} + document={previewDocument} + /> ); } diff --git a/src/components/applications/FinanceFnFPage.tsx b/src/components/applications/FinanceFnFPage.tsx index af5be86..ac531bf 100644 --- a/src/components/applications/FinanceFnFPage.tsx +++ b/src/components/applications/FinanceFnFPage.tsx @@ -76,6 +76,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) { const getMappedData = (s: any) => ({ id: s.id, + caseId: s.resignation?.resignationId || s.id, dealerCode: s.outlet?.code || 'N/A', dealerName: s.outlet?.dealer?.name || 'N/A', location: s.outlet?.city || s.outlet?.location || 'N/A', @@ -201,13 +202,18 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) { - Net Receivable + Net Financial Position
-
₹2.5L
+
sum + (s.financialData.netAmount || 0), 0) < 0 ? 'text-green-600' : 'text-red-600'}`}> + ₹{Math.abs(displaySettlements.reduce((sum, s) => sum + (s.financialData.netAmount || 0), 0)).toLocaleString('en-IN')} +
+

+ {displaySettlements.reduce((sum, s) => sum + (s.financialData.netAmount || 0), 0) < 0 ? 'Net Recovery' : 'Net Payable'} +

@@ -264,7 +270,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
-
{fnfCase.id}
+
{fnfCase.caseId}
{fnfCase.dealerCode}
diff --git a/src/components/applications/FnFDetails.tsx b/src/components/applications/FnFDetails.tsx index 5798ade..ad2a7f9 100644 --- a/src/components/applications/FnFDetails.tsx +++ b/src/components/applications/FnFDetails.tsx @@ -8,10 +8,11 @@ import { Label } from '../ui/label'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; import { Progress } from '../ui/progress'; import { useState, useEffect } from 'react'; -import { User, mockDocuments, mockAuditLogs } from '../../lib/mock-data'; +import { User, mockAuditLogs } from '../../lib/mock-data'; import { API } from '../../api/API'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; +import { DocumentPreviewModal } from '../ui/DocumentPreviewModal'; interface FnFDetailsProps { fnfId: string; @@ -24,6 +25,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { const [fnfCase, setFnfCase] = useState(null); const [loading, setLoading] = useState(true); const [sendStakeholdersDialog, setSendStakeholdersDialog] = useState(false); + const [previewDocument, setPreviewDocument] = useState(null); useEffect(() => { fetchFnFDetails(); @@ -35,14 +37,14 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { const response = await API.getFnFSettlementById(fnfId); const data = response.data as any; if (data.success) { - const s = data.settlement; + const s = data.fnf; // Map backend data to UI format const mappedCase = { id: s.id, - caseNumber: s.id.substring(0, 8).toUpperCase(), + caseNumber: s.resignation?.resignationId || s.id, status: s.status, requestType: s.resignationId ? 'Resignation' : 'Termination', - dealerName: s.outlet?.dealer?.name || 'N/A', + dealerName: s.outlet?.dealer?.fullName || 'N/A', dealerCode: s.outlet?.code || 'N/A', dealershipName: s.outlet?.name || 'N/A', location: s.outlet?.city || s.outlet?.location || 'N/A', @@ -51,19 +53,43 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { lastOperationalDateSales: s.resignation?.lastWorkingDay || s.terminationRequest?.effectiveDate || 'N/A', lastOperationalDateServices: s.resignation?.lastWorkingDay || s.terminationRequest?.effectiveDate || 'N/A', typeOfClosure: s.resignationId ? 'Voluntary' : 'Involuntary', - gst: s.outlet?.dealer?.pan || 'N/A', // Using PAN as placeholder if GST not available + gst: s.outlet?.dealer?.dealerProfile?.gstNumber || 'N/A', + pan: s.outlet?.dealer?.dealerProfile?.panNumber || 'N/A', financeReportStatus: s.status === 'Calculated' || s.status === 'Settled' ? 'Completed' : 'Pending', totalPayableAmount: parseFloat(s.totalPayables) || 0, totalRecoveryAmount: parseFloat(s.totalReceivables) || 0, - departmentResponses: (s.lineItems || []).map((li: any) => ({ - id: li.id, - departmentName: li.department, - status: li.remarks && li.remarks.toLowerCase().includes('no dues') ? 'No Dues' : (li.amount > 0 ? 'Dues' : 'Pending'), - amountType: li.amount > 0 ? 'Recovery Amount' : null, - amount: Math.abs(parseFloat(li.amount)) || 0, - submittedDate: li.updatedAt ? new Date(li.updatedAt).toLocaleDateString() : null, - remarks: li.remarks - })) + departmentResponses: [ + 'Warranty', 'Accessories', 'Sales', 'RTO', 'Service', 'Parts', + 'Finance', 'Insurance', 'Inventory', 'Marketing', 'HR', 'IT', + 'Legal', 'Quality', 'Logistics', 'Customer Relations' + ].map((deptName: string) => { + const c = (s.clearances || []).find((clearance: any) => clearance.department === deptName); + const lineItem = (s.lineItems || []).find((li: any) => li.department === deptName); + + return { + id: c?.id || `dept-${deptName}`, + departmentName: deptName, + status: c?.status || 'Pending', + amountType: lineItem ? (parseFloat(lineItem.amount) > 0 ? 'Recovery' : 'Payable') : null, + amount: lineItem ? Math.abs(parseFloat(lineItem.amount)) : 0, + submittedDate: c?.clearedAt ? new Date(c.clearedAt).toLocaleString() : null, + remarks: c?.remarks || '-', + supportingDocument: c?.supportingDocument || null + }; + }), + documents: [ + { id: 'res-letter', name: 'Resignation Letter.pdf', type: 'Resignation', uploadDate: new Date(s.createdAt).toLocaleDateString(), status: 'Verified', url: '#' }, + ...(s.clearances || []) + .filter((c: any) => c.supportingDocument) + .map((c: any) => ({ + id: c.id, + name: c.supportingDocument.split('/').pop(), + type: `${c.department} Proof`, + uploadDate: new Date(c.clearedAt).toLocaleDateString(), + status: 'Attached', + url: c.supportingDocument + })) + ] }; setFnfCase(mappedCase); } @@ -128,9 +154,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { const getDepartmentStatusColor = (status: string) => { switch (status) { - case 'No Dues': + case 'NOC Submitted': return 'bg-green-100 text-green-700 border-green-300'; - case 'Dues': + case 'Dues Pending': return 'bg-red-100 text-red-700 border-red-300'; case 'Pending': return 'bg-slate-100 text-slate-700 border-slate-300'; @@ -169,7 +195,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { onClick={() => navigate(`/worknotes/fnf/${fnfId}`, { state: { applicationName: fnfCase.dealerName || 'F&F Settlement', - registrationNumber: fnfId || '', + registrationNumber: fnfCase.caseNumber || '', participants: fnfCase.participants || [] } })} @@ -200,15 +226,15 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
-

No Dues

+

NOC Submitted

- {fnfCase.departmentResponses.filter((d: any) => d.status === 'No Dues').length} + {fnfCase.departmentResponses.filter((d: any) => d.status === 'NOC Submitted').length}

-

Dues

+

Dues Pending

- {fnfCase.departmentResponses.filter((d: any) => d.status === 'Dues').length} + {fnfCase.departmentResponses.filter((d: any) => d.status === 'Dues Pending').length}

@@ -351,12 +377,12 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
-

No Dues

-

{fnfCase.departmentResponses.filter((d: any) => d.status === 'No Dues').length}

+

NOC Submitted

+

{fnfCase.departmentResponses.filter((d: any) => d.status === 'NOC Submitted').length}

-

Dues

-

{fnfCase.departmentResponses.filter((d: any) => d.status === 'Dues').length}

+

Dues Pending

+

{fnfCase.departmentResponses.filter((d: any) => d.status === 'Dues Pending').length}

Pending

@@ -767,7 +793,12 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { {dept.amountType ? ( - + {dept.amountType} ) : ( @@ -776,7 +807,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { {dept.amount ? ( - + ₹{dept.amount.toLocaleString()} ) : ( @@ -890,7 +921,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { - {mockDocuments.map((doc: any) => ( + {fnfCase.documents.map((doc: any) => (
@@ -906,7 +937,23 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { - + ))} @@ -980,6 +1027,12 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { + + setPreviewDocument(null)} + document={previewDocument} + />
); } diff --git a/src/components/applications/ResignationDetails.tsx b/src/components/applications/ResignationDetails.tsx index ab9a8fd..42b9f32 100644 --- a/src/components/applications/ResignationDetails.tsx +++ b/src/components/applications/ResignationDetails.tsx @@ -1,4 +1,4 @@ -import { ArrowLeft, Check, X, RotateCcw, UserPlus, MessageSquare, FileText, Calendar, Send } from 'lucide-react'; +import { ArrowLeft, Check, X, RotateCcw, UserPlus, MessageSquare, FileText, Calendar, Send, Upload, Eye } from 'lucide-react'; import { Button } from '../ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; @@ -15,6 +15,14 @@ import { toast } from 'sonner'; import { resignationService } from '../../services/resignation.service'; import { Loader2 } from 'lucide-react'; import { API } from '../../api/API'; +import { DocumentPreviewModal } from '../ui/DocumentPreviewModal'; +import { formatDateTime } from '../ui/utils'; + +const ALL_DEPARTMENTS = [ + 'Warranty', 'Accessories', 'Sales', 'RTO', 'Service', 'Parts', + 'Finance', 'Insurance', 'Inventory', 'Marketing', 'HR', 'IT', + 'Legal', 'Quality', 'Logistics', 'Customer Relations' +]; interface ResignationDetailsProps { resignationId: string; @@ -29,13 +37,17 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig const [assignToUser, setAssignToUser] = useState(''); const [stageDocumentsDialog, setStageDocumentsDialog] = useState<{ open: boolean; stageName: string; documents: any[] }>({ open: false, stageName: '', documents: [] }); const [showClearanceDialog, setShowClearanceDialog] = useState(false); - const [selectedDept, setSelectedDept] = useState(null); - const [clearanceStatus, setClearanceStatus] = useState<'Cleared' | 'Pending' | 'Rejected'>('Cleared'); + const [selectedDept, setSelectedDept] = useState(''); + const [clearanceStatus, setClearanceStatus] = useState('Pending'); const [clearanceRemarks, setClearanceRemarks] = useState(''); + const [clearanceAmount, setClearanceAmount] = useState(0); + const [clearanceType, setClearanceType] = useState<'Payable' | 'Recovery'>('Recovery'); + const [clearanceFile, setClearanceFile] = useState(null); const [isUpdatingClearance, setIsUpdatingClearance] = useState(false); const [resignationData, setResignationData] = useState(null); // Real data from API const [isLoading, setIsLoading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); + const [previewDocument, setPreviewDocument] = useState(null); const fetchResignation = async () => { try { @@ -58,25 +70,26 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig // Progress stages logic based on live data const progressStages = [ - { id: 1, name: 'Request Submitted', key: 'Submitted', description: 'Resignation request created' }, - { id: 2, name: 'ASM Review', key: 'ASM', description: 'Area Sales Manager review' }, - { id: 3, name: 'Departmental Clearances', key: 'Clearance', description: 'Clearance from departments' }, - { id: 4, name: 'RBM + DD ZM Review', key: 'RBM', description: 'Regional Business Manager and DD ZM evaluation' }, - { id: 5, name: 'ZBH Review', key: 'ZBH', description: 'Zonal Business Head approval' }, - { id: 6, name: 'DD Lead Review', key: 'DD Lead', description: 'DD Lead final review' }, - { id: 7, name: 'NBH Approval', key: 'NBH', description: 'National Business Head approval' }, - { id: 8, name: 'Legal - Resignation Letter', key: 'Legal', description: 'Legal team issues resignation approval letter' } + { id: 1, name: 'ASM Review', key: 'ASM', description: 'Area Sales Manager review' }, + { id: 2, name: 'RBM Review', key: 'RBM', description: 'Regional Business Manager evaluation' }, + { id: 3, name: 'ZBH Review', key: 'ZBH', description: 'Zonal Business Head approval' }, + { id: 4, name: 'DD Lead Review', key: 'DD Lead', description: 'DD Lead final review' }, + { id: 5, name: 'NBH Approval', key: 'NBH', description: 'National Business Head approval' }, + { id: 6, name: 'DD Admin Review', key: 'DD Admin', description: 'DD Admin verification' }, + { id: 7, name: 'Legal - Resignation Letter', key: 'Legal', description: 'Legal team issues resignation approval letter' }, + { id: 8, name: 'F&F Settlement', key: 'F&F Initiated', description: 'Full & Final settlement process' }, + { id: 9, name: 'Completed', key: 'Completed', description: 'Resignation process finalized' } ]; const getStageStatus = (stageKey: string) => { if (!resignationData) return 'pending'; const currentStage = resignationData.currentStage; - // Simple logic for simulation - in real app, this would be more complex - const stagesOrdered = ['Submitted', 'ASM', 'Clearance', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'Legal']; + const stagesOrdered = ['ASM', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'DD Admin', 'Legal', 'F&F Initiated', 'Completed']; const currentIndex = stagesOrdered.indexOf(currentStage); const stageIndex = stagesOrdered.indexOf(stageKey); + if (currentIndex === -1) return 'pending'; // Fallback for rejected/other states if (stageIndex < currentIndex) return 'completed'; if (stageIndex === currentIndex) return 'active'; return 'pending'; @@ -129,16 +142,25 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig if (!selectedDept) return; try { setIsUpdatingClearance(true); - await resignationService.updateClearance(resignationId, { - department: selectedDept, - status: clearanceStatus, - remarks: clearanceRemarks - }); - toast.success(`${selectedDept} clearance updated`); + + const formData = new FormData(); + formData.append('department', selectedDept); + formData.append('status', clearanceStatus); + formData.append('remarks', clearanceRemarks); + formData.append('amount', String(clearanceAmount)); + formData.append('type', clearanceType); + + if (clearanceFile) { + formData.append('file', clearanceFile); + } + + await resignationService.updateClearance(resignationId, formData); + toast.success(`Successfully updated clearance for ${selectedDept}`); setShowClearanceDialog(false); + setClearanceFile(null); fetchResignation(); } catch (error) { - toast.error('Failed to update clearance'); + toast.error('Failed to update clearance status'); } finally { setIsUpdatingClearance(false); } @@ -161,7 +183,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
-

{resignationId}

+

{resignationData?.resignationId || resignationId}

{resignationData?.outlet?.name}

@@ -216,7 +238,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig {/* Secondary Actions */} {currentUser?.role !== 'Dealer' && (
- {canPushToFnF && ( + {canPushToFnF && resignationData?.status !== 'FNF_INITIATED' && resignationData?.status !== 'Settled' && (
@@ -379,7 +401,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
-

{resignationData?.submittedOn ? new Date(resignationData.submittedOn).toLocaleDateString() : 'N/A'}

+

{resignationData?.submittedOn ? formatDateTime(resignationData.submittedOn) : 'N/A'}

@@ -434,7 +456,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig {timelineEntry && (
- {new Date(timelineEntry.timestamp || timelineEntry.createdAt).toLocaleDateString()} + {formatDateTime(timelineEntry.timestamp || timelineEntry.createdAt)}
)}
@@ -472,31 +494,91 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
- {(resignationData?.clearances || []).map((clearance: any) => ( - + {ALL_DEPARTMENTS.map((dept) => { + const settlement = resignationData?.settlement; + const fffClearance = (settlement?.clearances || []).find((c: any) => c.department === dept); + const relatedLineItems = (settlement?.lineItems || []).filter((li: any) => li.department === dept); + const lineItemAmount = relatedLineItems.reduce((sum: number, li: any) => sum + parseFloat(li.amount || 0), 0); + + // Use standardized JSON field but override with live F&F data if available + const jsonClearance = (resignationData?.departmentalClearances || {})[dept] || { status: 'Pending', remarks: '', amount: 0, type: 'Recovery' }; + + const displayStatus = fffClearance ? (fffClearance.status === 'NOC Submitted' ? 'Cleared' : fffClearance.status === 'Pending' ? 'Pending' : 'Dues') : jsonClearance.status; + const displayRemarks = fffClearance ? fffClearance.remarks : jsonClearance.remarks; + const displayAmount = fffClearance ? Math.abs(lineItemAmount) : jsonClearance.amount; + const displayType = fffClearance ? (lineItemAmount < 0 ? 'Payable' : 'Recovery') : jsonClearance.type; + + return ( + - {clearance.department} + {dept} - {clearance.status} + {displayStatus || 'Pending'} -

- {clearance.remarks || 'No remarks provided'} -

- {currentUser && (currentUser.role === 'Super Admin' || currentUser.role === 'DD Admin' || (currentUser.role.includes(clearance.department) && resignationData?.currentStage === 'Clearance')) && ( +
+
+ Amount: ₹{(displayAmount || 0).toLocaleString()} + + {displayType || 'Recovery'} + +
+
+

+ {displayRemarks || 'Awaiting departmental verification.'} +

+ + {fffClearance?.supportingDocument && ( +
+
+
+ +
+
+

Evidence Attached

+ + {fffClearance.supportingDocument.split('/').pop()?.substring(0, 12)}... + +
+
+ +
+ )} +
+
+ {currentUser && (currentUser.role === 'Super Admin' || currentUser.role === 'DD Admin' || (currentUser.role.includes(dept) && resignationData?.currentStage === 'Clearance' || resignationData?.currentStage === 'F&F Initiated')) && (
@@ -530,8 +613,36 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig - {(resignationData?.documents || []).length > 0 ? ( - (resignationData.documents || []).map((doc: any, index: number) => ( + {(() => { + const allDocs = [ + ...(resignationData?.documents || []), + ...(resignationData?.uploadedDocuments || []) + ]; + + // Add clearance documents + if (resignationData?.departmentalClearances) { + Object.entries(resignationData.departmentalClearances).forEach(([dept, data]: [string, any]) => { + if (data.supportingDocument) { + allDocs.push({ + name: `${dept} Clearance Proof`, + type: 'Clearance NOC', + path: data.supportingDocument, + createdAt: data.updatedAt, + uploadedBy: data.updatedBy || 'Department Admin' + }); + } + }); + } + + if (allDocs.length === 0) return ( + + + No documents found + + + ); + + return allDocs.map((doc: any, index: number) => (
@@ -539,21 +650,33 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig {doc.name || doc.fileName}
- {doc.type || 'Document'} - {doc.createdAt ? new Date(doc.createdAt).toLocaleDateString() : 'N/A'} - {doc.uploadedBy || 'Dealer'} + {doc.documentType || doc.type || 'Document'} + {doc.createdAt ? formatDateTime(doc.createdAt) : 'N/A'} + {doc.uploader?.fullName || doc.uploadedBy || 'Dealer'} - +
)) - ) : ( - - - No documents found - - - )} + })() + }
@@ -576,7 +699,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig

{log.action || log.status}

- {new Date(log.timestamp || log.createdAt).toLocaleString()} + {formatDateTime(log.timestamp || log.createdAt)}

{log.user || log.actor}

{log.comments &&

{log.comments}

} @@ -765,10 +888,34 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig Cleared Pending / In-Review - Rejected / Dues Owed + Dues / Outstanding
+ +
+
+ + setClearanceAmount(Number(e.target.value))} + /> +
+
+ + +
+