diff --git a/src/api/API.ts b/src/api/API.ts index 37e0180..aacac4a 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -97,7 +97,7 @@ export const API = { deleteUser: (id: string) => client.delete(`/admin/users/${id}`), // Dealer & Outlets - getDealers: () => client.get('/dealer'), + getDealers: (params?: { onboarded?: string }) => client.get('/dealer', { params }), createDealer: (data: any) => client.post('/dealer', data), getDealerById: (id: string) => client.get(`/dealer/${id}`), updateDealer: (id: string, data: any) => client.put(`/dealer/${id}`, data), @@ -180,8 +180,11 @@ export const API = { headers: { 'Content-Type': 'multipart/form-data' } }), verifyRelocationDocument: (id: string, documentId: string) => client.post(`/relocation/${id}/documents/${documentId}/verify`), + rejectRelocationDocument: (id: string, documentId: string, data?: any) => + client.post(`/relocation/${id}/documents/${documentId}/reject`, data || {}), getConstitutionalChanges: () => client.get('/constitutional-change'), + getConstitutionalChangeMeta: () => client.get('/constitutional-change/meta'), getConstitutionalChangeById: (id: string) => client.get(`/constitutional-change/${id}`), createConstitutionalChange: (data: any) => client.post('/constitutional-change', data), updateConstitutionalChange: (id: string, action: string, data?: any) => client.post(`/constitutional-change/${id}/action`, { action, ...data }), diff --git a/src/components/applications/ApplicationDetails.tsx b/src/components/applications/ApplicationDetails.tsx index 3bcb27b..e7a93fa 100644 --- a/src/components/applications/ApplicationDetails.tsx +++ b/src/components/applications/ApplicationDetails.tsx @@ -255,6 +255,26 @@ const KT_MATRIX_CRITERIA = [ } ]; +function auditLogActionBadgeClass(action: string): string { + const a = String(action || '').toUpperCase(); + if (a.includes('REJECT') || a.includes('DELET') || a.includes('DISQUALIF')) { + return 'border-red-200 bg-red-50/90 text-red-800'; + } + if (a === 'CREATED' || a.includes('APPROV') || a.includes('COMPLETE')) { + return 'border-emerald-200 bg-emerald-50/90 text-emerald-900'; + } + if (a.includes('DOCUMENT') || a.includes('UPLOAD') || a.includes('ATTACHMENT')) { + return 'border-sky-200 bg-sky-50/80 text-sky-900'; + } + if (a.includes('PAYMENT') || a.includes('SECURITY') || a.includes('DEPOSIT')) { + return 'border-violet-200 bg-violet-50/80 text-violet-900'; + } + if (a.includes('FDD') || a.includes('QUESTIONNAIRE') || a.includes('INTERVIEW')) { + return 'border-amber-200 bg-amber-50/80 text-amber-900'; + } + return 'border-slate-200 bg-slate-50 text-slate-700'; +} + export const ApplicationDetails = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); @@ -442,7 +462,7 @@ export const ApplicationDetails = () => { const fetchAuditLogs = async () => { setAuditLoading(true); try { - const logs = await auditService.getAuditLogs('application', application.id); + const logs = await auditService.getAuditLogs('application', application.id, 1, 100); setAuditLogs(Array.isArray(logs) ? logs : []); } catch (error) { console.error('Failed to fetch audit logs', error); @@ -730,6 +750,7 @@ export const ApplicationDetails = () => { // Reset form setKtMatrixScores({}); + setKtMatrixSelectedValues({}); setKtMatrixRemarks(''); await fetchInterviews(); await fetchApplication(); // Refresh application status and progress @@ -1205,7 +1226,28 @@ export const ApplicationDetails = () => { { id: 8, name: 'LOI Approval', - status: getStageStatus('LOI Approval', () => ['Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Work', 'Statutory Work', 'LOA Pending', 'LOA Issued', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'LOI In Progress' ? 'active' : 'pending'), + status: getStageStatus('LOI Approval', () => + [ + 'Security Details', + 'Payment Pending', + 'LOI Issued', + 'Statutory LOI Ack', + 'Dealer Code Generation', + 'Architecture Work', + 'Statutory Work', + 'LOA Pending', + 'LOA Issued', + 'EOR In Progress', + 'EOR Complete', + 'Inauguration', + 'Approved', + 'Onboarded', + ].includes(application.status) + ? 'completed' + : application.status === 'LOI In Progress' + ? 'active' + : 'pending', + ), date: application.loiApprovalDate, description: 'Letter of Intent approval', evaluators: Array.from(new Set((application.participants || []) @@ -1217,7 +1259,26 @@ export const ApplicationDetails = () => { { id: 9, name: 'Security Details', - status: getStageStatus('Security Details', () => ['LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Work', 'Statutory Work', 'LOA Pending', 'LOA Issued', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Payment Pending' ? 'active' : 'pending'), + status: getStageStatus('Security Details', () => + [ + 'LOI Issued', + 'Statutory LOI Ack', + 'Dealer Code Generation', + 'Architecture Work', + 'Statutory Work', + 'LOA Pending', + 'LOA Issued', + 'EOR In Progress', + 'EOR Complete', + 'Inauguration', + 'Approved', + 'Onboarded', + ].includes(application.status) + ? 'completed' + : application.status === 'Security Details' || application.status === 'Payment Pending' + ? 'active' + : 'pending', + ), date: application.securityDetailsDate, description: 'Security verification', documentsUploaded: 3 @@ -1225,10 +1286,27 @@ export const ApplicationDetails = () => { { id: 10, name: 'LOI Issue', - status: getStageStatus('LOI Issue', () => - ['Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Work', 'Statutory Work', 'LOA Pending', 'LOA Issued', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : - application.status === 'LOI Issued' ? 'active' : 'pending' - ), + status: getStageStatus('LOI Issue', () => { + if ( + [ + 'Statutory LOI Ack', + 'Dealer Code Generation', + 'Architecture Work', + 'Statutory Work', + 'LOA Pending', + 'LOA Issued', + 'EOR In Progress', + 'EOR Complete', + 'Inauguration', + 'Approved', + 'Onboarded', + ].includes(application.status) + ) { + return 'completed'; + } + if (application.status === 'LOI Issued') return 'active'; + return 'pending'; + }), date: application.loiIssueDate, description: 'Letter of Intent issued', documentsUploaded: 1 @@ -1446,6 +1524,7 @@ export const ApplicationDetails = () => { case 'FDD Verification': newStatus = 'LOI In Progress'; break; case 'LOI In Progress': + newStatus = 'Security Details'; break; case 'Security Details': case 'Payment Pending': newStatus = 'LOI Issued'; break; @@ -1773,7 +1852,14 @@ export const ApplicationDetails = () => { // Centralized Permissions Utility (Consolidates 500 lines of fragmented logic) const getApplicationPermissions = () => { if (!application || !currentUser) { - return { canApprove: false, canReject: false, canSchedule: false, canAssign: false, isLoaLocked: false, showDecisionMessage: false }; + return { + canApprove: false, + canReject: false, + canSchedule: false, + canAssign: false, + isLoaLocked: false, + showDecisionMessage: false, + }; } // 1. Core Flags @@ -2491,7 +2577,7 @@ export const ApplicationDetails = () => { -
+
Questionnaire Progress @@ -3400,41 +3486,63 @@ export const ApplicationDetails = () => { {/* Audit Trail Tab */} - -
+ +
{auditLoading ? ( -
-
- Loading audit trail... +
+
+ Loading audit trail…
) : auditLogs.length === 0 ? ( -
+
No audit logs recorded yet for this application.
) : ( auditLogs.map((log: any) => ( -
-
-
-
-

{log.description || log.action}

- - {formatDateTime(log.timestamp)} - +
+
+
+ + {String(log.action || 'EVENT').replace(/_/g, ' ')} + + {log.stage ? ( + + {log.stage} + + ) : null}
-

by {log.userName || 'System'}

- {log.remarks && ( -

- "{log.remarks}" -

- )} - {log.changes && log.changes.length > 0 && ( -
- {log.changes.map((change: string, idx: number) => ( -

{change}

- ))} -
- )} + +
+

+ {log.description || '—'} +

+
+ + + + {log.userName || 'System'} + + {log.userEmail ? ( + · {log.userEmail} + ) : null} +
)) @@ -3509,9 +3617,40 @@ export const ApplicationDetails = () => { {permissions.isLoaLocked && ( - Stage Locked + LOA approval locked - First Fill (₹15L) must be verified by Finance before LOA Approval can proceed. + First Fill (later-stage payment) must be verified by Finance + before LOA approval can proceed. This is separate from the initial security deposit before LOI Issued. + + + )} + + {getDeposit('FIRST_FILL')?.status === 'Verified' && + application.status !== 'LOA Pending' && + !['LOA Issued', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded', 'Rejected'].includes( + application.status, + ) && ( + + + First Fill verified on file + + Finance has verified the First Fill payment. The application + status was not changed until you reach{' '} + LOA Pending. When you get there, LOA approval will not be + blocked by payment (same pattern as recording the initial security deposit before the LOI + security step). + + + )} + + {['Security Details', 'Payment Pending'].includes(application.status) && ( + + + Security Details review + + Check the initial security deposit on the Payments tab (Finance + may have already marked it verified). When satisfied, use Approve{' '} + to move to LOI Issued. )} @@ -3537,24 +3676,24 @@ export const ApplicationDetails = () => { )} {permissions.canApprove && ( - <> - + + )} - - + {permissions.canReject && ( + )} {permissions.showDecisionMessage && ( @@ -4187,120 +4326,83 @@ export const ApplicationDetails = () => { - {/* KT Matrix Modal */} + {/* KT Matrix — Level 1 */} - - {/* Ultra-Simple Header */} -
-
- KT Matrix Assessment -

Evaluate technical capability for {application.name}

-
-
-
{Object.keys(ktMatrixSelectedValues).length} of {KT_MATRIX_CRITERIA.length} Completed
- -
-
+ + + KT matrix + + Level 1 interview · {application.name} + + {Object.keys(ktMatrixSelectedValues).length} of {KT_MATRIX_CRITERIA.length} criteria answered + + + -
-
- {/* Question List - Minimalist Style */} -
- {KT_MATRIX_CRITERIA.map((criterion, idx) => ( -
-
-

- {idx + 1}. - {criterion.name} -

- - WT: {criterion.weight}% - -
+
+
+ {KT_MATRIX_CRITERIA.map((criterion, idx) => ( +
+ + +
+ ))} -
- {criterion.options.map((option) => { - const isSelected = ktMatrixSelectedValues[criterion.name] === option.value; - return ( -
handleKTMatrixChange(criterion.name, option.value, option.score)} - className={cn( - "px-3 py-1.5 rounded-lg border text-[11px] font-bold cursor-pointer transition-all flex items-center gap-2 select-none", - isSelected - ? "bg-slate-900 border-slate-900 text-white shadow-md" - : "bg-white border-slate-200 text-slate-500 hover:border-slate-400" - )} - > - {isSelected && } - {option.label} - - [{option.score}] - -
- ); - })} -
-
- ))} -
- - {/* Remarks Component */} -
- +
+