1266 lines
55 KiB
TypeScript
1266 lines
55 KiB
TypeScript
import { ArrowLeft, Check, RotateCcw, UserPlus, MessageSquare, FileText, Calendar, AlertTriangle, Send, ShieldCheck, Loader2, Upload } 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';
|
|
import { Badge } from '../ui/badge';
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
|
|
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 { Alert, AlertDescription, AlertTitle } from '../ui/alert';
|
|
import { useState, useEffect } from 'react';
|
|
import { User } from '../../lib/mock-data';
|
|
import { toast } from 'sonner';
|
|
import { terminationService } from '../../services/termination.service';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { API } from '../../api/API';
|
|
import { formatDateTime } from '../ui/utils';
|
|
import { TERMINATION_DOCUMENT_TYPES, TERMINATION_STAGE_OPTIONS } from '../../lib/offboardingDocumentOptions';
|
|
import { DocumentPreviewModal } from '../ui/DocumentPreviewModal';
|
|
import { WIDE_DIALOG_CLASS } from '../../lib/dialogStyles';
|
|
interface TerminationDetailsProps {
|
|
terminationId: string;
|
|
onBack: () => void;
|
|
currentUser: User | null;
|
|
}
|
|
|
|
export function TerminationDetails({ terminationId, onBack, currentUser }: TerminationDetailsProps) {
|
|
const navigate = useNavigate();
|
|
const [actionDialog, setActionDialog] = useState<{ open: boolean; type: 'approve' | 'withdrawal' | 'sendback' | 'assign' | 'pushfnf' | null }>({ open: false, type: null });
|
|
const [remarks, setRemarks] = useState('');
|
|
const [assignToUser, setAssignToUser] = useState('');
|
|
const [stageDocumentsDialog, setStageDocumentsDialog] = useState<{ open: boolean; stageName: string; documents: any[] }>({ open: false, stageName: '', documents: [] });
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [terminationData, setTerminationData] = useState<any>(null);
|
|
const [auditLogs, setAuditLogs] = useState<any[]>([]);
|
|
const [showSCNDialog, setShowSCNDialog] = useState(false);
|
|
const [scnFile, setScnFile] = useState<File | null>(null);
|
|
const [scnRemarks, setScnRemarks] = useState('');
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const [showFinalizeDialog, setShowFinalizeDialog] = useState(false);
|
|
const [finalDecision, setFinalDecision] = useState<'Approve' | 'Reject' | 'Reconsider'>('Approve');
|
|
const [finalRemarks, setFinalRemarks] = useState('');
|
|
const [showUploadDialog, setShowUploadDialog] = useState(false);
|
|
const [uploadFile, setUploadFile] = useState<File | null>(null);
|
|
const [uploadDocType, setUploadDocType] = useState(TERMINATION_DOCUMENT_TYPES[0]);
|
|
const [uploadStage, setUploadStage] = useState('');
|
|
const [previewDocument, setPreviewDocument] = useState<any>(null);
|
|
|
|
const fetchTermination = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
const data = await terminationService.getTerminationById(terminationId);
|
|
setTerminationData(data);
|
|
fetchAuditLogs();
|
|
} catch (error) {
|
|
console.error('Error fetching termination:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const fetchAuditLogs = async () => {
|
|
try {
|
|
const response: any = await API.getAuditLogs('termination', terminationId);
|
|
if (response.data && response.data.success) {
|
|
setAuditLogs(response.data.data || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching audit logs:', error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchTermination();
|
|
}, [terminationId]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center min-h-[400px] space-y-4">
|
|
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
|
|
<p className="text-slate-600">Loading termination details...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const handleIssueSCN = async () => {
|
|
try {
|
|
setIsProcessing(true);
|
|
await terminationService.issueSCN(terminationId, { remarks: scnRemarks });
|
|
toast.success('SCN issued successfully');
|
|
setShowSCNDialog(false);
|
|
fetchTermination();
|
|
} catch (error) {
|
|
toast.error('Failed to issue SCN');
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const handleUploadSCNResponse = async () => {
|
|
if (!scnFile) return;
|
|
try {
|
|
setIsProcessing(true);
|
|
await terminationService.uploadSCNResponse(terminationId, scnFile, scnRemarks);
|
|
toast.success('SCN response uploaded');
|
|
setShowSCNDialog(false);
|
|
fetchTermination();
|
|
} catch (error) {
|
|
toast.error('Failed to upload response');
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const handleFinalize = async () => {
|
|
try {
|
|
setIsProcessing(true);
|
|
await terminationService.finalizeTermination(terminationId, finalDecision, finalRemarks);
|
|
toast.success(`Termination ${finalDecision.toLowerCase()}ed`);
|
|
setShowFinalizeDialog(false);
|
|
fetchTermination();
|
|
} catch (error) {
|
|
toast.error('Failed to finalize termination');
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const handleUploadDocument = async () => {
|
|
if (!uploadFile) {
|
|
toast.error('Please select a file to upload');
|
|
return;
|
|
}
|
|
try {
|
|
setIsProcessing(true);
|
|
const formData = new FormData();
|
|
formData.append('file', uploadFile);
|
|
formData.append('documentType', uploadDocType);
|
|
if (uploadStage) formData.append('stage', uploadStage);
|
|
await terminationService.uploadDocument(terminationId, formData);
|
|
toast.success('Document uploaded successfully');
|
|
setShowUploadDialog(false);
|
|
setUploadFile(null);
|
|
setUploadDocType(TERMINATION_DOCUMENT_TYPES[0]);
|
|
setUploadStage('');
|
|
fetchTermination();
|
|
} catch (error) {
|
|
toast.error('Failed to upload document');
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
// Check if user can push to F&F (DD Lead and above)
|
|
const canPushToFnF = currentUser && ['DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Super Admin'].includes(currentUser.role);
|
|
|
|
// Centralized Permissions Utility for Termination logic (Robust Validation)
|
|
const getTerminationPermissions = () => {
|
|
if (!terminationData || !currentUser) {
|
|
return { canApprove: false, canWithdraw: false, canIssueSCN: false, canUploadSCNResponse: false, canFinalize: false, canPushToFnF: false };
|
|
}
|
|
|
|
const currentStage = terminationData.currentStage;
|
|
const status = terminationData.status;
|
|
const userRole = currentUser.role;
|
|
|
|
const isFinalState = ['Completed', 'Rejected', 'Withdrawn', 'Terminated'].includes(status) || currentStage === 'Terminated';
|
|
const isSettlementPhase = status === 'F&F Initiated' || currentStage === 'F&F Initiated' || status === 'Settled' || status === 'FNF_INITIATED';
|
|
|
|
const isCurrentlyAssigned = userRole === 'Super Admin' || userRole === 'DD Admin' || (
|
|
(currentStage === 'RBM Review' && userRole === 'RBM') ||
|
|
(currentStage === 'ZBH Review' && userRole === 'ZBH') ||
|
|
(currentStage === 'DD Lead Review' && userRole === 'DD Lead') ||
|
|
(currentStage === 'Legal Verification' && userRole === 'Legal Admin') ||
|
|
(currentStage === 'NBH Evaluation' && userRole === 'NBH') ||
|
|
(currentStage === 'NBH Final Approval' && userRole === 'NBH') ||
|
|
(currentStage === 'CCO Approval' && userRole === 'CCO') ||
|
|
(currentStage === 'CEO Final Approval' && userRole === 'CEO') ||
|
|
(currentStage === 'Legal - Termination Letter' && userRole === 'Legal Admin')
|
|
);
|
|
|
|
return {
|
|
canApprove: isCurrentlyAssigned && !isFinalState && !isSettlementPhase && !['Show Cause Notice', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval'].includes(currentStage),
|
|
canIssueSCN: currentStage === 'NBH Evaluation' && (userRole === 'NBH' || userRole === 'Super Admin') && !isFinalState,
|
|
canUploadSCNResponse: currentStage === 'Show Cause Notice' && (['Legal Admin', 'DD Admin', 'Super Admin'].includes(userRole)) && !isFinalState,
|
|
canFinalize: ['NBH Final Approval', 'CCO Approval', 'CEO Final Approval'].includes(currentStage) && (userRole === currentStage.replace(' Approval', '') || userRole === 'Super Admin') && !isFinalState,
|
|
canPushToFnF: canPushToFnF && !isSettlementPhase && !isFinalState,
|
|
canWithdraw: userRole === 'ASM' && currentStage === 'Request Initiated' && !isFinalState,
|
|
isFinalState,
|
|
isSettlementPhase
|
|
};
|
|
};
|
|
|
|
const permissions = getTerminationPermissions();
|
|
|
|
// Use actual data from backend
|
|
const request = terminationData || {};
|
|
|
|
const stageAliases: Record<string, string[]> = {
|
|
'Submitted': ['Submitted', 'Request Initiated'],
|
|
'RBM Review': ['RBM Review'],
|
|
'ZBH Review': ['ZBH Review'],
|
|
'DD Lead Review': ['DD Lead Review'],
|
|
'Legal Verification': ['Legal Verification'],
|
|
'NBH Evaluation': ['NBH Evaluation'],
|
|
'Show Cause Notice (SCN)': ['Show Cause Notice', 'Show Cause Notice (SCN)', 'SCN'],
|
|
'Personal Hearing': ['Personal Hearing'],
|
|
'NBH Final Approval': ['NBH Final Approval'],
|
|
'CCO Approval': ['CCO Approval'],
|
|
'CEO Final Approval': ['CEO Final Approval'],
|
|
'Legal - Termination Letter': ['Legal - Termination Letter'],
|
|
'Dealer Terminated': ['Terminated', 'Dealer Terminated']
|
|
};
|
|
|
|
const allUploadedDocs = [
|
|
...(request.documents || []),
|
|
...(request.uploadedDocuments || [])
|
|
];
|
|
|
|
const stageDocuments: Record<string, any[]> = Object.keys(stageAliases).reduce((acc: Record<string, any[]>, stageName) => {
|
|
const aliases = stageAliases[stageName] || [stageName];
|
|
const docs = allUploadedDocs
|
|
.filter((doc: any) => !doc.stage || aliases.includes(doc.stage))
|
|
.map((doc: any) => ({
|
|
id: doc.id || `${stageName}-${doc.fileName || doc.name}`,
|
|
name: doc.fileName || doc.name || 'Document',
|
|
type: doc.documentType || doc.type || 'Document',
|
|
uploadDate: doc.uploadDate || doc.createdAt ? formatDateTime(doc.uploadDate || doc.createdAt) : 'N/A',
|
|
uploader: doc.uploader?.fullName || doc.uploader || '-',
|
|
path: doc.filePath || doc.path || doc.url
|
|
}));
|
|
acc[stageName] = docs;
|
|
return acc;
|
|
}, {});
|
|
|
|
const getLatestStageTimelineEntry = (stageName: string) => {
|
|
const aliases = stageAliases[stageName] || [stageName];
|
|
const entries = (request.timeline || []).filter((entry: any) =>
|
|
aliases.includes(entry.stage) || aliases.includes(entry.targetStage)
|
|
);
|
|
return entries.length > 0 ? entries[entries.length - 1] : null;
|
|
};
|
|
|
|
const progressStages = [
|
|
{
|
|
id: 1,
|
|
name: 'Submitted',
|
|
status: 'completed',
|
|
description: 'Termination request initiated',
|
|
date: '',
|
|
actionType: '',
|
|
actionBy: '',
|
|
remarks: '',
|
|
feedback: ''
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'RBM Review',
|
|
status: request.currentStage === 'RBM Review' ? 'active' : ['ZBH Review', 'DD Lead Review', 'Legal Verification', 'NBH Evaluation', 'Show Cause Notice', 'Personal Hearing', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'Regional Business Manager review'
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'ZBH Review',
|
|
status: request.currentStage === 'ZBH Review' ? 'active' : ['DD Lead Review', 'Legal Verification', 'NBH Evaluation', 'Show Cause Notice', 'Personal Hearing', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'Zonal Business Head evaluation'
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'DD Lead Review',
|
|
status: request.currentStage === 'DD Lead Review' ? 'active' : ['Legal Verification', 'NBH Evaluation', 'Show Cause Notice', 'Personal Hearing', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'DD Lead validation'
|
|
},
|
|
{
|
|
id: 5,
|
|
name: 'Legal Verification',
|
|
status: request.currentStage === 'Legal Verification' ? 'active' : ['NBH Evaluation', 'Show Cause Notice', 'Personal Hearing', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'Legal team validates termination grounds'
|
|
},
|
|
{
|
|
id: 6,
|
|
name: 'NBH Evaluation',
|
|
status: request.currentStage === 'NBH Evaluation' ? 'active' : ['Show Cause Notice', 'Personal Hearing', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'National Business Head decision'
|
|
},
|
|
{
|
|
id: 7,
|
|
name: 'Show Cause Notice (SCN)',
|
|
status: request.currentStage === 'Show Cause Notice' ? 'active' : ['Personal Hearing', 'NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'SCN sent to dealer, awaiting response'
|
|
},
|
|
{
|
|
id: 8,
|
|
name: 'Personal Hearing',
|
|
status: request.currentStage === 'Personal Hearing' ? 'active' : ['NBH Final Approval', 'CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'Evaluation of SCN response & Hearing'
|
|
},
|
|
{
|
|
id: 9,
|
|
name: 'NBH Final Approval',
|
|
status: request.currentStage === 'NBH Final Approval' ? 'active' : ['CCO Approval', 'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'NBH final termination decision'
|
|
},
|
|
{
|
|
id: 10,
|
|
name: 'CCO Approval',
|
|
status: request.currentStage === 'CCO Approval' ? 'active' : ['CEO Final Approval', 'Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'Chief Commercial Officer approval'
|
|
},
|
|
{
|
|
id: 11,
|
|
name: 'CEO Final Approval',
|
|
status: request.currentStage === 'CEO Final Approval' ? 'active' : ['Legal - Termination Letter', 'Terminated'].includes(request.currentStage) ? 'completed' : 'pending',
|
|
description: 'CEO final authorization'
|
|
},
|
|
{
|
|
id: 12,
|
|
name: 'Legal - Termination Letter',
|
|
status: request.currentStage === 'Legal - Termination Letter' ? 'active' : request.currentStage === 'Terminated' ? 'completed' : 'pending',
|
|
description: 'Legal team issues final termination letter'
|
|
},
|
|
{
|
|
id: 13,
|
|
name: 'Dealer Terminated',
|
|
status: request.currentStage === 'Terminated' ? 'completed' : 'pending',
|
|
description: 'Dealership termination effective',
|
|
date: '',
|
|
actionType: '',
|
|
actionBy: '',
|
|
remarks: '',
|
|
feedback: ''
|
|
}
|
|
];
|
|
|
|
const handleViewStageDocuments = (stageName: string) => {
|
|
const documents = stageDocuments[stageName] || [];
|
|
setStageDocumentsDialog({ open: true, stageName, documents });
|
|
};
|
|
|
|
const handleAction = (type: 'approve' | 'withdrawal' | 'sendback' | 'assign' | 'pushfnf') => {
|
|
setActionDialog({ open: true, type });
|
|
};
|
|
|
|
const handleSubmitAction = async () => {
|
|
if (!remarks && actionDialog.type !== 'assign' && actionDialog.type !== 'pushfnf') {
|
|
toast.error('Please provide remarks');
|
|
return;
|
|
}
|
|
if (actionDialog.type === 'assign' && !assignToUser) {
|
|
toast.error('Please select a user');
|
|
return;
|
|
}
|
|
|
|
setIsProcessing(true);
|
|
try {
|
|
if (actionDialog.type === 'approve') {
|
|
await terminationService.updateTerminationStatus(terminationId, 'approve', remarks);
|
|
} else {
|
|
// Handle other actions
|
|
}
|
|
|
|
const actionMessages = {
|
|
approve: 'Request approved and forwarded',
|
|
withdrawal: 'Request withdrawn successfully',
|
|
sendback: 'Request sent back for clarification',
|
|
assign: `Request assigned to ${assignToUser}`,
|
|
pushfnf: 'Request pushed to F&F successfully'
|
|
};
|
|
|
|
toast.success(actionMessages[actionDialog.type!]);
|
|
setActionDialog({ open: false, type: null });
|
|
setRemarks('');
|
|
setAssignToUser('');
|
|
fetchTermination();
|
|
} catch (error) {
|
|
toast.error('Failed to perform action');
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const getSeverityColor = (severity: string) => {
|
|
switch (severity) {
|
|
case 'Critical':
|
|
return 'bg-red-100 text-red-700 border-red-300';
|
|
case 'High':
|
|
return 'bg-orange-100 text-orange-700 border-orange-300';
|
|
case 'Medium':
|
|
return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
|
default:
|
|
return 'bg-blue-100 text-blue-700 border-blue-300';
|
|
}
|
|
};
|
|
|
|
const workNotesCount = (request.worknotes || []).length;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Warning Alert */}
|
|
<Alert className="border-amber-200 bg-amber-50">
|
|
<AlertTriangle className="h-4 w-4 text-amber-600" />
|
|
<AlertTitle className="text-amber-900">Sensitive Information</AlertTitle>
|
|
<AlertDescription className="text-amber-700">
|
|
This is a termination case. All actions are logged and audited. Proceed with caution.
|
|
</AlertDescription>
|
|
</Alert>
|
|
|
|
{/* Header */}
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="outline" size="icon" onClick={onBack} className="hover:bg-slate-100 transition-colors">
|
|
<ArrowLeft className="w-4 h-4" />
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-2xl">{request.requestId || terminationId}</h1>
|
|
<p className="text-slate-600">{request.dealer?.businessName || request.dealer?.legalName || 'Termination'}</p>
|
|
</div>
|
|
<Badge className={getSeverityColor(request.severity)}>
|
|
{request.severity}
|
|
</Badge>
|
|
<Badge className={
|
|
request.status === 'Completed' || request.status === 'Terminated' || request.status === 'Settled'
|
|
? 'bg-green-100 text-green-700 border-green-300'
|
|
: request.status === 'Rejected' || request.status === 'Withdrawn'
|
|
? 'bg-red-100 text-red-700 border-red-300'
|
|
: 'bg-yellow-100 text-yellow-700 border-yellow-300'
|
|
}>
|
|
{request.status === 'Settled' ? 'Completed' : (request.status || 'Pending')}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Bar - Professional Layout */}
|
|
<Card className="border-amber-200 shadow-sm">
|
|
<CardContent className="pt-6">
|
|
<div className="flex flex-col gap-4">
|
|
{/* Primary Actions Row */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-slate-600 mr-2">Termination Actions:</span>
|
|
{currentUser?.role !== 'Dealer' && (
|
|
<>
|
|
{!permissions.canFinalize && (
|
|
<>
|
|
{permissions.canApprove && (
|
|
<Button
|
|
size="sm"
|
|
className="bg-green-600 hover:bg-green-700 transition-all hover:shadow-md"
|
|
onClick={() => handleAction('approve')}
|
|
>
|
|
<Check className="w-4 h-4 mr-2" />
|
|
Approve
|
|
</Button>
|
|
)}
|
|
{permissions.canIssueSCN && (
|
|
<Button
|
|
size="sm"
|
|
className="bg-purple-600 hover:bg-purple-700 transition-all shadow-sm"
|
|
onClick={() => setShowSCNDialog(true)}
|
|
>
|
|
<AlertTriangle className="w-4 h-4 mr-2" />
|
|
Issue SCN
|
|
</Button>
|
|
)}
|
|
{permissions.canUploadSCNResponse && (
|
|
<Button
|
|
size="sm"
|
|
className="bg-amber-600 hover:bg-amber-700 transition-all shadow-sm"
|
|
onClick={() => {
|
|
setScnFile(null);
|
|
setShowSCNDialog(true);
|
|
}}
|
|
>
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
Upload SCN Response
|
|
</Button>
|
|
)}
|
|
{permissions.canApprove && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="hover:bg-slate-50 transition-all"
|
|
onClick={() => handleAction('sendback')}
|
|
>
|
|
<RotateCcw className="w-4 h-4 mr-2" />
|
|
Send Back
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
{permissions.canFinalize && (
|
|
<Button
|
|
size="sm"
|
|
className="bg-indigo-600 hover:bg-indigo-700 transition-all shadow-sm"
|
|
onClick={() => setShowFinalizeDialog(true)}
|
|
>
|
|
<ShieldCheck className="w-4 h-4 mr-2" />
|
|
Final Authorization
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Secondary Actions */}
|
|
<div className="flex items-center gap-2">
|
|
{permissions.canPushToFnF && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="text-blue-600 border-blue-300 hover:bg-blue-50 transition-all"
|
|
onClick={() => handleAction('pushfnf')}
|
|
>
|
|
<Send className="w-4 h-4 mr-2" />
|
|
Push to F&F
|
|
</Button>
|
|
)}
|
|
{!permissions.isFinalState && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="hover:bg-slate-50 transition-all"
|
|
onClick={() => handleAction('assign')}
|
|
>
|
|
<UserPlus className="w-4 h-4 mr-2" />
|
|
Assign User
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Work Notes Button - Independent Section */}
|
|
<div className="flex items-center justify-between pt-4 border-t border-amber-200">
|
|
<div className="flex items-center gap-2">
|
|
<MessageSquare className="w-4 h-4 text-slate-500" />
|
|
<span className="text-sm text-slate-600">Communication & Notes</span>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="relative hover:bg-amber-50 hover:border-amber-300 hover:text-amber-700 transition-all shadow-sm"
|
|
onClick={() => navigate(`/worknotes/termination/${terminationId}`, {
|
|
state: {
|
|
applicationName: request?.dealer?.businessName || 'Termination',
|
|
registrationNumber: terminationId || '',
|
|
participants: request?.participants || []
|
|
}
|
|
})}
|
|
>
|
|
<MessageSquare className="w-4 h-4 mr-2" />
|
|
View Work Notes
|
|
{workNotesCount > 0 && (
|
|
<Badge className="ml-2 bg-amber-600 hover:bg-amber-700 text-white h-5 px-2">
|
|
{workNotesCount}
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Tabs */}
|
|
<Tabs defaultValue="details" className="w-full">
|
|
<TabsList className="bg-slate-100 p-1">
|
|
<TabsTrigger value="details" className="data-[state=active]:bg-white">Details</TabsTrigger>
|
|
<TabsTrigger value="progress" className="data-[state=active]:bg-white">Progress</TabsTrigger>
|
|
<TabsTrigger value="documents" className="data-[state=active]:bg-white">Documents</TabsTrigger>
|
|
<TabsTrigger value="audit" className="data-[state=active]:bg-white">Audit Trail</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Details Tab */}
|
|
<TabsContent value="details" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Dealer Information</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<Label className="text-slate-600">Dealer Code</Label>
|
|
<p>{request.dealer?.dealerCode?.dealerCode || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Dealer Name</Label>
|
|
<p>{request.dealer?.businessName || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">GST</Label>
|
|
<p>{request.dealer?.gstNumber || 'N/A'}</p>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<Label className="text-slate-600">Address</Label>
|
|
<p>{request.dealer?.registeredAddress || request.dealer?.application?.address || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">City</Label>
|
|
<p>{request.dealer?.application?.city || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">District</Label>
|
|
<p>{request.dealer?.application?.district?.name || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Dealership Name</Label>
|
|
<p>{request.dealer?.businessName || request.dealershipName}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Sales Code</Label>
|
|
<p>{request.dealer?.dealerCode?.salesCode || request.salesCode || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Service Code</Label>
|
|
<p>{request.dealer?.dealerCode?.serviceCode || request.serviceCode || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">GMA Code</Label>
|
|
<p>{request.dealer?.dealerCode?.gmaCode || request.accessoriesCode || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">GMA Code</Label>
|
|
<p>{request.dealer?.dealerCode?.gmaCode || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Gear Code</Label>
|
|
<p>{request.dealer?.dealerCode?.gearCode || 'N/A'}</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Operational Details</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<Label className="text-slate-600">Inauguration</Label>
|
|
<p>{request.dealer?.onboardedAt ? formatDateTime(request.dealer.onboardedAt, 'date') : (request.inauguration || 'N/A')}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">LOA Date</Label>
|
|
<p>{request.dealer?.loaDate ? formatDateTime(request.dealer.loaDate, 'date') : 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">LOI Date</Label>
|
|
<p>{request.dealer?.loiDate ? formatDateTime(request.dealer.loiDate, 'date') : 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Last 6 Months Sales</Label>
|
|
<p>{request.lastSixMonthsSales}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Number of Dealerships</Label>
|
|
<p>{request.numberOfDealerships}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Number of Studios</Label>
|
|
<p>{request.numberOfStudios}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Constitution</Label>
|
|
<p>{request.dealer?.constitutionType || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Dealership Type</Label>
|
|
<p>{request.dealer?.application?.businessType || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Type of Closure</Label>
|
|
<p>{request.typeOfClosure || 'Complete'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Format Category</Label>
|
|
<p>{request.formatCategory || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Dealer Score Card Band</Label>
|
|
<p>{request.dealerScoreCardBand || 'N/A'}</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-amber-200 bg-amber-50/30">
|
|
<CardHeader>
|
|
<CardTitle className="text-amber-900 flex items-center gap-2">
|
|
<AlertTriangle className="w-5 h-5" />
|
|
Termination Details
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label className="text-slate-600">Termination Category</Label>
|
|
<p className="text-amber-900">{request.category}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Sub Category</Label>
|
|
<p>{request.subCategory || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Description</Label>
|
|
<p>{request.reason}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<Label className="text-slate-600">Severity</Label>
|
|
<div className="mt-1">
|
|
<Badge className={getSeverityColor(request.severity)}>
|
|
{request.severity}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Submitted By</Label>
|
|
<p>{request.initiator?.fullName || 'System'}</p>
|
|
</div>
|
|
<div>
|
|
<Label className="text-slate-600">Submitted On</Label>
|
|
<p>{formatDateTime(request.createdAt)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Progress Tab */}
|
|
<TabsContent value="progress">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Termination Progress Timeline</CardTitle>
|
|
<CardDescription>Track the termination request approval process</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{progressStages.map((stage, index) => {
|
|
const documentCount = stageDocuments[stage.name]?.length || 0;
|
|
const timelineEntry = getLatestStageTimelineEntry(stage.name);
|
|
return (
|
|
<div key={stage.id} className="flex gap-4">
|
|
<div className="flex flex-col items-center">
|
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${stage.status === 'completed' ? 'bg-green-100 text-green-600' :
|
|
stage.status === 'active' ? 'bg-amber-100 text-amber-600' :
|
|
'bg-slate-100 text-slate-400'
|
|
}`}>
|
|
{stage.status === 'completed' ? (
|
|
<Check className="w-5 h-5" />
|
|
) : stage.status === 'active' ? (
|
|
<AlertTriangle className="w-5 h-5" />
|
|
) : (
|
|
<span>{stage.id}</span>
|
|
)}
|
|
</div>
|
|
{index < progressStages.length - 1 && (
|
|
<div className={`w-0.5 ${stage.remarks ? 'h-32' : 'h-16'
|
|
} ${stage.status === 'completed' ? 'bg-green-300' : 'bg-slate-200'
|
|
}`} />
|
|
)}
|
|
</div>
|
|
<div className="flex-1 pb-8">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className={
|
|
stage.status === 'completed' ? 'text-green-600' :
|
|
stage.status === 'active' ? 'text-amber-600' :
|
|
'text-slate-400'
|
|
}>{stage.name}</h3>
|
|
{documentCount > 0 && (
|
|
<button
|
|
onClick={() => handleViewStageDocuments(stage.name)}
|
|
className="flex items-center gap-1 px-2 py-1 rounded-full bg-amber-100 hover:bg-amber-200 text-amber-700 text-xs transition-colors cursor-pointer"
|
|
>
|
|
<FileText className="w-3 h-3" />
|
|
<span>{documentCount} {documentCount === 1 ? 'doc' : 'docs'}</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
{(timelineEntry?.timestamp || stage.date) && (
|
|
<div className="flex items-center gap-1 text-sm text-slate-600">
|
|
<Calendar className="w-4 h-4" />
|
|
<span>{formatDateTime(timelineEntry?.timestamp || stage.date)}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<p className="text-slate-600 text-sm">{stage.description}</p>
|
|
|
|
{timelineEntry && (
|
|
<div className="mt-3 space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
<Badge className="bg-blue-100 text-blue-700 border-blue-300">{timelineEntry.action || 'Updated'}</Badge>
|
|
<span className="text-xs text-slate-500">by {timelineEntry.user || 'System'}</span>
|
|
</div>
|
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3">
|
|
<div className="space-y-2">
|
|
<div>
|
|
<Label className="text-xs text-slate-600">Remarks:</Label>
|
|
<p className="text-sm text-slate-700 mt-1">{timelineEntry.remarks || 'No remarks provided.'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Documents Tab */}
|
|
<TabsContent value="documents">
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between">
|
|
<div>
|
|
<CardTitle>Documents</CardTitle>
|
|
<CardDescription>View and manage termination case documents</CardDescription>
|
|
</div>
|
|
<Button size="sm" onClick={() => setShowUploadDialog(true)} className="bg-amber-600 hover:bg-amber-700">
|
|
<Upload className="w-4 h-4 mr-2" />
|
|
Upload Document
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Document Name</TableHead>
|
|
<TableHead>Type</TableHead>
|
|
<TableHead>Upload Date</TableHead>
|
|
<TableHead>Uploader</TableHead>
|
|
<TableHead>Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{(() => {
|
|
const allDocs = [
|
|
...(request.documents || []),
|
|
...(request.uploadedDocuments || [])
|
|
];
|
|
|
|
if (allDocs.length === 0) return (
|
|
<TableRow>
|
|
<TableCell colSpan={5} className="text-center py-4 text-slate-500">
|
|
No documents found
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
|
|
return allDocs.map((doc: any, index: number) => (
|
|
<TableRow key={doc.id || index}>
|
|
<TableCell>
|
|
<div className="flex items-center gap-2">
|
|
<FileText className="w-4 h-4 text-slate-500" />
|
|
<span>{doc.name || doc.fileName}</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>{doc.documentType || doc.type || 'Document'}</TableCell>
|
|
<TableCell>{formatDateTime(doc.uploadDate || doc.createdAt)}</TableCell>
|
|
<TableCell>{doc.uploader?.fullName || doc.uploader || '-'}</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => {
|
|
const path = doc.filePath || doc.path || doc.url;
|
|
if (!path) return;
|
|
const fullPath = path.startsWith('/uploads/') && !path.startsWith('/uploads/documents/')
|
|
? path.replace('/uploads/', '/uploads/documents/')
|
|
: path;
|
|
setPreviewDocument({
|
|
fileName: doc.name || doc.fileName || 'Document',
|
|
filePath: fullPath,
|
|
documentType: doc.documentType || doc.type || 'Document'
|
|
});
|
|
}}
|
|
>
|
|
View
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
));
|
|
})()}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Audit Trail Tab */}
|
|
<TabsContent value="audit">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Audit Trail</CardTitle>
|
|
<CardDescription>Complete history of actions on this termination case</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{auditLogs.length > 0 ? (
|
|
auditLogs.map((log: any, index: number) => (
|
|
<div
|
|
key={index}
|
|
className="flex gap-3 pb-4 border-b border-slate-100 last:border-0"
|
|
>
|
|
<div className="w-2 h-2 rounded-full bg-slate-400 mt-2" />
|
|
<div className="flex-1">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<p className="font-semibold text-slate-900 flex items-center gap-2">
|
|
{log.description || log.action}
|
|
</p>
|
|
<span className="text-xs text-slate-500">
|
|
{formatDateTime(log.timestamp || log.createdAt)}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm text-slate-600 mb-2">
|
|
<Badge variant="outline" className="text-[10px] uppercase">{log.actor?.name || log.userName || 'System'}</Badge>
|
|
</div>
|
|
|
|
{(log.remarks || log.newData?.remarks || log.details?.remarks) && (
|
|
<div className="mt-2 p-3 bg-slate-50 border border-slate-200 rounded text-sm text-slate-700">
|
|
{log.remarks || log.newData?.remarks || log.details?.remarks}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="text-center py-8 text-slate-500">
|
|
<p>No activity logs found for this case.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
{/* Action Dialogs */}
|
|
<Dialog open={actionDialog.open} onOpenChange={(open) => setActionDialog({ open, type: null })}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{actionDialog.type === 'approve' && 'Approve Termination Request'}
|
|
{actionDialog.type === 'withdrawal' && 'Withdraw Termination Request'}
|
|
{actionDialog.type === 'sendback' && 'Send Back for Clarification'}
|
|
{actionDialog.type === 'assign' && 'Assign to User'}
|
|
{actionDialog.type === 'pushfnf' && 'Push to Full & Final Settlement'}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{actionDialog.type === 'assign'
|
|
? 'Select a user to assign this request to'
|
|
: actionDialog.type === 'pushfnf'
|
|
? 'This will move the termination case to F&F for dues clearance'
|
|
: 'Please provide remarks for this action'
|
|
}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
{actionDialog.type === 'assign' ? (
|
|
<div className="space-y-2">
|
|
<Label>Select User</Label>
|
|
<Select value={assignToUser} onValueChange={setAssignToUser}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Choose a user" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="rbm">RBM - Regional Business Manager</SelectItem>
|
|
<SelectItem value="zbh">ZBH - Zonal Business Head</SelectItem>
|
|
<SelectItem value="dd-lead">DD Lead</SelectItem>
|
|
<SelectItem value="legal">Legal Team</SelectItem>
|
|
<SelectItem value="nbh">NBH - National Business Head</SelectItem>
|
|
<SelectItem value="cco">CCO - Chief Commercial Officer</SelectItem>
|
|
<SelectItem value="ceo">CEO</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
) : actionDialog.type === 'pushfnf' ? (
|
|
<div className="space-y-2">
|
|
<Label>Remarks (Optional)</Label>
|
|
<Textarea
|
|
value={remarks}
|
|
onChange={(e) => setRemarks(e.target.value)}
|
|
placeholder="Add any additional notes..."
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
<Label>Remarks *</Label>
|
|
<Textarea
|
|
value={remarks}
|
|
onChange={(e) => setRemarks(e.target.value)}
|
|
placeholder="Enter your remarks here..."
|
|
rows={4}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setActionDialog({ open: false, type: null })}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleSubmitAction}
|
|
className={
|
|
actionDialog.type === 'approve' ? 'bg-green-600 hover:bg-green-700' :
|
|
actionDialog.type === 'withdrawal' ? 'bg-red-600 hover:bg-red-700' :
|
|
'bg-blue-600 hover:bg-blue-700'
|
|
}
|
|
>
|
|
Confirm
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Stage Documents Dialog */}
|
|
<Dialog open={stageDocumentsDialog.open} onOpenChange={(open) => setStageDocumentsDialog({ open, stageName: '', documents: [] })}>
|
|
<DialogContent className={WIDE_DIALOG_CLASS}>
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<FileText className="w-5 h-5 text-amber-600" />
|
|
Documents - {stageDocumentsDialog.stageName}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Documents uploaded for this stage ({stageDocumentsDialog.documents.length} {stageDocumentsDialog.documents.length === 1 ? 'document' : 'documents'})
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="max-h-96 overflow-y-auto">
|
|
{stageDocumentsDialog.documents.length > 0 ? (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Document Name</TableHead>
|
|
<TableHead>Type</TableHead>
|
|
<TableHead>Upload Date</TableHead>
|
|
<TableHead>Uploader</TableHead>
|
|
<TableHead>Action</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{stageDocumentsDialog.documents.map((doc) => (
|
|
<TableRow key={doc.id}>
|
|
<TableCell>{doc.name}</TableCell>
|
|
<TableCell>
|
|
<Badge variant="outline">{doc.type}</Badge>
|
|
</TableCell>
|
|
<TableCell>{doc.uploadDate}</TableCell>
|
|
<TableCell>{doc.uploader}</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="text-amber-600 hover:text-amber-700"
|
|
onClick={() => {
|
|
const path = doc.path;
|
|
if (!path) return;
|
|
const fullPath = path.startsWith('/uploads/') && !path.startsWith('/uploads/documents/')
|
|
? path.replace('/uploads/', '/uploads/documents/')
|
|
: path;
|
|
setPreviewDocument({
|
|
fileName: doc.name || 'Document',
|
|
filePath: fullPath,
|
|
documentType: doc.type || 'Document'
|
|
});
|
|
}}
|
|
>
|
|
<FileText className="w-4 h-4 mr-1" />
|
|
View
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
) : (
|
|
<div className="text-center py-8 text-slate-500">
|
|
No documents uploaded for this stage yet
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setStageDocumentsDialog({ open: false, stageName: '', documents: [] })}>
|
|
Close
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* SCN Dialog */}
|
|
<Dialog open={showSCNDialog} onOpenChange={setShowSCNDialog}>
|
|
<DialogContent className="bg-white">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{request.currentStage === 'SCN' ? 'Upload SCN Response' : 'Issue Show Cause Notice (SCN)'}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{request.currentStage === 'SCN'
|
|
? 'Upload the response received from the dealer regarding the SCN.'
|
|
: 'Confirm the issuance of a formal Show Cause Notice to the dealer.'}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4 pt-4">
|
|
{request.currentStage === 'SCN' && (
|
|
<div className="space-y-2">
|
|
<Label>SCN Response File</Label>
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<input
|
|
type="file"
|
|
className="hidden"
|
|
id="scn-file-upload"
|
|
onChange={(e) => setScnFile(e.target.files?.[0] || null)}
|
|
/>
|
|
<Button
|
|
variant="outline"
|
|
className="w-full border-dashed"
|
|
onClick={() => document.getElementById('scn-file-upload')?.click()}
|
|
>
|
|
{scnFile ? scnFile.name : 'Select PDF or Image'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="space-y-2">
|
|
<Label>Remarks/Details</Label>
|
|
<Textarea
|
|
placeholder="Add any internal remarks or justification..."
|
|
value={scnRemarks}
|
|
onChange={(e) => setScnRemarks(e.target.value)}
|
|
rows={4}
|
|
/>
|
|
</div>
|
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
<Button variant="outline" onClick={() => setShowSCNDialog(false)} disabled={isProcessing}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
className={request.currentStage === 'SCN' ? 'bg-amber-600 hover:bg-amber-700' : 'bg-purple-600 hover:bg-purple-700'}
|
|
onClick={request.currentStage === 'SCN' ? handleUploadSCNResponse : handleIssueSCN}
|
|
disabled={isProcessing || (request.currentStage === 'SCN' && !scnFile)}
|
|
>
|
|
{isProcessing ? 'Processing...' : request.currentStage === 'SCN' ? 'Upload Response' : 'Issue SCN'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Finalize Termination Dialog */}
|
|
<Dialog open={showFinalizeDialog} onOpenChange={setShowFinalizeDialog}>
|
|
<DialogContent className="bg-white">
|
|
<DialogHeader>
|
|
<DialogTitle>Final Termination Authorization</DialogTitle>
|
|
<DialogDescription>
|
|
Provide your final decision on this termination case.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4 pt-4">
|
|
<div className="space-y-2">
|
|
<Label>Final Decision</Label>
|
|
<Select value={finalDecision} onValueChange={(val: any) => setFinalDecision(val)}>
|
|
<SelectTrigger className="mt-2 text-slate-900 border-slate-300">
|
|
<SelectValue placeholder="Select decision" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-white border-slate-200 shadow-xl overflow-visible z-[9999]">
|
|
<SelectItem value="Approve" className="text-amber-700 focus:bg-amber-50">Confirm Termination</SelectItem>
|
|
<SelectItem value="Reject" className="text-slate-600 focus:bg-slate-50">Reject Termination</SelectItem>
|
|
<SelectItem value="Reconsider" className="text-amber-600 focus:bg-amber-50">Reconsider / Give More Time</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>Authorization Remarks</Label>
|
|
<Textarea
|
|
placeholder="Provide your rationale for this decision..."
|
|
value={finalRemarks}
|
|
onChange={(e) => setFinalRemarks(e.target.value)}
|
|
rows={4}
|
|
/>
|
|
</div>
|
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
<Button variant="outline" onClick={() => setShowFinalizeDialog(false)} disabled={isProcessing}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
className="bg-indigo-600 hover:bg-indigo-700"
|
|
onClick={handleFinalize}
|
|
disabled={isProcessing || !finalRemarks}
|
|
>
|
|
{isProcessing ? 'Authorizing...' : 'Submit Decision'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Dialog open={showUploadDialog} onOpenChange={setShowUploadDialog}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Upload Termination Document</DialogTitle>
|
|
<DialogDescription>Add a document and map it to a stage (optional).</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4 pt-2">
|
|
<div className="space-y-2">
|
|
<Label>Document Type</Label>
|
|
<Select value={uploadDocType} onValueChange={setUploadDocType}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select document type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{TERMINATION_DOCUMENT_TYPES.map((docType) => (
|
|
<SelectItem key={docType} value={docType}>
|
|
{docType}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>Stage (Optional)</Label>
|
|
<Select value={uploadStage || 'none'} onValueChange={(value) => setUploadStage(value === 'none' ? '' : value)}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select stage" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="none">No Stage Mapping</SelectItem>
|
|
{TERMINATION_STAGE_OPTIONS.map((stage) => (
|
|
<SelectItem key={stage} value={stage}>
|
|
{stage}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>File</Label>
|
|
<input type="file" onChange={(e) => setUploadFile(e.target.files?.[0] || null)} />
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setShowUploadDialog(false)} disabled={isProcessing}>Cancel</Button>
|
|
<Button onClick={handleUploadDocument} disabled={isProcessing}>
|
|
{isProcessing ? 'Uploading...' : 'Upload'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<DocumentPreviewModal
|
|
isOpen={!!previewDocument}
|
|
onClose={() => setPreviewDocument(null)}
|
|
document={previewDocument}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|