started removing document type dependecy and f&F screeen bug changes fixed
This commit is contained in:
parent
61deac775c
commit
faa29a7511
@ -151,6 +151,9 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
|||||||
const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false);
|
const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false);
|
||||||
const [selectedDocType, setSelectedDocType] = useState<number | null>(null);
|
const [selectedDocType, setSelectedDocType] = useState<number | null>(null);
|
||||||
const [uploadFile, setUploadFile] = useState<File | null>(null);
|
const [uploadFile, setUploadFile] = useState<File | null>(null);
|
||||||
|
// True when the dialog was opened from a checklist row -> doc type is implicit,
|
||||||
|
// so we hide the dropdown and show the doc name as a read-only badge instead.
|
||||||
|
const [docTypeLocked, setDocTypeLocked] = useState(false);
|
||||||
const [activeMainTab, setActiveMainTab] = useState('workflow');
|
const [activeMainTab, setActiveMainTab] = useState('workflow');
|
||||||
const [activeDocumentTab, setActiveDocumentTab] = useState('required');
|
const [activeDocumentTab, setActiveDocumentTab] = useState('required');
|
||||||
const [request, setRequest] = useState<any>(null);
|
const [request, setRequest] = useState<any>(null);
|
||||||
@ -890,9 +893,26 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<h4 className="text-slate-900">Document Checklist</h4>
|
<h4 className="text-slate-900">Document Checklist</h4>
|
||||||
<Dialog open={isUploadDialogOpen} onOpenChange={setIsUploadDialogOpen}>
|
<Dialog
|
||||||
|
open={isUploadDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsUploadDialogOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setDocTypeLocked(false);
|
||||||
|
setUploadFile(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size="sm" className="bg-amber-600 hover:bg-amber-700">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-amber-600 hover:bg-amber-700"
|
||||||
|
onClick={() => {
|
||||||
|
setDocTypeLocked(false);
|
||||||
|
setSelectedDocType(null);
|
||||||
|
setUploadFile(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Upload className="w-4 h-4 mr-2" />
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
Upload Document
|
Upload Document
|
||||||
</Button>
|
</Button>
|
||||||
@ -901,29 +921,42 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Upload Document</DialogTitle>
|
<DialogTitle>Upload Document</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Select the document type and upload the file
|
{docTypeLocked
|
||||||
|
? 'Pick a file for the selected document.'
|
||||||
|
: 'Select the document type and upload the file.'}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
{docTypeLocked && selectedDocType != null ? (
|
||||||
<Label>Document Type</Label>
|
<div>
|
||||||
<select
|
<Label>Document</Label>
|
||||||
className="mt-1 flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2"
|
<div className="mt-1 flex items-center gap-2 bg-amber-50 border border-amber-200 rounded-md px-3 h-10">
|
||||||
value={selectedDocType != null ? String(selectedDocType) : ''}
|
<Badge className="bg-amber-600 text-white border-transparent">
|
||||||
onChange={(e) => {
|
{documentNames[selectedDocType] || `Document ${selectedDocType}`}
|
||||||
const v = e.target.value;
|
</Badge>
|
||||||
setSelectedDocType(v ? Number(v) : null);
|
</div>
|
||||||
}}
|
</div>
|
||||||
>
|
) : (
|
||||||
<option value="">Select document type</option>
|
<div>
|
||||||
{uploadDocumentTypeOptions.map((docNum) => (
|
<Label>Document Type</Label>
|
||||||
<option key={docNum} value={String(docNum)}>
|
<select
|
||||||
{docNum !== OTHER_DOCUMENT_DOC_NUMBER && isDocTypeUploaded(docNum) ? '✓ ' : ''}
|
className="mt-1 flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2"
|
||||||
{documentNames[docNum] || `Document ${docNum}`}
|
value={selectedDocType != null ? String(selectedDocType) : ''}
|
||||||
</option>
|
onChange={(e) => {
|
||||||
))}
|
const v = e.target.value;
|
||||||
</select>
|
setSelectedDocType(v ? Number(v) : null);
|
||||||
</div>
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Select document type</option>
|
||||||
|
{uploadDocumentTypeOptions.map((docNum) => (
|
||||||
|
<option key={docNum} value={String(docNum)}>
|
||||||
|
{docNum !== OTHER_DOCUMENT_DOC_NUMBER && isDocTypeUploaded(docNum) ? '✓ ' : ''}
|
||||||
|
{documentNames[docNum] || `Document ${docNum}`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Label>Upload File</Label>
|
<Label>Upload File</Label>
|
||||||
<Input type="file" className="mt-1" onChange={(e) => setUploadFile(e.target.files?.[0] || null)} />
|
<Input type="file" className="mt-1" onChange={(e) => setUploadFile(e.target.files?.[0] || null)} />
|
||||||
@ -976,15 +1009,33 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{uploaded ? (
|
<div className="flex items-center gap-2">
|
||||||
<Badge className={getStatusColor(uploaded.status)}>
|
{uploaded ? (
|
||||||
{uploaded.status}
|
<Badge className={getStatusColor(uploaded.status)}>
|
||||||
</Badge>
|
{uploaded.status}
|
||||||
) : (
|
</Badge>
|
||||||
<Badge className="bg-slate-100 text-slate-600 border-slate-300">
|
) : (
|
||||||
Not Uploaded
|
<Badge className="bg-slate-100 text-slate-600 border-slate-300">
|
||||||
</Badge>
|
Not Uploaded
|
||||||
)}
|
</Badge>
|
||||||
|
)}
|
||||||
|
{!ok && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 px-2 text-amber-700 hover:bg-amber-50"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedDocType(docNum);
|
||||||
|
setUploadFile(null);
|
||||||
|
setDocTypeLocked(true);
|
||||||
|
setIsUploadDialogOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Upload className="w-3.5 h-3.5 mr-1" />
|
||||||
|
{isRejected ? 'Re-upload' : 'Upload'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -691,9 +691,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
|||||||
<TabsTrigger value="progress">Progress</TabsTrigger>
|
<TabsTrigger value="progress">Progress</TabsTrigger>
|
||||||
<TabsTrigger value="details">Case Details</TabsTrigger>
|
<TabsTrigger value="details">Case Details</TabsTrigger>
|
||||||
<TabsTrigger value="departments">Department Responses</TabsTrigger>
|
<TabsTrigger value="departments">Department Responses</TabsTrigger>
|
||||||
<TabsTrigger value="financial">Financial Summary</TabsTrigger>
|
|
||||||
<TabsTrigger value="documents">Documents</TabsTrigger>
|
<TabsTrigger value="documents">Documents</TabsTrigger>
|
||||||
<TabsTrigger value="bank">Bank Details</TabsTrigger>
|
{/* Bank Details tab hidden temporarily */}
|
||||||
|
{/* <TabsTrigger value="bank">Bank Details</TabsTrigger> */}
|
||||||
<TabsTrigger value="audit">Audit Trail</TabsTrigger>
|
<TabsTrigger value="audit">Audit Trail</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
@ -1451,145 +1451,143 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
|||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* Financial Summary Tab */}
|
{/* Department Claim vs Finance Validation */}
|
||||||
<TabsContent value="financial">
|
<Card className="border-blue-200 bg-blue-50 mt-6">
|
||||||
<div className="space-y-6">
|
<CardHeader>
|
||||||
<Card className="border-blue-200 bg-blue-50">
|
<CardTitle>Department Claim vs Finance Validation</CardTitle>
|
||||||
<CardHeader>
|
<CardDescription>
|
||||||
<CardTitle>Department Claim vs Finance Validation</CardTitle>
|
Final settlement totals are based on finance validated values.
|
||||||
<CardDescription>
|
</CardDescription>
|
||||||
Final settlement totals are based on finance validated values.
|
</CardHeader>
|
||||||
</CardDescription>
|
<CardContent>
|
||||||
</CardHeader>
|
<Table>
|
||||||
<CardContent>
|
<TableHeader>
|
||||||
<Table>
|
<TableRow>
|
||||||
<TableHeader>
|
<TableHead>Department</TableHead>
|
||||||
<TableRow>
|
<TableHead>Department Claim</TableHead>
|
||||||
<TableHead>Department</TableHead>
|
<TableHead>Finance Validated</TableHead>
|
||||||
<TableHead>Department Claim</TableHead>
|
<TableHead>Variance</TableHead>
|
||||||
<TableHead>Finance Validated</TableHead>
|
</TableRow>
|
||||||
<TableHead>Variance</TableHead>
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{departmentReconciliation.map((row) => (
|
||||||
|
<TableRow key={row.department}>
|
||||||
|
<TableCell>{row.department}</TableCell>
|
||||||
|
<TableCell>{row.claimAmount > 0 ? `${row.claimType} ₹${row.claimAmount.toLocaleString()}` : '-'}</TableCell>
|
||||||
|
<TableCell>{row.validatedAmount > 0 ? `${row.validatedType} ₹${row.validatedAmount.toLocaleString()}` : '-'}</TableCell>
|
||||||
|
<TableCell className={row.variance === 0 ? 'text-slate-600' : row.variance > 0 ? 'text-red-600' : 'text-green-600'}>
|
||||||
|
{row.claimAmount === 0 && row.validatedAmount === 0 ? '-' : `₹${row.variance.toLocaleString()}`}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
))}
|
||||||
<TableBody>
|
</TableBody>
|
||||||
{departmentReconciliation.map((row) => (
|
</Table>
|
||||||
<TableRow key={row.department}>
|
</CardContent>
|
||||||
<TableCell>{row.department}</TableCell>
|
</Card>
|
||||||
<TableCell>{row.claimAmount > 0 ? `${row.claimType} ₹${row.claimAmount.toLocaleString()}` : '-'}</TableCell>
|
|
||||||
<TableCell>{row.validatedAmount > 0 ? `${row.validatedType} ₹${row.validatedAmount.toLocaleString()}` : '-'}</TableCell>
|
|
||||||
<TableCell className={row.variance === 0 ? 'text-slate-600' : row.variance > 0 ? 'text-red-600' : 'text-green-600'}>
|
|
||||||
{row.claimAmount === 0 && row.validatedAmount === 0 ? '-' : `₹${row.variance.toLocaleString()}`}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
{/* Financial Summary */}
|
||||||
<CardHeader>
|
<Card className="mt-6">
|
||||||
<CardTitle>Financial Summary</CardTitle>
|
<CardHeader>
|
||||||
<CardDescription>
|
<CardTitle>Financial Summary</CardTitle>
|
||||||
Consolidated view of all payable and receivable amounts
|
<CardDescription>
|
||||||
</CardDescription>
|
Consolidated view of all payable and receivable amounts
|
||||||
</CardHeader>
|
</CardDescription>
|
||||||
<CardContent>
|
</CardHeader>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<CardContent>
|
||||||
<div className="p-6 bg-green-50 rounded-lg border border-green-200">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<p className="text-sm text-green-700 mb-2">
|
<div className="p-6 bg-green-50 rounded-lg border border-green-200">
|
||||||
Total Payable Amount
|
<p className="text-sm text-green-700 mb-2">
|
||||||
</p>
|
Total Payable Amount
|
||||||
<p className="text-3xl text-green-600">
|
</p>
|
||||||
₹{fnfCase.totalPayableAmount?.toLocaleString() || "0"}
|
<p className="text-3xl text-green-600">
|
||||||
</p>
|
₹{fnfCase.totalPayableAmount?.toLocaleString() || "0"}
|
||||||
<p className="text-xs text-green-600 mt-1">
|
</p>
|
||||||
Amount to be paid to dealer
|
<p className="text-xs text-green-600 mt-1">
|
||||||
</p>
|
Amount to be paid to dealer
|
||||||
</div>
|
</p>
|
||||||
<div className="p-6 bg-red-50 rounded-lg border border-red-200">
|
|
||||||
<p className="text-sm text-red-700 mb-2">
|
|
||||||
Total receivable amount
|
|
||||||
</p>
|
|
||||||
<p className="text-3xl text-red-600">
|
|
||||||
₹{fnfCase.totalRecoveryAmount?.toLocaleString() || "0"}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-red-600 mt-1">
|
|
||||||
Amount receivable from dealer
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="p-6 bg-amber-50 rounded-lg border border-amber-200">
|
|
||||||
<p className="text-sm text-amber-700 mb-2">
|
|
||||||
Total Deductions
|
|
||||||
</p>
|
|
||||||
<p className="text-3xl text-amber-600 font-bold">
|
|
||||||
₹{fnfCase.totalDeductions?.toLocaleString() || "0"}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-amber-600 mt-1">
|
|
||||||
Warranty holdbacks / Policy penalties
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="p-6 bg-blue-50 rounded-lg border border-blue-200">
|
|
||||||
<p className="text-sm text-blue-700 mb-2">Net Settlement Amount</p>
|
|
||||||
<p
|
|
||||||
className={`text-3xl font-extrabold ${(fnfCase.netAmount || 0) < 0
|
|
||||||
? "text-red-600"
|
|
||||||
: "text-green-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
₹{Math.abs(fnfCase.netAmount || 0).toLocaleString()}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-blue-600 mt-1">
|
|
||||||
{(fnfCase.netAmount || 0) < 0
|
|
||||||
? "Receivable from dealer"
|
|
||||||
: "Payment to dealer"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
<div className="p-6 bg-red-50 rounded-lg border border-red-200">
|
||||||
</Card>
|
<p className="text-sm text-red-700 mb-2">
|
||||||
|
Total receivable amount
|
||||||
<Card>
|
</p>
|
||||||
<CardHeader>
|
<p className="text-3xl text-red-600">
|
||||||
<CardTitle>Finance Report Status</CardTitle>
|
₹{fnfCase.totalRecoveryAmount?.toLocaleString() || "0"}
|
||||||
</CardHeader>
|
</p>
|
||||||
<CardContent>
|
<p className="text-xs text-red-600 mt-1">
|
||||||
<div className="flex items-center gap-4">
|
Amount receivable from dealer
|
||||||
<Badge
|
</p>
|
||||||
className={
|
</div>
|
||||||
fnfCase.financeReportStatus === "Completed"
|
<div className="p-6 bg-amber-50 rounded-lg border border-amber-200">
|
||||||
? "bg-green-100 text-green-700 border-green-300"
|
<p className="text-sm text-amber-700 mb-2">
|
||||||
: fnfCase.financeReportStatus === "In Progress"
|
Total Deductions
|
||||||
? "bg-yellow-100 text-yellow-700 border-yellow-300"
|
</p>
|
||||||
: "bg-slate-100 text-slate-700 border-slate-300"
|
<p className="text-3xl text-amber-600 font-bold">
|
||||||
}
|
₹{fnfCase.totalDeductions?.toLocaleString() || "0"}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-amber-600 mt-1">
|
||||||
|
Warranty holdbacks / Policy penalties
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 bg-blue-50 rounded-lg border border-blue-200">
|
||||||
|
<p className="text-sm text-blue-700 mb-2">Net Settlement Amount</p>
|
||||||
|
<p
|
||||||
|
className={`text-3xl font-extrabold ${(fnfCase.netAmount || 0) < 0
|
||||||
|
? "text-red-600"
|
||||||
|
: "text-green-600"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{fnfCase.financeReportStatus}
|
₹{Math.abs(fnfCase.netAmount || 0).toLocaleString()}
|
||||||
</Badge>
|
</p>
|
||||||
{fnfCase.financeReportStatus === "Pending" && (
|
<p className="text-xs text-blue-600 mt-1">
|
||||||
<p className="text-slate-600 text-sm">
|
{(fnfCase.netAmount || 0) < 0
|
||||||
Waiting for all department responses before finance can
|
? "Receivable from dealer"
|
||||||
prepare final report
|
: "Payment to dealer"}
|
||||||
</p>
|
</p>
|
||||||
)}
|
|
||||||
{fnfCase.financeReportStatus === "In Progress" && (
|
|
||||||
<p className="text-slate-600 text-sm">
|
|
||||||
Finance team is reviewing department responses and
|
|
||||||
preparing final settlement report
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{fnfCase.financeRemarks && (
|
</div>
|
||||||
<div className="mt-4 p-4 bg-slate-50 rounded-lg">
|
</CardContent>
|
||||||
<Label className="text-slate-600">Finance Remarks</Label>
|
</Card>
|
||||||
<p className="mt-1">{fnfCase.financeRemarks}</p>
|
|
||||||
</div>
|
{/* Finance Report Status */}
|
||||||
|
<Card className="mt-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Finance Report Status</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Badge
|
||||||
|
className={
|
||||||
|
fnfCase.financeReportStatus === "Completed"
|
||||||
|
? "bg-green-100 text-green-700 border-green-300"
|
||||||
|
: fnfCase.financeReportStatus === "In Progress"
|
||||||
|
? "bg-yellow-100 text-yellow-700 border-yellow-300"
|
||||||
|
: "bg-slate-100 text-slate-700 border-slate-300"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{fnfCase.financeReportStatus}
|
||||||
|
</Badge>
|
||||||
|
{fnfCase.financeReportStatus === "Pending" && (
|
||||||
|
<p className="text-slate-600 text-sm">
|
||||||
|
Waiting for all department responses before finance can
|
||||||
|
prepare final report
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
{fnfCase.financeReportStatus === "In Progress" && (
|
||||||
</Card>
|
<p className="text-slate-600 text-sm">
|
||||||
</div>
|
Finance team is reviewing department responses and
|
||||||
|
preparing final settlement report
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{fnfCase.financeRemarks && (
|
||||||
|
<div className="mt-4 p-4 bg-slate-50 rounded-lg">
|
||||||
|
<Label className="text-slate-600">Finance Remarks</Label>
|
||||||
|
<p className="mt-1">{fnfCase.financeRemarks}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Documents Tab */}
|
{/* Documents Tab */}
|
||||||
|
|||||||
@ -416,56 +416,36 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-6 py-4" data-testid="onboarding-documents-upload-form">
|
<div className="space-y-6 py-4" data-testid="onboarding-documents-upload-form">
|
||||||
<div className="grid gap-6 bg-slate-50/50 p-4 sm:p-6 rounded-2xl border border-slate-200">
|
<div className="bg-slate-50/50 p-4 sm:p-6 rounded-2xl border border-slate-200">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-slate-700 font-semibold px-1">Stage context <span className="text-red-500">*</span></Label>
|
<Label className="text-slate-700 font-semibold px-1">Document Name <span className="text-red-500">*</span></Label>
|
||||||
<Select value={selectedStage || 'null'} onValueChange={(val) => setSelectedStage(val === 'null' ? null : val)}>
|
<Input
|
||||||
<SelectTrigger className="bg-white border-slate-200 h-11 rounded-xl focus:ring-amber-500 shadow-sm" data-testid="onboarding-documents-stage-select"><SelectValue placeholder="Select stage" /></SelectTrigger>
|
type="text"
|
||||||
<SelectContent>
|
placeholder="Enter document name"
|
||||||
<SelectItem value="null">General / No Stage</SelectItem>
|
value={uploadDocType}
|
||||||
{flattenedStages.map((s: any, idx: number) => <SelectItem key={`${s.name}-${idx}`} value={s.name}>{s.parentBranch ? `${s.parentBranch}: ${s.name}` : s.name}</SelectItem>)}
|
onChange={(e) => setUploadDocType(e.target.value)}
|
||||||
</SelectContent>
|
className="bg-white border-slate-200 h-12 rounded-xl focus:ring-amber-500 shadow-sm"
|
||||||
</Select>
|
data-testid="onboarding-documents-name-input"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-slate-700 font-semibold px-1">Document Type <span className="text-red-500">*</span></Label>
|
<Label className="text-slate-700 font-semibold px-1">Select File <span className="text-red-500">*</span></Label>
|
||||||
<Select value={uploadDocType} onValueChange={setUploadDocType}>
|
<Input
|
||||||
<SelectTrigger className="bg-white border-slate-200 h-11 rounded-xl focus:ring-amber-500 shadow-sm" data-testid="onboarding-documents-type-select"><SelectValue placeholder="Select type" /></SelectTrigger>
|
type="file"
|
||||||
<SelectContent>
|
className="bg-white border-slate-200 h-12 rounded-xl focus:ring-amber-500 shadow-sm file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-amber-50 file:text-amber-700 hover:file:bg-amber-100 cursor-pointer"
|
||||||
{(() => {
|
onChange={(e) => {
|
||||||
const baseDocs = ['Other'];
|
const file = e.target.files ? e.target.files[0] : null;
|
||||||
const stageConfigs = documentConfigs.filter((c: any) => {
|
setUploadFile(file);
|
||||||
const cfgStage = c.stageCode?.trim();
|
if (file) {
|
||||||
const selStage = (selectedStage || 'General').trim();
|
const baseName = file.name.replace(/\.[^/.]+$/, '');
|
||||||
if (cfgStage === selStage) return true;
|
setUploadDocType(baseName);
|
||||||
if (selStage.startsWith('EOR:') && cfgStage === 'EOR') return true;
|
}
|
||||||
if (!selectedStage && cfgStage === 'General') return true;
|
}}
|
||||||
return false;
|
data-testid="onboarding-documents-file-input"
|
||||||
});
|
/>
|
||||||
let filteredDocs: string[] = [];
|
|
||||||
if (stageConfigs.length > 0) filteredDocs = stageConfigs.map((c: any) => c.documentType);
|
|
||||||
else if (!selectedStage || selectedStage === 'General') {
|
|
||||||
filteredDocs = ['PAN Card', 'GST Certificate', 'Aadhaar Card', 'Passport Size Photograph', 'Partnership Deed', 'LLP Agreement', 'Certificate of Incorporation', 'Board Resolution', 'Firm Registration Certificate', 'Cancelled Check', 'Bank Statement', 'Other'];
|
|
||||||
} else if (selectedStage?.toLowerCase().includes('architecture')) {
|
|
||||||
filteredDocs = ['Architecture Blueprint', 'Site Plan', 'Proposed Site City Map', 'Site Readiness Report', 'Architecture Completion Certificate', 'Other'];
|
|
||||||
} else if (selectedStage?.toLowerCase().includes('fdd')) {
|
|
||||||
filteredDocs = ['FDD Final Audit Report', 'Bank Statement', 'Income Tax Returns (ITR)', 'CIBIL Report', 'Other'];
|
|
||||||
} else filteredDocs = baseDocs;
|
|
||||||
if (selectedStage?.startsWith('EOR: ')) {
|
|
||||||
const eorItem = selectedStage.replace('EOR: ', '');
|
|
||||||
if (!filteredDocs.includes(eorItem)) filteredDocs = [eorItem, ...filteredDocs];
|
|
||||||
}
|
|
||||||
return Array.from(new Set(filteredDocs)).map((doc, idx) => <SelectItem key={`${doc}-${idx}`} value={doc} data-testid={`onboarding-documents-type-option-${idx}`}>{doc}</SelectItem>);
|
|
||||||
})()}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-slate-700 font-semibold px-1">Select File <span className="text-red-500">*</span></Label>
|
|
||||||
<Input type="file" className="bg-white border-slate-200 h-12 rounded-xl focus:ring-amber-500 shadow-sm file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-amber-50 file:text-amber-700 hover:file:bg-amber-100 cursor-pointer" onChange={(e) => setUploadFile(e.target.files ? e.target.files[0] : null)} data-testid="onboarding-documents-file-input" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row gap-3 pt-4">
|
<div className="flex flex-col sm:flex-row gap-3 pt-4">
|
||||||
<Button className="flex-1 order-2 sm:order-1 py-3 sm:py-5 rounded-xl border-slate-200 font-semibold text-slate-600 hover:bg-slate-50" variant="outline" onClick={() => setShowUploadForm(false)} disabled={isUploading} data-testid="onboarding-documents-upload-cancel">Cancel</Button>
|
<Button className="flex-1 order-2 sm:order-1 py-3 sm:py-5 rounded-xl border-slate-200 font-semibold text-slate-600 hover:bg-slate-50" variant="outline" onClick={() => setShowUploadForm(false)} disabled={isUploading} data-testid="onboarding-documents-upload-cancel">Cancel</Button>
|
||||||
|
|||||||
@ -368,13 +368,20 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
|||||||
(!doc.stage && doc.documentType?.toLowerCase().includes(stage.name.toLowerCase().split(' ')[0]))
|
(!doc.stage && doc.documentType?.toLowerCase().includes(stage.name.toLowerCase().split(' ')[0]))
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
|
// Upload is allowed only on the currently active (and unlocked) stage.
|
||||||
|
const canUploadHere = stage.status === 'active' && !stage.isLocked;
|
||||||
|
|
||||||
|
if (stageDocsCount === 0 && !canUploadHere) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<div className="flex items-center gap-2 mt-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedStage(stage.name);
|
setSelectedStage(stage.name);
|
||||||
setShowDocumentsModal(true);
|
setShowDocumentsModal(true);
|
||||||
if (stageDocsCount === 0) setShowUploadForm(true);
|
if (stageDocsCount === 0 && canUploadHere) setShowUploadForm(true);
|
||||||
}}
|
}}
|
||||||
className="text-xs font-semibold text-blue-600 hover:text-blue-800 flex items-center gap-1.5 px-3 py-1 rounded-full bg-blue-50 border border-blue-100 hover:bg-blue-100 transition-all shadow-sm"
|
className="text-xs font-semibold text-blue-600 hover:text-blue-800 flex items-center gap-1.5 px-3 py-1 rounded-full bg-blue-50 border border-blue-100 hover:bg-blue-100 transition-all shadow-sm"
|
||||||
data-testid={`onboarding-progress-stage-docs-${index}`}
|
data-testid={`onboarding-progress-stage-docs-${index}`}
|
||||||
@ -471,23 +478,32 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
|||||||
<p className="text-slate-500 text-xs mt-0.5">{branchStage.description}</p>
|
<p className="text-slate-500 text-xs mt-0.5">{branchStage.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2 mt-1">
|
{(() => {
|
||||||
<button
|
// Upload is allowed only on the currently active branch stage.
|
||||||
onClick={() => {
|
const canUploadHere = branchStage.status === 'active';
|
||||||
setSelectedStage(branchStage.name);
|
if (stageDocs.length === 0 && !canUploadHere) {
|
||||||
setShowDocumentsModal(true);
|
return null;
|
||||||
if (stageDocs.length === 0) setShowUploadForm(true);
|
}
|
||||||
}}
|
return (
|
||||||
className={cn(
|
<div className="flex items-center gap-2 mt-1">
|
||||||
"text-[10px] font-medium flex items-center gap-1 transition-colors",
|
<button
|
||||||
branchColor === 'blue' ? "text-blue-600 hover:text-blue-800" : "text-green-600 hover:text-green-800"
|
onClick={() => {
|
||||||
)}
|
setSelectedStage(branchStage.name);
|
||||||
data-testid={`onboarding-progress-branch-stage-docs-${branchKey}-${bsIdx}`}
|
setShowDocumentsModal(true);
|
||||||
>
|
if (stageDocs.length === 0 && canUploadHere) setShowUploadForm(true);
|
||||||
<FileText className="w-2.5 h-2.5" />
|
}}
|
||||||
{stageDocs.length > 0 ? `${stageDocs.length} Docs` : 'Upload'}
|
className={cn(
|
||||||
</button>
|
"text-[10px] font-medium flex items-center gap-1 transition-colors",
|
||||||
</div>
|
branchColor === 'blue' ? "text-blue-600 hover:text-blue-800" : "text-green-600 hover:text-green-800"
|
||||||
|
)}
|
||||||
|
data-testid={`onboarding-progress-branch-stage-docs-${branchKey}-${bsIdx}`}
|
||||||
|
>
|
||||||
|
<FileText className="w-2.5 h-2.5" />
|
||||||
|
{stageDocs.length > 0 ? `${stageDocs.length} Docs` : 'Upload'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
<p className="text-slate-400 text-[10px] mt-1" data-testid={`onboarding-progress-branch-stage-status-${branchKey}-${bsIdx}`}>
|
<p className="text-slate-400 text-[10px] mt-1" data-testid={`onboarding-progress-branch-stage-status-${branchKey}-${bsIdx}`}>
|
||||||
{isDone && branchStage.date ? `Done: ${formatDateTime(branchStage.date)}` : isDone && stageDocs.length > 0 ? `Uploaded: ${formatDateTime(stageDocs[0].updatedAt || stageDocs[0].createdAt)}` : 'Pending'}
|
{isDone && branchStage.date ? `Done: ${formatDateTime(branchStage.date)}` : isDone && stageDocs.length > 0 ? `Uploaded: ${formatDateTime(stageDocs[0].updatedAt || stageDocs[0].createdAt)}` : 'Pending'}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -328,14 +328,26 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
|
|||||||
|
|
||||||
const handleUpload = async () => {
|
const handleUpload = async () => {
|
||||||
if (!uploadFile || !uploadDocType) {
|
if (!uploadFile || !uploadDocType) {
|
||||||
toast.warning('Please select a file and document type');
|
toast.warning('Please enter a document name and select a file');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', uploadFile);
|
const originalExt = uploadFile.name.match(/\.[^/.]+$/)?.[0] || '';
|
||||||
formData.append('documentType', uploadDocType);
|
const typedName = uploadDocType.trim();
|
||||||
|
const customFileName = typedName.toLowerCase().endsWith(originalExt.toLowerCase())
|
||||||
|
? typedName
|
||||||
|
: `${typedName}${originalExt}`;
|
||||||
|
formData.append('file', uploadFile, customFileName);
|
||||||
|
// Document type is owned by the entry point, not a user-facing dropdown.
|
||||||
|
// For checklist-driven entry points (e.g. EOR items), use the checklist item's name
|
||||||
|
// so backend auto-linking (EOR compliance, architecture date, etc.) still works.
|
||||||
|
// Everything else uploads as a generic 'Other' document.
|
||||||
|
const checklistDocType = selectedStage?.startsWith('EOR: ')
|
||||||
|
? selectedStage.replace(/^EOR:\s*/, '')
|
||||||
|
: null;
|
||||||
|
formData.append('documentType', checklistDocType || 'Other');
|
||||||
if (selectedStage) formData.append('stage', selectedStage);
|
if (selectedStage) formData.append('stage', selectedStage);
|
||||||
await onboardingService.uploadDocument(applicationId, formData);
|
await onboardingService.uploadDocument(applicationId, formData);
|
||||||
toast.success('Document uploaded successfully');
|
toast.success('Document uploaded successfully');
|
||||||
|
|||||||
@ -217,6 +217,9 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
|||||||
const [isSubmittingEor, setIsSubmittingEor] = useState(false);
|
const [isSubmittingEor, setIsSubmittingEor] = useState(false);
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [selectedDocType, setSelectedDocType] = useState<string>(requiredDocuments[0]);
|
const [selectedDocType, setSelectedDocType] = useState<string>(requiredDocuments[0]);
|
||||||
|
// True when the dialog was opened from a checklist row -> doc type is implicit,
|
||||||
|
// so we hide the dropdown and show the doc name as a read-only badge instead.
|
||||||
|
const [docTypeLocked, setDocTypeLocked] = useState(false);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState('workflow');
|
const [activeTab, setActiveTab] = useState('workflow');
|
||||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||||
@ -877,9 +880,26 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
|||||||
{/* Upload Button */}
|
{/* Upload Button */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-slate-900">Required Documents</h4>
|
<h4 className="text-slate-900">Required Documents</h4>
|
||||||
<Dialog open={isUploadDialogOpen} onOpenChange={setIsUploadDialogOpen}>
|
<Dialog
|
||||||
|
open={isUploadDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsUploadDialogOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setDocTypeLocked(false);
|
||||||
|
setSelectedFile(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size="sm" className="bg-amber-600 hover:bg-amber-700">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-amber-600 hover:bg-amber-700"
|
||||||
|
onClick={() => {
|
||||||
|
setDocTypeLocked(false);
|
||||||
|
setSelectedDocType(requiredDocuments[0]);
|
||||||
|
setSelectedFile(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Upload className="w-4 h-4 mr-2" />
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
Upload Document
|
Upload Document
|
||||||
</Button>
|
</Button>
|
||||||
@ -888,29 +908,42 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Upload Document</DialogTitle>
|
<DialogTitle>Upload Document</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Select the document type and upload the file
|
{docTypeLocked
|
||||||
|
? 'Pick a file for the selected document.'
|
||||||
|
: 'Select the document type and upload the file.'}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
{docTypeLocked ? (
|
||||||
<Label>Document Type</Label>
|
<div>
|
||||||
<select
|
<Label>Document</Label>
|
||||||
className="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md"
|
<div className="mt-1 flex items-center gap-2 bg-amber-50 border border-amber-200 rounded-md px-3 h-10">
|
||||||
value={selectedDocType}
|
<Badge className="bg-amber-600 text-white border-transparent">
|
||||||
onChange={(e) => setSelectedDocType(e.target.value)}
|
{selectedDocType}
|
||||||
>
|
</Badge>
|
||||||
{requiredDocuments.map((doc, index) => {
|
</div>
|
||||||
const isAlreadyUploaded = request.documents?.some((d: any) =>
|
</div>
|
||||||
d.type === doc || d.name.toLowerCase().includes(doc.toLowerCase().split(' ')[0])
|
) : (
|
||||||
);
|
<div>
|
||||||
return (
|
<Label>Document Type</Label>
|
||||||
<option key={index} value={doc}>
|
<select
|
||||||
{isAlreadyUploaded ? `✅ ${doc}` : doc}
|
className="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md"
|
||||||
</option>
|
value={selectedDocType}
|
||||||
);
|
onChange={(e) => setSelectedDocType(e.target.value)}
|
||||||
})}
|
>
|
||||||
</select>
|
{requiredDocuments.map((doc, index) => {
|
||||||
</div>
|
const isAlreadyUploaded = request.documents?.some((d: any) =>
|
||||||
|
d.type === doc || d.name.toLowerCase().includes(doc.toLowerCase().split(' ')[0])
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<option key={index} value={doc}>
|
||||||
|
{isAlreadyUploaded ? `✅ ${doc}` : doc}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Label>Upload File</Label>
|
<Label>Upload File</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -952,17 +985,35 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex items-center gap-2 p-2 rounded border text-sm ${uploaded ? 'bg-green-50 border-green-200' : 'bg-slate-50 border-slate-200'
|
className={`flex items-center justify-between gap-2 p-2 rounded border text-sm ${uploaded ? 'bg-green-50 border-green-200' : 'bg-slate-50 border-slate-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{uploaded ? (
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<CheckCircle2 className="w-4 h-4 text-green-600 flex-shrink-0" />
|
{uploaded ? (
|
||||||
) : (
|
<CheckCircle2 className="w-4 h-4 text-green-600 flex-shrink-0" />
|
||||||
<AlertCircle className="w-4 h-4 text-slate-400 flex-shrink-0" />
|
) : (
|
||||||
|
<AlertCircle className="w-4 h-4 text-slate-400 flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span className={`truncate ${uploaded ? 'text-green-900' : 'text-slate-700'}`}>
|
||||||
|
{doc}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{!uploaded && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-7 px-2 text-amber-700 hover:bg-amber-50 flex-shrink-0"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedDocType(doc);
|
||||||
|
setSelectedFile(null);
|
||||||
|
setDocTypeLocked(true);
|
||||||
|
setIsUploadDialogOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Upload className="w-3.5 h-3.5 mr-1" />
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<span className={uploaded ? 'text-green-900' : 'text-slate-700'}>
|
|
||||||
{doc}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -239,6 +239,22 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
|||||||
|
|
||||||
const isAwaitingFnfGate = currentStage === 'Awaiting F&F';
|
const isAwaitingFnfGate = currentStage === 'Awaiting F&F';
|
||||||
|
|
||||||
|
const resolvedStageKey = (() => {
|
||||||
|
const normalized = String(currentStage || '').trim();
|
||||||
|
const matched = stagesOrdered.find(
|
||||||
|
(key) => key === normalized || (RESIGNATION_STAGE_ALIASES[key] || []).includes(normalized)
|
||||||
|
);
|
||||||
|
return matched || normalized;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const isNbHApprovalStep = resolvedStageKey === 'NBH';
|
||||||
|
const isAwaitingFnfStep = resolvedStageKey === 'Awaiting F&F';
|
||||||
|
/** Legacy rows only: acceptance letter at Legal before DD Admin completed Awaiting F&F gate */
|
||||||
|
const isLegalLegacyFnfStep = resolvedStageKey === 'Legal';
|
||||||
|
|
||||||
|
const fnfPushRoles = ['DD Lead', 'DD Head', 'DD Admin', 'Super Admin'];
|
||||||
|
const fnfPushLegacyRoles = ['DD Lead', 'DD Head', 'DD Admin', 'Super Admin'];
|
||||||
|
|
||||||
const canApprove = isCurrentlyAssigned &&
|
const canApprove = isCurrentlyAssigned &&
|
||||||
!isFinalState &&
|
!isFinalState &&
|
||||||
!isSettlementPhase &&
|
!isSettlementPhase &&
|
||||||
@ -250,14 +266,26 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
canApprove,
|
canApprove,
|
||||||
canSendBack: isCurrentlyAssigned && !isFinalState && !isSettlementPhase && stageIndex > 0,
|
// SRS §7.3.2: Send Back returns to DD Admin for correction. Legal Admin only drafts/uploads
|
||||||
|
// the Resignation Acceptance Letter and cannot send the case back to earlier reviewers.
|
||||||
|
canSendBack:
|
||||||
|
isCurrentlyAssigned &&
|
||||||
|
!isFinalState &&
|
||||||
|
!isSettlementPhase &&
|
||||||
|
stageIndex > 0 &&
|
||||||
|
userRole !== 'Legal Admin' &&
|
||||||
|
userRoleCode !== 'LEGAL_ADMIN',
|
||||||
canWithdraw: userRole === 'Dealer' && !isPastNBH && !isFinalState,
|
canWithdraw: userRole === 'Dealer' && !isPastNBH && !isFinalState,
|
||||||
canRevoke: (userRoleCode === 'SUPER_ADMIN' || userRole === 'DD Admin') && !isFinalState && !isSettlementPhase,
|
canRevoke: (userRoleCode === 'SUPER_ADMIN' || userRole === 'DD Admin') && !isFinalState && !isSettlementPhase,
|
||||||
canPushToFnF: ['DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Super Admin'].includes(userRole) &&
|
// Push to F&F: after DD Admin gate only — not during NBH (or any earlier) approval.
|
||||||
!isSettlementPhase &&
|
// Roles: DD Lead / DD Head / DD Admin / Super Admin (not NBH).
|
||||||
!isFinalState &&
|
canPushToFnF:
|
||||||
(currentStage === 'Awaiting F&F' || currentStage === 'Legal') &&
|
fnfPushRoles.includes(userRole) &&
|
||||||
isLwdReached,
|
!isSettlementPhase &&
|
||||||
|
!isFinalState &&
|
||||||
|
!isNbHApprovalStep &&
|
||||||
|
isLwdReached &&
|
||||||
|
(isAwaitingFnfStep || (isLegalLegacyFnfStep && fnfPushLegacyRoles.includes(userRole))),
|
||||||
canAssign: userRole !== 'Dealer' && !isFinalState
|
canAssign: userRole !== 'Dealer' && !isFinalState
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -595,13 +595,6 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Warning Alert */}
|
{/* 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 */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
|
|||||||
@ -291,13 +291,13 @@ export function TerminationPage({ currentUser, onViewDetails }: TerminationPageP
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Warning Alert */}
|
{/* Warning Alert */}
|
||||||
<Alert className="border-red-200 bg-red-50">
|
{/* <Alert className="border-red-200 bg-red-50">
|
||||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||||
<AlertTitle className="text-red-900">Restricted Access</AlertTitle>
|
<AlertTitle className="text-red-900">Restricted Access</AlertTitle>
|
||||||
<AlertDescription className="text-red-700">
|
<AlertDescription className="text-red-700">
|
||||||
This section contains sensitive information. All termination actions are logged and require proper authorization.
|
This section contains sensitive information. All termination actions are logged and require proper authorization.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert> */}
|
||||||
|
|
||||||
{/* Header Stats */}
|
{/* Header Stats */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user