theme have color enhanced and smtp setup done
This commit is contained in:
parent
faa29a7511
commit
06116af31a
@ -207,6 +207,10 @@ export const API = {
|
||||
updateConstitutionalChange: (id: string, action: ConstitutionalChangeAction, data?: { comments?: string; remarks?: string }) =>
|
||||
client.post(`/constitutional-change/${id}/action`, { action, ...data }),
|
||||
uploadConstitutionalDocuments: (id: string, documents: any[]) => client.post(`/constitutional-change/${id}/documents`, { documents }),
|
||||
verifyConstitutionalDocument: (id: string, documentId: string) =>
|
||||
client.post(`/constitutional-change/${id}/documents/${documentId}/verify`),
|
||||
rejectConstitutionalDocument: (id: string, documentId: string, data?: { remarks?: string }) =>
|
||||
client.post(`/constitutional-change/${id}/documents/${documentId}/reject`, data || {}),
|
||||
|
||||
// SLA
|
||||
getSlaConfigs: () => client.get('/master/sla-configs'),
|
||||
|
||||
@ -47,7 +47,6 @@ export function Sidebar({ onLogout }: SidebarProps) {
|
||||
const [flyout, setFlyout] = useState<FlyoutState | null>(null);
|
||||
const hoverTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const currentRole = currentUser?.role || currentUser?.roleCode || '';
|
||||
const hasRole = (roles: string[]) => {
|
||||
const normalizedTargetRoles = roles.map((r) => r.toLowerCase());
|
||||
const userRole = String(currentUser?.role || '').toLowerCase();
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { SlaBucket, SlaStatusSnapshot } from '@/services/sla.service';
|
||||
|
||||
const BUCKET_CLASS: Record<SlaBucket, string> = {
|
||||
healthy: 'bg-emerald-100 text-emerald-800 border-emerald-200',
|
||||
warning: 'bg-amber-100 text-amber-800 border-amber-200',
|
||||
warning: 'bg-red-50 text-red-800 border-red-200',
|
||||
critical: 'bg-orange-100 text-orange-800 border-orange-200',
|
||||
breached: 'bg-red-100 text-red-800 border-red-200'
|
||||
};
|
||||
|
||||
@ -38,8 +38,8 @@ export const DocumentPreviewModal: React.FC<DocumentPreviewModalProps> = ({
|
||||
<>
|
||||
<div className="flex items-center justify-between p-4 border-b bg-slate-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-amber-100 flex items-center justify-center border border-amber-200">
|
||||
<Eye className="w-5 h-5 text-amber-600" />
|
||||
<div className="w-10 h-10 rounded-lg bg-red-50 flex items-center justify-center border border-red-200">
|
||||
<Eye className="w-5 h-5 text-re-red" />
|
||||
</div>
|
||||
<div>
|
||||
<DialogTitle className="text-sm font-bold text-slate-900 leading-none mb-1">
|
||||
|
||||
@ -7,9 +7,12 @@ import { cn } from "./utils";
|
||||
|
||||
function Progress({
|
||||
className,
|
||||
indicatorClassName,
|
||||
value,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
||||
}: React.ComponentProps<typeof ProgressPrimitive.Root> & {
|
||||
indicatorClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<ProgressPrimitive.Root
|
||||
data-slot="progress"
|
||||
@ -21,7 +24,7 @@ function Progress({
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
data-slot="progress-indicator"
|
||||
className="bg-primary h-full w-full flex-1 transition-all"
|
||||
className={cn("bg-primary h-full w-full flex-1 transition-all", indicatorClassName)}
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
|
||||
@ -93,7 +93,7 @@ const ACTION_BADGE_CLASS: Record<string, string> = {
|
||||
DELETED: 'bg-rose-100 text-rose-700 border-rose-200',
|
||||
ACTIVATED: 'bg-emerald-100 text-emerald-700 border-emerald-200',
|
||||
DEACTIVATED: 'bg-slate-200 text-slate-700 border-slate-300',
|
||||
INITIALIZED: 'bg-amber-100 text-amber-700 border-amber-200',
|
||||
INITIALIZED: 'bg-red-50 text-re-red-hover border-red-200',
|
||||
SUBMITTED: 'bg-indigo-100 text-indigo-700 border-indigo-200',
|
||||
ASSIGNED: 'bg-violet-100 text-violet-700 border-violet-200',
|
||||
UNASSIGNED: 'bg-slate-200 text-slate-700 border-slate-300',
|
||||
|
||||
@ -94,13 +94,13 @@ export function ProspectiveLoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-4 overflow-y-auto">
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-40 -right-40 w-80 h-80 bg-amber-600/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-amber-600/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -top-40 -right-40 w-80 h-80 bg-re-red/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-re-red/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-amber-600 rounded-full mb-4">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-re-red rounded-full mb-4">
|
||||
<svg viewBox="0 0 24 24" className="w-12 h-12 text-white" fill="currentColor">
|
||||
<path d="M12 2L4 6v6c0 5.5 3.8 10.7 8 12 4.2-1.3 8-6.5 8-12V6l-8-4zm0 2.2l6 3v4.8c0 4.5-3.1 8.7-6 10-2.9-1.3-6-5.5-6-10V7.2l6-3z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
@ -122,8 +122,8 @@ export function ProspectiveLoginPage() {
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 bg-amber-100 rounded-lg">
|
||||
<Smartphone className="w-6 h-6 text-amber-600" />
|
||||
<div className="p-2 bg-red-50 rounded-lg">
|
||||
<Smartphone className="w-6 h-6 text-re-red" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-slate-900 text-lg font-semibold">Dealer Login</h2>
|
||||
@ -158,7 +158,7 @@ export function ProspectiveLoginPage() {
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full bg-amber-600 hover:bg-amber-700 h-9"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover h-9"
|
||||
disabled={isLoading || phone.length < 10}
|
||||
>
|
||||
{isLoading ? 'Sending...' : 'Send OTP'}
|
||||
@ -198,7 +198,7 @@ export function ProspectiveLoginPage() {
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full bg-amber-600 hover:bg-amber-700 h-9"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover h-9"
|
||||
disabled={isLoading || otp.length < 6}
|
||||
>
|
||||
{isLoading ? 'Verifying...' : 'Verify OTP'}
|
||||
@ -207,7 +207,7 @@ export function ProspectiveLoginPage() {
|
||||
<div className="text-center text-sm">
|
||||
<button
|
||||
type="button"
|
||||
className="text-amber-600 hover:text-amber-700 font-medium"
|
||||
className="text-re-red hover:text-re-red-hover font-medium"
|
||||
onClick={() => setStep('PHONE')}
|
||||
>
|
||||
Change Phone Number
|
||||
@ -215,7 +215,7 @@ export function ProspectiveLoginPage() {
|
||||
<span className="mx-2 text-slate-400">|</span>
|
||||
<button
|
||||
type="button"
|
||||
className="text-amber-600 hover:text-amber-700 font-medium"
|
||||
className="text-re-red hover:text-re-red-hover font-medium"
|
||||
onClick={handleSendOtp}
|
||||
disabled={isLoading}
|
||||
>
|
||||
|
||||
@ -17,6 +17,14 @@ import { useSlaBatchStatus } from '@/hooks/useSlaBatchStatus';
|
||||
import { OFFBOARDING_ACTIONS } from '@/lib/offboarding-actions';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { formatDateTime } from '@/components/ui/utils';
|
||||
import {
|
||||
getCurrentStageBadgeClass,
|
||||
getOffboardingRequestStatusBadgeClass,
|
||||
getStatusLabelBadgeClass,
|
||||
getStatusProgressBarClass,
|
||||
isOffboardingTerminalNegative,
|
||||
WORKFLOW_IN_PROGRESS_ACCENT,
|
||||
} from '@/lib/offboardingDisplay';
|
||||
|
||||
interface ConstitutionalChangeDetailsProps {
|
||||
requestId: string;
|
||||
@ -88,19 +96,12 @@ const getTypeColor = (type: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const s = String(status || '');
|
||||
if (s === 'Completed' || s === 'Verified' || s === 'APPROVED' || s === 'COMPLETED' || s === 'CREATED' || /^DOCUMENT/i.test(s)) {
|
||||
return 'bg-green-100 text-green-700 border-green-300';
|
||||
}
|
||||
if (s.includes('Revoked') || s === 'REVOKED') return 'bg-orange-100 text-orange-800 border-orange-300';
|
||||
if (s.includes('Rejected') || s === 'REJECTED') return 'bg-red-100 text-red-700 border-red-300';
|
||||
if (s === 'SENT BACK' || s.includes('Review') || s.includes('Pending') || s === 'In Progress' || s === 'Submitted') {
|
||||
return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||
}
|
||||
if (s === 'UPDATED') return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
};
|
||||
const getStatusColor = (status: string) => getStatusLabelBadgeClass(status);
|
||||
|
||||
const getDocChecklistUploadButtonClass = (isRejected: boolean) =>
|
||||
isRejected
|
||||
? 'h-8 px-2 text-red-700 hover:bg-red-50 hover:text-red-800'
|
||||
: 'h-8 px-2 text-slate-700 hover:bg-slate-50';
|
||||
|
||||
/** Audit rows were stored as UPDATED for approvals; avoid treating "UPDATED" as pending via substring "update". */
|
||||
const getConstitutionalHistoryPresentation = (entry: any) => {
|
||||
@ -233,7 +234,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
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" />
|
||||
<Loader2 className="w-8 h-8 text-re-red animate-spin" />
|
||||
<p className="text-slate-600">Loading request details...</p>
|
||||
</div>
|
||||
);
|
||||
@ -312,9 +313,9 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
(String(request.status || '') === 'Completed' && !['Rejected', 'Revoked'].includes(String(request.currentStage || '')));
|
||||
|
||||
/** SRS §12.2 — closed failure states: do not show misleading step progress. */
|
||||
const workflowTerminalNegative =
|
||||
['Rejected', 'Revoked'].includes(String(request.status || '')) ||
|
||||
['Rejected', 'Revoked'].includes(String(request.currentStage || ''));
|
||||
const workflowTerminalNegative = isOffboardingTerminalNegative(request.status, request.currentStage);
|
||||
const statusProgressBarClass = getStatusProgressBarClass(request.status, request.currentStage);
|
||||
const requestStatusBadgeClass = getOffboardingRequestStatusBadgeClass(request.status, request.currentStage);
|
||||
|
||||
const getLatestStageTimelineEntry = (stageName: string) => {
|
||||
const aliases: Record<string, string[]> = {
|
||||
@ -512,6 +513,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
? existingDocs.findIndex((d: any) => Number(d?.docNumber) === selectedDocType)
|
||||
: -1;
|
||||
const payloadDoc = {
|
||||
id: globalThis.crypto?.randomUUID?.() ?? `doc-${Date.now()}-${selectedDocType}`,
|
||||
docNumber: selectedDocType,
|
||||
name: documentNames[selectedDocType] || 'Other',
|
||||
fileName: uploadFile.name,
|
||||
@ -541,22 +543,19 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
}
|
||||
};
|
||||
|
||||
const resolveDocumentId = (doc: any, index: number) =>
|
||||
doc?.id != null ? String(doc.id) : String(index);
|
||||
|
||||
const handleVerifyDocument = async (targetDoc: any, targetIndex: number) => {
|
||||
try {
|
||||
const existingDocs = Array.isArray(request.documents) ? [...request.documents] : [];
|
||||
const updatedDocs = existingDocs.map((doc: any, index: number) => {
|
||||
const isTargetByIndex = index === targetIndex;
|
||||
const isTargetByDocNumber = targetDoc.docNumber && doc.docNumber === targetDoc.docNumber;
|
||||
if (!(isTargetByIndex || isTargetByDocNumber)) return doc;
|
||||
return { ...doc, status: 'Verified', verifiedOn: new Date().toISOString(), verifiedBy: currentUser?.name || 'System' };
|
||||
});
|
||||
|
||||
const response = await API.uploadConstitutionalDocuments(requestId, updatedDocs) as any;
|
||||
const documentId = resolveDocumentId(targetDoc, targetIndex);
|
||||
const response = await API.verifyConstitutionalDocument(requestId, documentId) as any;
|
||||
if (response.data?.success) {
|
||||
toast.success('Document verified successfully');
|
||||
fetchRequestDetails();
|
||||
await fetchRequestDetails({ silent: true });
|
||||
if (request?.id) await fetchAuditLogs(request.id);
|
||||
} else {
|
||||
toast.error('Failed to verify document');
|
||||
toast.error(response.data?.message || 'Failed to verify document');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Verify document error:', error);
|
||||
@ -566,31 +565,29 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
|
||||
const submitRejectDocument = async () => {
|
||||
if (rejectDocIndex == null || !String(rejectDocReason).trim()) {
|
||||
toast.error('Please enter a rejection reason (SRS document verification).');
|
||||
toast.error('Please enter a rejection reason.');
|
||||
return;
|
||||
}
|
||||
const targetDoc = (request?.documents || [])[rejectDocIndex];
|
||||
if (!targetDoc) {
|
||||
toast.error('Document not found');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsRejectingDoc(true);
|
||||
const existingDocs = Array.isArray(request.documents) ? [...request.documents] : [];
|
||||
const updatedDocs = existingDocs.map((doc: any, index: number) => {
|
||||
if (index !== rejectDocIndex) return doc;
|
||||
return {
|
||||
...doc,
|
||||
status: 'Rejected',
|
||||
rejectedOn: new Date().toISOString(),
|
||||
rejectedBy: (currentUser as any)?.fullName || 'System',
|
||||
rejectionReason: rejectDocReason.trim()
|
||||
};
|
||||
});
|
||||
const response = await API.uploadConstitutionalDocuments(requestId, updatedDocs) as any;
|
||||
const documentId = resolveDocumentId(targetDoc, rejectDocIndex);
|
||||
const response = await API.rejectConstitutionalDocument(requestId, documentId, {
|
||||
remarks: rejectDocReason.trim()
|
||||
}) as any;
|
||||
if (response.data?.success) {
|
||||
toast.success('Document marked as rejected');
|
||||
setRejectDocDialogOpen(false);
|
||||
setRejectDocIndex(null);
|
||||
setRejectDocReason('');
|
||||
await fetchRequestDetails({ silent: true });
|
||||
if (request?.id) await fetchAuditLogs(request.id);
|
||||
} else {
|
||||
toast.error('Failed to reject document');
|
||||
toast.error(response.data?.message || 'Failed to reject document');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Reject document error:', error);
|
||||
@ -620,7 +617,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={getStatusColor(request.status)}>
|
||||
<Badge className={requestStatusBadgeClass}>
|
||||
{request.status}
|
||||
</Badge>
|
||||
<SlaBadge status={getSla('constitutional', requestId)} />
|
||||
@ -655,7 +652,12 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<p className="text-slate-600 text-sm mb-1">Request Information</p>
|
||||
<p className="text-slate-900 text-sm">Submitted: {formatDateTime(request.createdAt)}</p>
|
||||
<p className="text-slate-600 text-sm">By: {request.dealer?.fullName || 'Dealer'}</p>
|
||||
<p className="text-slate-900 text-sm mt-2">Current Stage: {request.currentStage}</p>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2">
|
||||
<span className="text-slate-600 text-sm">Current Stage:</span>
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -722,16 +724,18 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
|
||||
<CardContent>
|
||||
{/* Workflow Progress Tab */}
|
||||
<TabsContent value="workflow" className="mt-0">
|
||||
<TabsContent value="workflow" className="mt-0 status-progress-ui">
|
||||
{/* Progress Bar */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-slate-900">Overall Progress</span>
|
||||
<span className="text-slate-600">{request.progressPercentage}%</span>
|
||||
<Badge className={`${statusProgressBarClass} text-white border-transparent hover:opacity-90`}>
|
||||
{request.progressPercentage}% Complete
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="h-3 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-600 transition-all duration-500"
|
||||
className={`h-full transition-all duration-500 ${statusProgressBarClass}`}
|
||||
style={{ width: `${request.progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
@ -790,13 +794,13 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
{/* Status Icon */}
|
||||
<div className="flex flex-col items-center">
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${isCompleted ? 'bg-green-100' :
|
||||
isCurrent ? 'bg-amber-100' :
|
||||
isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.icon :
|
||||
'bg-slate-100'
|
||||
}`}>
|
||||
{isCompleted ? (
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
) : isCurrent ? (
|
||||
<Clock className="w-5 h-5 text-amber-600" />
|
||||
<Clock className="w-5 h-5 text-re-red" />
|
||||
) : (
|
||||
<AlertCircle className="w-5 h-5 text-slate-400" />
|
||||
)}
|
||||
@ -808,20 +812,19 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
</div>
|
||||
|
||||
{/* Stage Info */}
|
||||
<div className={`flex-1 pb-8 ${isCurrent ? 'bg-amber-50 -ml-4 pl-4 pr-4 py-3 rounded-lg border border-amber-200' : ''}`}>
|
||||
<div className={`flex-1 pb-8 ${isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.panel : ''}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className={`${isCurrent ? 'text-amber-900' : 'text-slate-900'}`}>
|
||||
<h4 className={`${isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.title : 'text-slate-900'}`}>
|
||||
{formatStageLabel(stage.name)}
|
||||
</h4>
|
||||
<p className={`text-sm ${isCurrent ? 'text-amber-700' : 'text-slate-600'}`}>
|
||||
<p className={`text-sm ${isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.subtitle : 'text-slate-600'}`}>
|
||||
{`Responsible: ${formatStageRole(stage.role)}`}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<Badge className={
|
||||
isCompleted ? 'bg-green-100 text-green-700 border-green-300' :
|
||||
isCurrent ? 'bg-amber-100 text-amber-700 border-amber-300' :
|
||||
isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.stageBadge :
|
||||
'bg-slate-100 text-slate-500 border-slate-300'
|
||||
}>
|
||||
{isCompleted ? 'Completed' : isCurrent ? 'In Progress' : 'Pending'}
|
||||
@ -906,7 +909,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
onClick={() => {
|
||||
setDocTypeLocked(false);
|
||||
setSelectedDocType(null);
|
||||
@ -930,8 +933,8 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
{docTypeLocked && selectedDocType != null ? (
|
||||
<div>
|
||||
<Label>Document</Label>
|
||||
<div className="mt-1 flex items-center gap-2 bg-amber-50 border border-amber-200 rounded-md px-3 h-10">
|
||||
<Badge className="bg-amber-600 text-white border-transparent">
|
||||
<div className="mt-1 flex items-center gap-2 bg-red-50 border border-red-200 rounded-md px-3 h-10">
|
||||
<Badge className="bg-re-red text-white border-transparent">
|
||||
{documentNames[selectedDocType] || `Document ${selectedDocType}`}
|
||||
</Badge>
|
||||
</div>
|
||||
@ -940,7 +943,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<div>
|
||||
<Label>Document Type</Label>
|
||||
<select
|
||||
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"
|
||||
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-re-red focus-visible:ring-offset-2"
|
||||
value={selectedDocType != null ? String(selectedDocType) : ''}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
@ -967,7 +970,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
onClick={handleUploadDocument}
|
||||
disabled={isUploadingDoc}
|
||||
>
|
||||
@ -1004,7 +1007,9 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
{uploaded && (
|
||||
<p className={isRejected ? 'text-red-700 text-sm' : ok ? 'text-green-700 text-sm' : 'text-slate-600 text-sm'}>
|
||||
{uploaded.fileName || uploaded.name}
|
||||
{isRejected && uploaded.rejectionReason ? ` — ${uploaded.rejectionReason}` : ''}
|
||||
{isRejected && (uploaded.rejectionReason || uploaded.rejectionRemarks)
|
||||
? ` — ${uploaded.rejectionReason || uploaded.rejectionRemarks}`
|
||||
: ''}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@ -1023,7 +1028,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 px-2 text-amber-700 hover:bg-amber-50"
|
||||
className={getDocChecklistUploadButtonClass(!!isRejected)}
|
||||
onClick={() => {
|
||||
setSelectedDocType(docNum);
|
||||
setUploadFile(null);
|
||||
@ -1062,7 +1067,10 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{(request.documents || []).map((doc: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableRow
|
||||
key={index}
|
||||
className={String(doc.status) === 'Rejected' ? 'bg-red-50/80' : undefined}
|
||||
>
|
||||
<TableCell className="text-slate-900">
|
||||
{doc.docNumber ? documentNames[doc.docNumber] : doc.name}
|
||||
</TableCell>
|
||||
@ -1166,7 +1174,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<div key={entry.id || index} className="flex items-start gap-4 pb-4 border-b border-slate-200 last:border-0">
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${pres.variant === 'success' ? 'bg-green-100' :
|
||||
pres.variant === 'danger' ? 'bg-red-100' :
|
||||
pres.variant === 'pending' ? 'bg-amber-100' :
|
||||
pres.variant === 'pending' ? 'bg-red-50' :
|
||||
'bg-slate-100'
|
||||
}`}>
|
||||
{pres.variant === 'success' ? (
|
||||
@ -1174,7 +1182,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
) : pres.variant === 'danger' ? (
|
||||
<AlertCircle className="w-5 h-5 text-red-600" />
|
||||
) : pres.variant === 'pending' ? (
|
||||
<Clock className="w-5 h-5 text-amber-600" />
|
||||
<Clock className="w-5 h-5 text-re-red" />
|
||||
) : (
|
||||
<Clock className="w-5 h-5 text-slate-500" />
|
||||
)}
|
||||
@ -1223,7 +1231,21 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<p className="text-slate-600 text-sm">Current Stage</p>
|
||||
<p className="text-slate-900">{request.currentStage}</p>
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-slate-600 text-sm">Progress</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full transition-all duration-300 ${statusProgressBarClass}`}
|
||||
style={{ width: `${request.progressPercentage ?? 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-slate-900">{request.progressPercentage ?? 0}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -1279,7 +1301,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
{permissions.canSendBack && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full border-amber-300 text-amber-900 hover:bg-amber-50"
|
||||
className="w-full border-red-300 text-red-900 hover:bg-red-50"
|
||||
onClick={() => handleAction('sendBack')}
|
||||
disabled={isActionLoading}
|
||||
>
|
||||
@ -1295,7 +1317,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
{permissions.canRevoke && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full border-orange-300 text-orange-900 hover:bg-orange-50"
|
||||
className="w-full border-red-300 text-re-red-hover hover:bg-red-50"
|
||||
onClick={() => handleAction('revoke')}
|
||||
disabled={isActionLoading}
|
||||
>
|
||||
@ -1406,9 +1428,9 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
type="submit"
|
||||
className={
|
||||
actionType === 'approve' ? 'bg-green-600 hover:bg-green-700' :
|
||||
actionType === 'reject' ? 'bg-red-600 hover:bg-red-700' :
|
||||
actionType === 'sendBack' ? 'bg-amber-600 hover:bg-amber-700' :
|
||||
'bg-orange-600 hover:bg-orange-700'
|
||||
actionType === 'reject' ? 'bg-re-red hover:bg-re-red-hover' :
|
||||
actionType === 'sendBack' ? 'bg-re-red hover:bg-re-red-hover' :
|
||||
'bg-re-red hover:bg-re-red-hover'
|
||||
}
|
||||
disabled={isActionLoading}
|
||||
>
|
||||
@ -1434,7 +1456,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
<DialogHeader>
|
||||
<DialogTitle>Reject document</DialogTitle>
|
||||
<DialogDescription>
|
||||
Per SRS relocation-style verification states, mark this upload as Rejected and provide a reason for the dealer.
|
||||
Mark this upload as rejected and provide a reason. The action is recorded in the audit trail.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
|
||||
@ -15,6 +15,11 @@ import { API } from '@/api/API';
|
||||
import { SlaBadge } from '@/components/sla/SlaBadge';
|
||||
import { useSlaBatchStatus } from '@/hooks/useSlaBatchStatus';
|
||||
import { formatDateTime } from '@/components/ui/utils';
|
||||
import {
|
||||
getCurrentStageBadgeClass,
|
||||
getRequestStatusBadgeClass,
|
||||
getStatusProgressBarClass,
|
||||
} from '@/lib/statusProgressTheme';
|
||||
import { normalizeDealerProfileConstitution } from '@/lib/constitutional-change';
|
||||
import {
|
||||
Pagination,
|
||||
@ -62,13 +67,8 @@ const documentNames: Record<number, string> = {
|
||||
[OTHER_DOCUMENT_DOC_NUMBER]: 'Other'
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
|
||||
if (status.includes('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||
if (status.includes('Rejected')) return 'bg-red-100 text-red-700 border-red-300';
|
||||
if (status.includes('Collection')) return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
};
|
||||
const getStatusColor = (status: string, currentStage?: string) =>
|
||||
getRequestStatusBadgeClass(status, currentStage);
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
switch(type) {
|
||||
@ -295,7 +295,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
title: 'Submitted / Review',
|
||||
value: paginationMeta?.stats?.pending || 0,
|
||||
icon: Calendar,
|
||||
color: 'bg-yellow-500',
|
||||
color: 'bg-re-red',
|
||||
},
|
||||
{
|
||||
title: 'Completed',
|
||||
@ -316,7 +316,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
{/* Loading Overlay */}
|
||||
{isLoading && (
|
||||
<div className="fixed inset-0 bg-slate-900/20 backdrop-blur-sm z-50 flex items-center justify-center">
|
||||
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
|
||||
<Loader2 className="w-8 h-8 text-re-red animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -331,7 +331,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-amber-600 hover:bg-amber-700">
|
||||
<Button className="bg-re-red hover:bg-re-red-hover">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Request
|
||||
</Button>
|
||||
@ -492,7 +492,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
disabled={
|
||||
!dealerData ||
|
||||
!targetType ||
|
||||
@ -605,7 +605,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
<Badge variant="outline" className="border-slate-300 text-slate-700">
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
<SlaBadge status={getSla('constitutional', request.id || request.requestId)} compact />
|
||||
@ -615,7 +615,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-600 transition-all duration-300"
|
||||
className={`h-full transition-all duration-300 ${getStatusProgressBarClass(request.status, request.currentStage)}`}
|
||||
style={{ width: `${request.progressPercentage || 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
@ -686,14 +686,14 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
<Badge variant="outline" className="border-slate-300 text-slate-700">
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
<SlaBadge status={getSla('constitutional', request.id || request.requestId)} compact />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge className={getStatusColor(request.status)}>
|
||||
<Badge className={getStatusColor(request.status, request.currentStage)}>
|
||||
{request.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
@ -766,7 +766,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-600 transition-all duration-300"
|
||||
className={`h-full transition-all duration-300 ${getStatusProgressBarClass(request.status, request.currentStage)}`}
|
||||
style={{ width: `${request.progressPercentage || 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
@ -775,7 +775,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
<Badge variant="outline" className="border-slate-300 text-slate-700">
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
<SlaBadge status={getSla('constitutional', request.id || request.requestId)} compact />
|
||||
@ -846,7 +846,7 @@ export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChange
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge className={getStatusColor(request.status)}>
|
||||
<Badge className={getStatusColor(request.status, request.currentStage)}>
|
||||
{request.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
|
||||
@ -15,18 +15,15 @@ import { dealerService } from '@/services/dealer.service';
|
||||
import { formatDateTime } from '@/components/ui/utils';
|
||||
import { API } from '@/api/API';
|
||||
import { normalizeDealerProfileConstitution } from '@/lib/constitutional-change';
|
||||
import { getRequestStatusBadgeClass } from '@/lib/statusProgressTheme';
|
||||
|
||||
interface DealerConstitutionalChangePageProps {
|
||||
currentUser?: UserType | null;
|
||||
onViewDetails?: (id: string) => void;
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
|
||||
if (status.includes('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||
if (status.includes('Rejected')) return 'bg-red-100 text-red-700 border-red-300';
|
||||
return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
};
|
||||
const getStatusColor = (status: string, currentStage?: string) =>
|
||||
getRequestStatusBadgeClass(status, currentStage);
|
||||
|
||||
export function DealerConstitutionalChangePage({ onViewDetails }: DealerConstitutionalChangePageProps) {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
@ -128,7 +125,7 @@ export function DealerConstitutionalChangePage({ onViewDetails }: DealerConstitu
|
||||
title: 'Pending',
|
||||
value: requests.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
|
||||
icon: Calendar,
|
||||
color: 'bg-yellow-500',
|
||||
color: 'bg-re-red',
|
||||
},
|
||||
{
|
||||
title: 'Completed',
|
||||
|
||||
@ -42,7 +42,7 @@ export function Dashboard({ onNavigate }: DashboardProps) {
|
||||
title: 'Level 1 Pending',
|
||||
value: dashboardStats.level1Pending,
|
||||
icon: Clock,
|
||||
color: 'bg-amber-500',
|
||||
color: 'bg-red-500',
|
||||
trend: { value: 3, isPositive: false },
|
||||
filter: 'Level 1 Pending'
|
||||
},
|
||||
@ -210,7 +210,7 @@ export function Dashboard({ onNavigate }: DashboardProps) {
|
||||
<div className="w-24 text-slate-700">{item.location}</div>
|
||||
<div className="flex-1 bg-slate-200 rounded-full h-8 relative overflow-hidden">
|
||||
<div
|
||||
className="bg-amber-600 h-full rounded-full transition-all flex items-center justify-end px-3"
|
||||
className="bg-re-red h-full rounded-full transition-all flex items-center justify-end px-3"
|
||||
style={{ width: `${(item.count / maxLocationCount) * 100}%` }}
|
||||
>
|
||||
<span className="text-white">{item.count}</span>
|
||||
@ -237,7 +237,7 @@ export function Dashboard({ onNavigate }: DashboardProps) {
|
||||
className="flex items-start gap-4 p-3 hover:bg-slate-50 rounded-lg cursor-pointer transition-colors"
|
||||
onClick={() => onNavigate('applications')}
|
||||
>
|
||||
<div className="w-10 h-10 bg-amber-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<div className="w-10 h-10 bg-red-50 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
{activity.action === 'Approved' && <CheckCircle className="w-5 h-5 text-green-600" />}
|
||||
{activity.action === 'Interview Scheduled' && <Clock className="w-5 h-5 text-blue-600" />}
|
||||
{activity.action === 'Document Uploaded' && <FileText className="w-5 h-5 text-purple-600" />}
|
||||
|
||||
@ -184,7 +184,7 @@ export function FDDDashboardPage() {
|
||||
<td className="px-6 py-4">
|
||||
<Badge className={`px-3 py-1 rounded-full text-[10px] uppercase font-bold tracking-wider ${
|
||||
app.overallStatus === 'Completed' ? 'bg-green-100 text-green-700' :
|
||||
'bg-amber-100 text-amber-700'
|
||||
'bg-red-50 text-re-red-hover'
|
||||
}`}>
|
||||
{app.overallStatus === 'Active' ? 'FDD Pending' : app.overallStatus}
|
||||
</Badge>
|
||||
|
||||
@ -214,7 +214,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-20 text-amber-600">
|
||||
<div className="flex items-center justify-center p-20 text-re-red">
|
||||
<Clock className="w-8 h-8 animate-spin mr-3" />
|
||||
<span>Loading Finance Data...</span>
|
||||
</div>
|
||||
@ -234,7 +234,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-3">
|
||||
<Card
|
||||
className="cursor-pointer hover:shadow-lg transition-shadow border-amber-200 bg-amber-50/20"
|
||||
className="cursor-pointer hover:shadow-lg transition-shadow border-red-200 bg-red-50/20"
|
||||
onClick={() => {
|
||||
if (pendingAudits.length > 0 && onViewAuditDetails) {
|
||||
onViewAuditDetails(pendingAudits[0].applicationId || pendingAudits[0].id);
|
||||
@ -244,12 +244,12 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
}}
|
||||
>
|
||||
<CardHeader className="pb-3">
|
||||
<CardDescription className="text-amber-600 font-bold">Pending Audits</CardDescription>
|
||||
<CardTitle className="text-3xl text-amber-600">{pendingAudits.length}</CardTitle>
|
||||
<CardDescription className="text-re-red font-bold">Pending Audits</CardDescription>
|
||||
<CardTitle className="text-3xl text-re-red">{pendingAudits.length}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-slate-600 text-xs font-medium">FDD Sign-offs</p>
|
||||
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2 text-xs">
|
||||
<Button variant="link" className="p-0 h-auto text-re-red mt-2 text-xs">
|
||||
Review Now →
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -265,7 +265,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-slate-600">Onboarding Payments</p>
|
||||
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
||||
<Button variant="link" className="p-0 h-auto text-re-red mt-2">
|
||||
View All →
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -281,7 +281,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-slate-600">Total Validated</p>
|
||||
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
||||
<Button variant="link" className="p-0 h-auto text-re-red mt-2">
|
||||
View All →
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -297,7 +297,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-slate-600">Offboarding Cases</p>
|
||||
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
||||
<Button variant="link" className="p-0 h-auto text-re-red mt-2">
|
||||
View All →
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -313,7 +313,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-slate-600">Settlements Done</p>
|
||||
<Button variant="link" className="p-0 h-auto text-amber-600 mt-2">
|
||||
<Button variant="link" className="p-0 h-auto text-re-red mt-2">
|
||||
View All →
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -381,7 +381,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-slate-600">Amount</p>
|
||||
<p className="text-amber-700 font-bold">
|
||||
<p className="text-re-red-hover font-bold">
|
||||
₹{parseFloat(app.amount).toLocaleString('en-IN')}
|
||||
</p>
|
||||
</div>
|
||||
@ -709,7 +709,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleAddLineItem}
|
||||
className="w-full bg-amber-600 hover:bg-amber-700"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</Button>
|
||||
@ -837,7 +837,7 @@ export function FinanceDashboard({ onNavigate, onViewPaymentDetails, onViewAudit
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 uppercase tracking-wider mb-1">Current Status</p>
|
||||
<Badge variant="outline" className="bg-amber-100 text-amber-700 border-amber-200">
|
||||
<Badge variant="outline" className="bg-red-50 text-re-red-hover border-red-200">
|
||||
{selectedFnF.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@ -784,7 +784,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-re-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -812,11 +812,11 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
|
||||
{/* Status Banner */}
|
||||
<Card className="border-amber-200 bg-amber-50">
|
||||
<Card className="border-red-200 bg-red-50">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="size-12 shrink-0 aspect-square rounded-full bg-amber-100 flex items-center justify-center">
|
||||
<div className="size-12 shrink-0 aspect-square rounded-full bg-red-50 flex items-center justify-center">
|
||||
<IndianRupee className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
@ -825,7 +825,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Badge className="bg-amber-600">
|
||||
<Badge className="bg-re-red">
|
||||
{fnfCase.status}
|
||||
</Badge>
|
||||
<Badge variant={fnfCase.terminationType === 'Resignation' ? 'default' : 'secondary'}>
|
||||
@ -932,7 +932,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-slate-500">Status</Label>
|
||||
<Badge className="bg-amber-600">
|
||||
<Badge className="bg-re-red">
|
||||
{fnfCase.status}
|
||||
</Badge>
|
||||
</div>
|
||||
@ -994,9 +994,9 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
<span className="text-slate-900">Total Receivables (from Dealer)</span>
|
||||
<span className="text-red-700 text-lg">- ₹{settlement.receivables.toLocaleString('en-IN')}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center p-3 bg-amber-50 rounded-lg">
|
||||
<div className="flex justify-between items-center p-3 bg-red-50 rounded-lg">
|
||||
<span className="text-slate-900">Total Deductions</span>
|
||||
<span className="text-amber-700 text-lg">- ₹{settlement.deductions.toLocaleString('en-IN')}</span>
|
||||
<span className="text-re-red-hover text-lg">- ₹{settlement.deductions.toLocaleString('en-IN')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1030,8 +1030,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3 p-4 bg-blue-50 border border-amber-200 rounded-lg">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div className="flex items-start gap-3 p-4 bg-blue-50 border border-red-200 rounded-lg">
|
||||
<AlertCircle className="w-5 h-5 text-re-red mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm text-slate-900 mb-1">Calculation Formula</p>
|
||||
<p className="text-sm text-slate-600">
|
||||
@ -1401,12 +1401,12 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</Card>
|
||||
|
||||
{/* Deductions - Editable */}
|
||||
<Card className="border-amber-200 bg-amber-50">
|
||||
<Card className="border-red-200 bg-red-50">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600" />
|
||||
<AlertCircle className="w-5 h-5 text-re-red" />
|
||||
Deductions (Editable)
|
||||
</CardTitle>
|
||||
<CardDescription>Add or modify pending claims and deductions</CardDescription>
|
||||
@ -1512,7 +1512,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</Table>
|
||||
|
||||
{/* Add New Deduction */}
|
||||
<div className="border-t border-amber-300 pt-4 space-y-3">
|
||||
<div className="border-t border-red-300 pt-4 space-y-3">
|
||||
<p className="text-sm text-slate-700">Add New Deduction Item:</p>
|
||||
<div className="grid grid-cols-12 gap-2">
|
||||
<Select
|
||||
@ -1541,17 +1541,17 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
onChange={(e) => setNewDeduction({ ...newDeduction, amount: e.target.value })}
|
||||
className="col-span-3"
|
||||
/>
|
||||
<Button onClick={handleAddDeduction} className="col-span-1 bg-amber-600 hover:bg-amber-700">
|
||||
<Button onClick={handleAddDeduction} className="col-span-1 bg-re-red hover:bg-re-red-hover">
|
||||
<Plus className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Total */}
|
||||
<div className="pt-3 border-t-2 border-amber-400">
|
||||
<div className="pt-3 border-t-2 border-red-300">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-slate-900">Total Deductions</span>
|
||||
<span className="text-amber-700 text-xl">
|
||||
<span className="text-re-red-hover text-xl">
|
||||
₹{settlement.deductions.toLocaleString('en-IN')}
|
||||
</span>
|
||||
</div>
|
||||
@ -1563,7 +1563,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
<Card className="border-2 border-blue-300 bg-blue-50">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle className="w-5 h-5 text-amber-600" />
|
||||
<CheckCircle className="w-5 h-5 text-re-red" />
|
||||
Final Settlement Summary
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@ -1579,7 +1579,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
<div className="flex justify-between items-center p-3 bg-white rounded-lg">
|
||||
<span className="text-slate-900">Total Deductions</span>
|
||||
<span className="text-amber-700 text-lg">- ₹{settlement.deductions.toLocaleString('en-IN')}</span>
|
||||
<span className="text-re-red-hover text-lg">- ₹{settlement.deductions.toLocaleString('en-IN')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1613,8 +1613,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3 p-4 bg-white border border-amber-200 rounded-lg">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div className="flex items-start gap-3 p-4 bg-white border border-red-200 rounded-lg">
|
||||
<AlertCircle className="w-5 h-5 text-re-red mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm text-slate-900 mb-1">Calculation Formula</p>
|
||||
<p className="text-sm text-slate-600">
|
||||
@ -1758,7 +1758,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
filePath: dept.supportingDocument,
|
||||
documentType: 'Departmental Clearance Proof'
|
||||
})}
|
||||
className="flex items-center gap-1 text-[10px] text-amber-600 hover:underline"
|
||||
className="flex items-center gap-1 text-[10px] text-re-red hover:underline"
|
||||
>
|
||||
<Paperclip className="w-3 h-3" />
|
||||
View Proof
|
||||
@ -1774,10 +1774,10 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</Card>
|
||||
|
||||
{/* Important Notes */}
|
||||
<Card className="bg-blue-50 border-amber-200">
|
||||
<Card className="bg-blue-50 border-red-200">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<AlertCircle className="w-5 h-5 text-re-red mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm text-slate-900 mb-1">Department Response Guidelines</p>
|
||||
<ul className="text-sm text-slate-700 space-y-1">
|
||||
@ -1820,7 +1820,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
filePath: doc.url,
|
||||
documentType: doc.type
|
||||
})}
|
||||
className="text-amber-600 hover:text-amber-700 text-[10px] font-semibold flex items-center gap-1"
|
||||
className="text-re-red hover:text-re-red-hover text-[10px] font-semibold flex items-center gap-1"
|
||||
>
|
||||
<Paperclip className="w-3 h-3" /> PREVIEW
|
||||
</button>
|
||||
@ -1873,7 +1873,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="border-2 border-dashed border-slate-300 rounded-lg p-8 text-center hover:border-amber-400 hover:bg-amber-50 transition-colors">
|
||||
<div className="border-2 border-dashed border-slate-300 rounded-lg p-8 text-center hover:border-red-300 hover:bg-red-50 transition-colors">
|
||||
<Upload className="w-8 h-8 text-slate-400 mx-auto mb-2" />
|
||||
<p className="text-slate-600 mb-2">Click to upload or drag and drop</p>
|
||||
<p className="text-sm text-slate-500">PDF, DOC, DOCX, PNG, JPG, XLSX (max 10MB)</p>
|
||||
@ -1910,7 +1910,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-amber-600"
|
||||
className="bg-re-red"
|
||||
onClick={() => {
|
||||
setEditingBank(null);
|
||||
setIsBankModalOpen(true);
|
||||
@ -1924,9 +1924,9 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{bankDetails.length > 0 ? (
|
||||
bankDetails.map((bank: any) => (
|
||||
<Card key={bank.id} className={`relative ${bank.isPrimary ? 'border-amber-500 bg-blue-50/30' : ''}`}>
|
||||
<Card key={bank.id} className={`relative ${bank.isPrimary ? 'border-re-red bg-blue-50/30' : ''}`}>
|
||||
{bank.isPrimary && (
|
||||
<div className="absolute top-0 right-0 p-1 bg-amber-600 text-white text-[10px] uppercase font-bold px-2 rounded-bl">
|
||||
<div className="absolute top-0 right-0 p-1 bg-re-red text-white text-[10px] uppercase font-bold px-2 rounded-bl">
|
||||
Primary
|
||||
</div>
|
||||
)}
|
||||
@ -1955,7 +1955,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-[11px] text-amber-600"
|
||||
className="h-7 text-[11px] text-re-red"
|
||||
onClick={() => {
|
||||
setEditingBank(bank);
|
||||
setIsBankModalOpen(true);
|
||||
@ -2056,7 +2056,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
{/* Settlement Checklist */}
|
||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 mb-4">
|
||||
<p className="text-sm font-bold text-slate-900 mb-3 flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-amber-600" />
|
||||
<CheckCircle className="w-4 h-4 text-re-red" />
|
||||
Compliance Checklist
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
@ -2067,7 +2067,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
id={`check-${item.id}`}
|
||||
checked={checklist.includes(item.id)}
|
||||
onChange={() => toggleChecklist(item.id)}
|
||||
className="w-4 h-4 mt-1 rounded border-slate-300 text-amber-600 focus:ring-amber-500"
|
||||
className="w-4 h-4 mt-1 rounded border-slate-300 text-re-red focus:ring-re-red"
|
||||
/>
|
||||
<label htmlFor={`check-${item.id}`} className="text-sm text-slate-700 leading-tight">
|
||||
{item.label}
|
||||
@ -2146,7 +2146,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
}}
|
||||
/>
|
||||
{parseFloat(settlementDetails.adjustments) !== 0 && (
|
||||
<p className="text-sm text-amber-600 mt-1 flex items-center gap-1">
|
||||
<p className="text-sm text-re-red mt-1 flex items-center gap-1">
|
||||
<AlertCircle className="w-3 h-3" />
|
||||
Adjusted amount: ₹{settlementDetails.settlementAmount}
|
||||
</p>
|
||||
@ -2197,7 +2197,7 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full border-blue-300 text-amber-600 hover:bg-blue-50"
|
||||
className="w-full border-blue-300 text-re-red hover:bg-blue-50"
|
||||
onClick={handleRequestClarification}
|
||||
disabled={submitting}
|
||||
>
|
||||
|
||||
@ -150,7 +150,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-re-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -172,7 +172,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-slate-900 text-2xl">{pendingCount}</div>
|
||||
<Calculator className="w-8 h-8 text-amber-600" />
|
||||
<Calculator className="w-8 h-8 text-re-red" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -224,21 +224,21 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
<Button
|
||||
variant={filterStatus === 'all' ? 'default' : 'outline'}
|
||||
onClick={() => setFilterStatus('all')}
|
||||
className={filterStatus === 'all' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={filterStatus === 'all' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
>
|
||||
All Cases ({displaySettlements.length})
|
||||
</Button>
|
||||
<Button
|
||||
variant={filterStatus === 'pending' ? 'default' : 'outline'}
|
||||
onClick={() => setFilterStatus('pending')}
|
||||
className={filterStatus === 'pending' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={filterStatus === 'pending' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
>
|
||||
Pending Review ({pendingCount})
|
||||
</Button>
|
||||
<Button
|
||||
variant={filterStatus === 'approved' ? 'default' : 'outline'}
|
||||
onClick={() => setFilterStatus('approved')}
|
||||
className={filterStatus === 'approved' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={filterStatus === 'approved' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
>
|
||||
Approved ({approvedCount})
|
||||
</Button>
|
||||
@ -314,7 +314,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={fnfCase.status === 'Settlement Approved' ? 'default' : 'secondary'}
|
||||
className={fnfCase.status === 'Settlement Approved' ? 'bg-green-600 text-white' : 'bg-amber-600 text-white'}
|
||||
className={fnfCase.status === 'Settlement Approved' ? 'bg-green-600 text-white' : 'bg-re-red text-white'}
|
||||
>
|
||||
{fnfCase.status}
|
||||
</Badge>
|
||||
@ -323,7 +323,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
<Button
|
||||
size="sm"
|
||||
variant={fnfCase.status === 'Pending Finance Review' ? 'default' : 'outline'}
|
||||
className={fnfCase.status === 'Pending Finance Review' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={fnfCase.status === 'Pending Finance Review' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
onClick={() => handleViewDetails(fnfCase)}
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
@ -442,10 +442,10 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
</div>
|
||||
|
||||
{/* Deductions */}
|
||||
<Card className="border-amber-200 bg-amber-50">
|
||||
<Card className="border-red-200 bg-red-50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600" />
|
||||
<AlertCircle className="w-5 h-5 text-re-red" />
|
||||
Deductions
|
||||
</CardTitle>
|
||||
<CardDescription>Pending claims and deductions</CardDescription>
|
||||
@ -479,9 +479,9 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
<span className="text-slate-900">Total Receivables (from Dealer)</span>
|
||||
<span className="text-red-600 text-lg">- ₹{settlement.receivables.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center p-3 bg-amber-50 rounded">
|
||||
<div className="flex justify-between items-center p-3 bg-red-50 rounded">
|
||||
<span className="text-slate-900">Total Deductions</span>
|
||||
<span className="text-amber-600 text-lg">- ₹{settlement.deductions.toLocaleString()}</span>
|
||||
<span className="text-re-red text-lg">- ₹{settlement.deductions.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -695,7 +695,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
<p className="text-sm text-slate-500">Status</p>
|
||||
<Badge
|
||||
variant={selectedCase.status === 'Settlement Approved' ? 'default' : 'secondary'}
|
||||
className={selectedCase.status === 'Settlement Approved' ? 'bg-green-600 text-white' : 'bg-amber-600 text-white'}
|
||||
className={selectedCase.status === 'Settlement Approved' ? 'bg-green-600 text-white' : 'bg-re-red text-white'}
|
||||
>
|
||||
{selectedCase.status}
|
||||
</Badge>
|
||||
@ -793,10 +793,10 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
</Card>
|
||||
|
||||
{/* Deductions */}
|
||||
<Card className="border-amber-200 bg-amber-50">
|
||||
<Card className="border-red-200 bg-red-50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600" />
|
||||
<AlertCircle className="w-5 h-5 text-re-red" />
|
||||
Deductions
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@ -850,7 +850,7 @@ export function FinanceFnFPage({ onViewFnFDetails }: FinanceFnFPageProps = {}) {
|
||||
</Button>
|
||||
{selectedCase?.status === 'Pending Finance Review' && (
|
||||
<Button
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
onClick={() => {
|
||||
setShowDetailsDialog(false);
|
||||
handleReviewCase(selectedCase);
|
||||
|
||||
@ -146,7 +146,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-20">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-amber-600"></div>
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-re-red"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -168,7 +168,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Button
|
||||
size="sm"
|
||||
variant={activeType === 'SECURITY_DEPOSIT' ? 'default' : 'outline'}
|
||||
className={activeType === 'SECURITY_DEPOSIT' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={activeType === 'SECURITY_DEPOSIT' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
onClick={() => setActiveType('SECURITY_DEPOSIT')}
|
||||
>
|
||||
Security Deposit
|
||||
@ -176,7 +176,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Button
|
||||
size="sm"
|
||||
variant={activeType === 'FIRST_FILL' ? 'default' : 'outline'}
|
||||
className={activeType === 'FIRST_FILL' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={activeType === 'FIRST_FILL' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
onClick={() => setActiveType('FIRST_FILL')}
|
||||
>
|
||||
First Fill
|
||||
@ -190,7 +190,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
"border",
|
||||
activeDeposit?.status === 'Verified' ? "border-green-200 bg-green-50" :
|
||||
activeDeposit?.status === 'Rejected' ? "border-red-200 bg-red-50" :
|
||||
"border-amber-200 bg-amber-50"
|
||||
"border-red-200 bg-red-50"
|
||||
)}>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
@ -199,13 +199,13 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
"w-12 h-12 rounded-full flex items-center justify-center",
|
||||
activeDeposit?.status === 'Verified' ? "bg-green-100" :
|
||||
activeDeposit?.status === 'Rejected' ? "bg-red-100" :
|
||||
"bg-amber-100"
|
||||
"bg-red-50"
|
||||
)}>
|
||||
<IndianRupee className={cn(
|
||||
"w-6 h-6",
|
||||
activeDeposit?.status === 'Verified' ? "text-green-600" :
|
||||
activeDeposit?.status === 'Rejected' ? "text-red-600" :
|
||||
"text-amber-600"
|
||||
"text-re-red"
|
||||
)} />
|
||||
</div>
|
||||
<div>
|
||||
@ -224,7 +224,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Badge className={cn(
|
||||
activeDeposit?.status === 'Verified' ? "bg-green-600" :
|
||||
activeDeposit?.status === 'Rejected' ? "bg-red-600" :
|
||||
"bg-amber-600 text-white"
|
||||
"bg-re-red text-white"
|
||||
)}>
|
||||
{activeDeposit?.status || 'No Record'}
|
||||
</Badge>
|
||||
@ -237,7 +237,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-xl">
|
||||
<User className="w-5 h-5 text-amber-600" />
|
||||
<User className="w-5 h-5 text-re-red" />
|
||||
Applicant Information
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@ -265,7 +265,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-xl">
|
||||
<CreditCard className="w-5 h-5 text-amber-600" />
|
||||
<CreditCard className="w-5 h-5 text-re-red" />
|
||||
Deposit Tracking
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@ -273,7 +273,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<Label className="text-slate-500 block mb-1">Expected Amount</Label>
|
||||
<p className="text-2xl font-bold text-amber-900">
|
||||
<p className="text-2xl font-bold text-red-900">
|
||||
₹{(activeType === 'SECURITY_DEPOSIT'
|
||||
? (configs.SECURITY_DEPOSIT?.amount || 500000)
|
||||
: (configs.FIRST_FILL?.amount || 1500000)
|
||||
@ -312,7 +312,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-xl">
|
||||
<FileText className="w-5 h-5 text-amber-600" />
|
||||
<FileText className="w-5 h-5 text-re-red" />
|
||||
Verification Evidence
|
||||
</CardTitle>
|
||||
<CardDescription>Documents uploaded by the applicant for payment proof</CardDescription>
|
||||
@ -342,7 +342,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-amber-600 hover:text-amber-700 hover:bg-amber-50"
|
||||
className="text-re-red hover:text-re-red-hover hover:bg-red-50"
|
||||
onClick={() => {
|
||||
setPreviewDoc(doc);
|
||||
setShowPreviewModal(true);
|
||||
@ -364,10 +364,10 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card className="border-amber-100 shadow-sm">
|
||||
<CardHeader className="bg-amber-50/50">
|
||||
<Card className="border-red-100 shadow-sm">
|
||||
<CardHeader className="bg-red-50/50">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Wallet className="w-5 h-5 text-amber-600" />
|
||||
<Wallet className="w-5 h-5 text-re-red" />
|
||||
Finance Action
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@ -431,7 +431,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Button
|
||||
className={cn(
|
||||
"w-full transition-all duration-200",
|
||||
activeDeposit?.status === 'Verified' ? "bg-green-600 hover:bg-green-600 opacity-90" : "bg-amber-600 hover:bg-amber-700"
|
||||
activeDeposit?.status === 'Verified' ? "bg-green-600 hover:bg-green-600 opacity-90" : "bg-re-red hover:bg-re-red-hover"
|
||||
)}
|
||||
onClick={handleApprovePayment}
|
||||
disabled={isSubmitting || activeDeposit?.status === 'Verified'}
|
||||
@ -461,7 +461,7 @@ export function FinancePaymentDetailsPage({ applicationId, onBack }: FinancePaym
|
||||
<Card className="bg-slate-900 text-white border-none shadow-xl">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base font-medium flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-amber-400" />
|
||||
<Clock className="w-4 h-4 text-re-red" />
|
||||
Next Steps
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@ -396,7 +396,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-re-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -494,7 +494,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "New":
|
||||
return "bg-amber-100 text-blue-700 border-blue-300";
|
||||
return "bg-red-50 text-blue-700 border-blue-300";
|
||||
case "In Progress":
|
||||
return "bg-yellow-100 text-yellow-700 border-yellow-300";
|
||||
case "Under Review":
|
||||
@ -599,7 +599,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
<Badge
|
||||
className={
|
||||
fnfCase.requestType === "Resignation"
|
||||
? "bg-amber-100 text-amber-700 border-amber-300"
|
||||
? "bg-red-50 text-re-red-hover border-red-300"
|
||||
: "bg-red-100 text-red-700 border-red-300"
|
||||
}
|
||||
>
|
||||
@ -626,7 +626,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
|
||||
{/* {canSendToStakeholders && fnfCase.status === "New" && (
|
||||
<Button
|
||||
className="bg-amber-600 hover:bg-blue-700"
|
||||
className="bg-re-red hover:bg-blue-700"
|
||||
onClick={() => setSendStakeholdersDialog(true)}
|
||||
>
|
||||
<Send className="w-4 h-4 mr-2" />
|
||||
@ -775,7 +775,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
)
|
||||
? "bg-green-100 border-green-600"
|
||||
: responsesReceived > 0
|
||||
? "bg-amber-100 border-amber-600"
|
||||
? "bg-red-50 border-re-red"
|
||||
: "bg-slate-100 border-slate-300"
|
||||
}`}
|
||||
>
|
||||
@ -785,7 +785,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
) ? (
|
||||
<Check className="w-6 h-6 text-green-600" />
|
||||
) : responsesReceived > 0 ? (
|
||||
<Users className="w-6 h-6 text-amber-600" />
|
||||
<Users className="w-6 h-6 text-re-red" />
|
||||
) : (
|
||||
<Clock className="w-6 h-6 text-slate-400" />
|
||||
)}
|
||||
@ -814,7 +814,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
)
|
||||
? "bg-green-600"
|
||||
: responsesReceived > 0
|
||||
? "bg-amber-600"
|
||||
? "bg-re-red"
|
||||
: "bg-slate-400"
|
||||
}
|
||||
>
|
||||
@ -843,7 +843,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
fnfCase.status,
|
||||
)
|
||||
? "bg-green-50 border-green-200"
|
||||
: "bg-blue-50 border-amber-200"
|
||||
: "bg-blue-50 border-red-200"
|
||||
}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
@ -910,14 +910,14 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
className={`size-12 shrink-0 aspect-square rounded-full flex items-center justify-center border-2 ${fnfCase.status === "Completed"
|
||||
? "bg-green-100 border-green-600"
|
||||
: fnfCase.status === "Finance Approval"
|
||||
? "bg-amber-100 border-amber-600"
|
||||
? "bg-red-50 border-re-red"
|
||||
: "bg-slate-100 border-slate-300"
|
||||
}`}
|
||||
>
|
||||
{fnfCase.status === "Completed" ? (
|
||||
<Check className="w-6 h-6 text-green-600" />
|
||||
) : fnfCase.status === "Finance Approval" ? (
|
||||
<FileCheck className="w-6 h-6 text-amber-600" />
|
||||
<FileCheck className="w-6 h-6 text-re-red" />
|
||||
) : (
|
||||
<Clock className="w-6 h-6 text-slate-400" />
|
||||
)}
|
||||
@ -940,7 +940,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
fnfCase.status === "Completed"
|
||||
? "bg-green-600"
|
||||
: fnfCase.status === "Finance Approval"
|
||||
? "bg-amber-600"
|
||||
? "bg-re-red"
|
||||
: "bg-slate-400"
|
||||
}
|
||||
>
|
||||
@ -964,7 +964,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
className={
|
||||
fnfCase.status === "Completed"
|
||||
? "bg-green-50 border-green-200"
|
||||
: "bg-blue-50 border-amber-200"
|
||||
: "bg-blue-50 border-red-200"
|
||||
}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
@ -989,7 +989,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
"0"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-amber-100 rounded-lg">
|
||||
<div className="text-center p-3 bg-red-50 rounded-lg">
|
||||
<p className="text-xs text-blue-700 mb-1">
|
||||
Net Amount
|
||||
</p>
|
||||
@ -1022,14 +1022,14 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
className={`size-12 shrink-0 aspect-square rounded-full flex items-center justify-center border-2 ${fnfCase.status === "Completed"
|
||||
? "bg-green-100 border-green-600"
|
||||
: fnfCase.status === "Finance Approval"
|
||||
? "bg-amber-100 border-amber-600"
|
||||
? "bg-red-50 border-re-red"
|
||||
: "bg-slate-100 border-slate-300"
|
||||
}`}
|
||||
>
|
||||
{fnfCase.status === "Completed" ? (
|
||||
<Check className="w-6 h-6 text-green-600" />
|
||||
) : fnfCase.status === "Finance Approval" ? (
|
||||
<MessageSquare className="w-6 h-6 text-amber-600" />
|
||||
<MessageSquare className="w-6 h-6 text-re-red" />
|
||||
) : (
|
||||
<Clock className="w-6 h-6 text-slate-400" />
|
||||
)}
|
||||
@ -1052,7 +1052,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
fnfCase.status === "Completed"
|
||||
? "bg-green-600"
|
||||
: fnfCase.status === "Finance Approval"
|
||||
? "bg-amber-600"
|
||||
? "bg-re-red"
|
||||
: "bg-slate-400"
|
||||
}
|
||||
>
|
||||
@ -1255,7 +1255,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-amber-200 bg-blue-50/30">
|
||||
<Card className="border-red-200 bg-blue-50/30">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-blue-900">
|
||||
F&F Settlement Information
|
||||
@ -1426,7 +1426,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-amber-600 hover:text-blue-700"
|
||||
className="text-re-red hover:text-blue-700"
|
||||
onClick={() => {
|
||||
setSelectedDept(dept);
|
||||
setClearanceForm({
|
||||
@ -1518,14 +1518,14 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
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">
|
||||
<div className="p-6 bg-red-50 rounded-lg border border-red-200">
|
||||
<p className="text-sm text-re-red-hover mb-2">
|
||||
Total Deductions
|
||||
</p>
|
||||
<p className="text-3xl text-amber-600 font-bold">
|
||||
<p className="text-3xl text-re-red font-bold">
|
||||
₹{fnfCase.totalDeductions?.toLocaleString() || "0"}
|
||||
</p>
|
||||
<p className="text-xs text-amber-600 mt-1">
|
||||
<p className="text-xs text-re-red mt-1">
|
||||
Warranty holdbacks / Policy penalties
|
||||
</p>
|
||||
</div>
|
||||
@ -1677,7 +1677,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
setEditingBank(null);
|
||||
setIsBankModalOpen(true);
|
||||
}}
|
||||
className="bg-amber-600"
|
||||
className="bg-re-red"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Account
|
||||
@ -1687,16 +1687,16 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{bankDetails.length > 0 ? (
|
||||
bankDetails.map((bank: any) => (
|
||||
<Card key={bank.id} className={`relative overflow-hidden ${bank.isPrimary ? 'border-amber-500 bg-blue-50/30' : ''}`}>
|
||||
<Card key={bank.id} className={`relative overflow-hidden ${bank.isPrimary ? 'border-re-red bg-blue-50/30' : ''}`}>
|
||||
{bank.isPrimary && (
|
||||
<div className="absolute top-0 right-0 p-1 bg-amber-600 text-white text-[10px] uppercase font-bold px-2 rounded-bl">
|
||||
<div className="absolute top-0 right-0 p-1 bg-re-red text-white text-[10px] uppercase font-bold px-2 rounded-bl">
|
||||
Primary
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-amber-100 rounded-lg">
|
||||
<Building2 className="w-5 h-5 text-amber-600" />
|
||||
<div className="p-2 bg-red-50 rounded-lg">
|
||||
<Building2 className="w-5 h-5 text-re-red" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-bold text-slate-900">{bank.bankName}</p>
|
||||
@ -1727,7 +1727,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-amber-600"
|
||||
className="h-8 text-re-red"
|
||||
onClick={() => {
|
||||
setEditingBank(bank);
|
||||
setIsBankModalOpen(true);
|
||||
@ -1787,12 +1787,12 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
<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.action === 'FNF_CREATED' && <Badge className="bg-amber-600 h-2 w-2 p-0 rounded-full" />}
|
||||
{log.action === 'FNF_CREATED' && <Badge className="bg-re-red h-2 w-2 p-0 rounded-full" />}
|
||||
{(log.description && !log.newData?.action) ? log.description : (
|
||||
<>
|
||||
{getFriendlyActionName(log.newData?.action || log.action)}
|
||||
{log.newData?.department && (
|
||||
<span className="text-amber-600 ml-1 font-bold">
|
||||
<span className="text-re-red ml-1 font-bold">
|
||||
- {log.newData.department}
|
||||
</span>
|
||||
)}
|
||||
@ -1855,7 +1855,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-blue-50 rounded-lg border border-amber-200">
|
||||
<div className="p-4 bg-blue-50 rounded-lg border border-red-200">
|
||||
<p className="text-sm text-blue-900 mb-2">
|
||||
Notifications will be sent to:
|
||||
</p>
|
||||
@ -1877,7 +1877,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSendToStakeholders}
|
||||
className="bg-amber-600 hover:bg-blue-700"
|
||||
className="bg-re-red hover:bg-blue-700"
|
||||
>
|
||||
<Send className="w-4 h-4 mr-2" />
|
||||
Send Notifications
|
||||
@ -1951,7 +1951,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-amber-600 hover:bg-blue-700"
|
||||
className="bg-re-red hover:bg-blue-700"
|
||||
onClick={handleUpdateClearance}
|
||||
disabled={isUpdatingClearance}
|
||||
>
|
||||
|
||||
@ -34,7 +34,7 @@ const getStatusColor = (status: string) => {
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
return type === 'Resignation'
|
||||
? 'bg-amber-100 text-amber-700 border-amber-300'
|
||||
? 'bg-red-50 text-re-red-hover border-red-300'
|
||||
: 'bg-red-100 text-red-700 border-red-300';
|
||||
};
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ const BUCKET_LABEL: Record<SlaBucket, string> = {
|
||||
|
||||
const BUCKET_CLASS: Record<SlaBucket, string> = {
|
||||
healthy: 'bg-emerald-100 text-emerald-800 border-emerald-200',
|
||||
warning: 'bg-amber-100 text-amber-800 border-amber-200',
|
||||
warning: 'bg-red-50 text-red-800 border-red-200',
|
||||
critical: 'bg-orange-100 text-orange-800 border-orange-200',
|
||||
breached: 'bg-red-100 text-red-800 border-red-200'
|
||||
};
|
||||
@ -74,7 +74,7 @@ function ProgressCell({ percent, bucket }: { percent: number; bucket: SlaBucket
|
||||
: bucket === 'critical'
|
||||
? 'bg-orange-500'
|
||||
: bucket === 'warning'
|
||||
? 'bg-amber-500'
|
||||
? 'bg-red-500'
|
||||
: 'bg-emerald-500';
|
||||
const capped = Math.min(percent, 100);
|
||||
return (
|
||||
@ -134,7 +134,7 @@ function QueueTable({ items, emptyMessage }: { items: SlaQueueItem[]; emptyMessa
|
||||
href={row.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-amber-600 hover:text-amber-800"
|
||||
className="text-re-red hover:text-red-800"
|
||||
title="Open case"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
@ -219,7 +219,7 @@ export const SLAMonitorPanel: React.FC = () => {
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
||||
<Activity className="w-5 h-5 text-amber-600" />
|
||||
<Activity className="w-5 h-5 text-re-red" />
|
||||
SLA Operations Monitor
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
@ -280,7 +280,7 @@ export const SLAMonitorPanel: React.FC = () => {
|
||||
icon={<AlertTriangle className="w-4 h-4 text-red-600" />}
|
||||
highlight="red"
|
||||
/>
|
||||
<SummaryCard label="Due soon" value={summary?.dueSoonCount ?? '—'} icon={<Timer className="w-4 h-4 text-amber-600" />} />
|
||||
<SummaryCard label="Due soon" value={summary?.dueSoonCount ?? '—'} icon={<Timer className="w-4 h-4 text-re-red" />} />
|
||||
<SummaryCard
|
||||
label="On track"
|
||||
value={summary?.onTrackCount ?? '—'}
|
||||
@ -297,7 +297,7 @@ export const SLAMonitorPanel: React.FC = () => {
|
||||
<Card className="border-slate-200">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<BarChart3 className="w-4 h-4 text-amber-600" />
|
||||
<BarChart3 className="w-4 h-4 text-re-red" />
|
||||
Analytics (last {analytics.periodDays} days)
|
||||
</CardTitle>
|
||||
<CardDescription>Breach rate, resolution time, and top delayed stages</CardDescription>
|
||||
@ -434,7 +434,7 @@ export const SLAMonitorPanel: React.FC = () => {
|
||||
<Badge variant="outline">{b.status}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a href={b.link} target="_blank" rel="noopener noreferrer" className="text-amber-600">
|
||||
<a href={b.link} target="_blank" rel="noopener noreferrer" className="text-re-red">
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
</TableCell>
|
||||
@ -621,7 +621,7 @@ function StatusRow({ label, value, ok }: { label: string; value: string; ok?: bo
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-600">{label}</span>
|
||||
<span className={ok === false ? 'text-amber-700 font-medium' : 'text-slate-900'}>{value}</span>
|
||||
<span className={ok === false ? 'text-re-red-hover font-medium' : 'text-slate-900'}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ export const SLAConfigPage: React.FC = () => {
|
||||
<div className="space-y-6 max-w-7xl mx-auto">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 flex items-center gap-2">
|
||||
<Clock className="w-6 h-6 text-amber-600" />
|
||||
<Clock className="w-6 h-6 text-re-red" />
|
||||
SLA & Escalation
|
||||
</h1>
|
||||
<p className="text-slate-500">Configure TAT rules and monitor live queue, breaches, and schedulers</p>
|
||||
@ -104,13 +104,13 @@ export const SLAConfigPage: React.FC = () => {
|
||||
<CardHeader className="pb-3 bg-gradient-to-br from-white to-slate-50/50">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-amber-50 border border-amber-100 flex items-center justify-center">
|
||||
<Clock className="w-5 h-5 text-amber-600" />
|
||||
<div className="w-10 h-10 rounded-xl bg-red-50 border border-red-100 flex items-center justify-center">
|
||||
<Clock className="w-5 h-5 text-re-red" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg">{sla.activityName}</CardTitle>
|
||||
<CardDescription className="flex items-center gap-1.5 mt-0.5">
|
||||
<span className="font-semibold text-amber-700">
|
||||
<span className="font-semibold text-re-red-hover">
|
||||
Target TAT: {sla.tatHours} {sla.tatUnit}
|
||||
</span>
|
||||
</CardDescription>
|
||||
@ -133,7 +133,7 @@ export const SLAConfigPage: React.FC = () => {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleEdit(sla)}
|
||||
className="h-8 w-8 text-slate-400 hover:text-amber-600"
|
||||
className="h-8 w-8 text-slate-400 hover:text-re-red"
|
||||
>
|
||||
<Pen className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ApplicationProgressBar } from '@/features/onboarding/components/ApplicationProgressBar';
|
||||
import { Application } from '@/lib/mock-data';
|
||||
import { MapPin, Phone, Mail, Award, Calendar, Building } from 'lucide-react';
|
||||
import { formatDateTime } from '@/components/ui/utils';
|
||||
@ -17,7 +17,7 @@ export function ApplicationCard({ application, onViewDetails }: ApplicationCardP
|
||||
'Questionnaire Pending': 'bg-orange-500',
|
||||
'Questionnaire Completed': 'bg-blue-500',
|
||||
'Shortlisted': 'bg-cyan-500',
|
||||
'Level 1 Pending': 'bg-amber-500',
|
||||
'Level 1 Pending': 'bg-red-500',
|
||||
'Level 1 Approved': 'bg-green-500',
|
||||
'Level 2 Pending': 'bg-purple-500',
|
||||
'Level 2 Approved': 'bg-green-600',
|
||||
@ -124,7 +124,12 @@ export function ApplicationCard({ application, onViewDetails }: ApplicationCardP
|
||||
<span className="text-slate-600">Progress</span>
|
||||
<span className="text-slate-900" data-testid="onboarding-application-card-progress-text">{application.progress}%</span>
|
||||
</div>
|
||||
<Progress value={application.progress} className="h-2" data-testid="onboarding-application-card-progress-bar" />
|
||||
<ApplicationProgressBar
|
||||
value={application.progress}
|
||||
status={application.status}
|
||||
barClassName="h-2 w-full bg-status-progress-soft"
|
||||
data-testid="onboarding-application-card-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Deadline Warning */}
|
||||
@ -138,7 +143,7 @@ export function ApplicationCard({ application, onViewDetails }: ApplicationCardP
|
||||
|
||||
<Button
|
||||
onClick={() => onViewDetails(application.id)}
|
||||
className="w-full bg-amber-600 hover:bg-amber-700"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover"
|
||||
data-testid="onboarding-application-card-view-button"
|
||||
>
|
||||
View Details
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { cn } from '@/components/ui/utils';
|
||||
import { getStatusProgressBarClass } from '@/lib/statusProgressTheme';
|
||||
|
||||
export interface ApplicationProgressBarProps {
|
||||
value: number;
|
||||
status?: string | null;
|
||||
currentStage?: string | null;
|
||||
/** Track styling — matches Application Details progress tab by default */
|
||||
barClassName?: string;
|
||||
showPercent?: boolean;
|
||||
percentClassName?: string;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
/** Status-colored progress bar (amber / green / re-red) — same strategy as Application Details. */
|
||||
export function ApplicationProgressBar({
|
||||
value,
|
||||
status,
|
||||
currentStage,
|
||||
barClassName = 'h-2 w-20 bg-status-progress-soft',
|
||||
showPercent = false,
|
||||
percentClassName = 'text-slate-600 shrink-0',
|
||||
'data-testid': testId,
|
||||
}: ApplicationProgressBarProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('flex items-center gap-2 min-w-0', showPercent && 'w-full max-w-[8rem]')}
|
||||
data-testid={testId ? `${testId}-container` : undefined}
|
||||
>
|
||||
<Progress
|
||||
value={value}
|
||||
className={barClassName}
|
||||
indicatorClassName={getStatusProgressBarClass(status, currentStage)}
|
||||
data-testid={testId}
|
||||
/>
|
||||
{showPercent && (
|
||||
<span className={percentClassName} data-testid={testId ? `${testId}-text` : undefined}>
|
||||
{value}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -115,7 +115,7 @@ export const BankDetailsModal: React.FC<BankDetailsModalProps> = ({
|
||||
id="isPrimaryModal"
|
||||
name="isPrimary"
|
||||
defaultChecked={editingBank?.isPrimary}
|
||||
className="w-4 h-4 rounded border-slate-300 text-amber-600 focus:ring-amber-500"
|
||||
className="w-4 h-4 rounded border-slate-300 text-re-red focus:ring-re-red"
|
||||
data-testid="onboarding-is-primary-checkbox"
|
||||
/>
|
||||
<Label htmlFor="isPrimaryModal" className="text-xs font-medium cursor-pointer">Set as primary account</Label>
|
||||
@ -124,7 +124,7 @@ export const BankDetailsModal: React.FC<BankDetailsModalProps> = ({
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" size="sm" onClick={onClose} data-testid="onboarding-bank-details-cancel">Cancel</Button>
|
||||
<Button type="submit" disabled={isSubmitting} size="sm" className="bg-amber-600" data-testid="onboarding-bank-details-submit">
|
||||
<Button type="submit" disabled={isSubmitting} size="sm" className="bg-re-red" data-testid="onboarding-bank-details-submit">
|
||||
{isSubmitting ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
|
||||
{editingBank ? 'Update Account' : 'Save Bank Details'}
|
||||
</Button>
|
||||
|
||||
@ -33,11 +33,11 @@ const QuestionnaireResponseView: React.FC<QuestionnaireResponseViewProps> = ({ a
|
||||
<div className="space-y-6" data-testid="onboarding-questionnaire-view">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<ClipboardList className="w-5 h-5 text-amber-600" />
|
||||
<ClipboardList className="w-5 h-5 text-re-red" />
|
||||
<h3 className="text-slate-900">Questionnaire Responses</h3>
|
||||
</div>
|
||||
{totalScore !== undefined && (
|
||||
<Badge className="bg-amber-600" data-testid="onboarding-questionnaire-total-score">Score: {totalScore}/100</Badge>
|
||||
<Badge className="bg-re-red" data-testid="onboarding-questionnaire-total-score">Score: {totalScore}/100</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -62,12 +62,12 @@ const QuestionnaireResponseView: React.FC<QuestionnaireResponseViewProps> = ({ a
|
||||
return (
|
||||
<div
|
||||
key={resp.id}
|
||||
className="border border-slate-200 rounded-lg p-5 hover:border-amber-300 transition-colors"
|
||||
className="border border-slate-200 rounded-lg p-5 hover:border-red-300 transition-colors"
|
||||
data-testid={`onboarding-questionnaire-item-${index}`}
|
||||
>
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<div className="w-8 h-8 rounded-full bg-amber-100 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-amber-600">{index + 1}</span>
|
||||
<div className="w-8 h-8 rounded-full bg-red-50 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-re-red">{index + 1}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
|
||||
@ -126,9 +126,9 @@ export function ApplicantInformationCard({
|
||||
data-testid="onboarding-applicant-info-edit-firm-type"
|
||||
>
|
||||
Proposed Firm Type
|
||||
<Pencil className="w-3 h-3 text-slate-300 group-hover:text-amber-600 transition-colors" />
|
||||
<Pencil className="w-3 h-3 text-slate-300 group-hover:text-re-red transition-colors" />
|
||||
</p>
|
||||
<p className="text-slate-900 font-black text-amber-700 tracking-tight leading-none mt-1" data-testid="onboarding-applicant-info-firm-type">
|
||||
<p className="text-slate-900 font-black text-re-red-hover tracking-tight leading-none mt-1" data-testid="onboarding-applicant-info-firm-type">
|
||||
{application.constitutionType || 'Not Provided'}
|
||||
</p>
|
||||
</div>
|
||||
@ -214,14 +214,14 @@ export function ApplicantInformationCard({
|
||||
<div className="pt-6 border-t mt-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-black text-slate-900 uppercase tracking-widest flex items-center gap-2">
|
||||
<CreditCard className="w-4 h-4 text-amber-600" /> Statutory & Bank Information
|
||||
<CreditCard className="w-4 h-4 text-re-red" /> Statutory & Bank Information
|
||||
</h3>
|
||||
{canEditStatutory && !isEditingStatutory && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onEditStatutory}
|
||||
className="h-8 text-amber-600 hover:text-amber-700 hover:bg-amber-50 gap-1.5"
|
||||
className="h-8 text-re-red hover:text-re-red-hover hover:bg-red-50 gap-1.5"
|
||||
data-testid="onboarding-applicant-info-edit-statutory"
|
||||
>
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
@ -232,7 +232,7 @@ export function ApplicantInformationCard({
|
||||
|
||||
{isEditingStatutory ? (
|
||||
<div
|
||||
className="bg-slate-50/50 p-6 rounded-xl border-2 border-amber-100 space-y-4"
|
||||
className="bg-slate-50/50 p-6 rounded-xl border-2 border-red-100 space-y-4"
|
||||
data-testid="onboarding-applicant-info-statutory-edit-form"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@ -324,7 +324,7 @@ export function ApplicantInformationCard({
|
||||
size="sm"
|
||||
onClick={onSaveStatutory}
|
||||
disabled={isSavingStatutory}
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
data-testid="onboarding-applicant-info-statutory-save"
|
||||
>
|
||||
{isSavingStatutory ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Save Details'}
|
||||
|
||||
@ -271,9 +271,9 @@ export function ApplicationDetailsActionModals(props: ApplicationDetailsActionMo
|
||||
<Select value={interviewType} onValueChange={setInterviewType}>
|
||||
<SelectTrigger className="mt-2" data-testid="onboarding-schedule-type-select"><SelectValue placeholder="Select interview type" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="level1" disabled={isInterviewCompleted(1) || isInterviewActive(1)}><div className="flex items-center justify-between w-full"><span>Level 1</span>{isInterviewCompleted(1) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(1) && <Clock className="w-4 h-4 text-amber-500 ml-2 inline" />}</div></SelectItem>
|
||||
<SelectItem value="level2" disabled={!isInterviewCompleted(1) || isInterviewCompleted(2) || isInterviewActive(2)}><div className="flex items-center justify-between w-full"><span>Level 2</span>{!isInterviewCompleted(1) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L1)</span>}{isInterviewCompleted(2) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(2) && <Clock className="w-4 h-4 text-amber-500 ml-2 inline" />}</div></SelectItem>
|
||||
<SelectItem value="level3" disabled={!isInterviewCompleted(2) || isInterviewCompleted(3) || isInterviewActive(3)}><div className="flex items-center justify-between w-full"><span>Level 3</span>{!isInterviewCompleted(2) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L2)</span>}{isInterviewCompleted(3) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(3) && <Clock className="w-4 h-4 text-amber-500 ml-2 inline" />}</div></SelectItem>
|
||||
<SelectItem value="level1" disabled={isInterviewCompleted(1) || isInterviewActive(1)}><div className="flex items-center justify-between w-full"><span>Level 1</span>{isInterviewCompleted(1) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(1) && <Clock className="w-4 h-4 text-re-red ml-2 inline" />}</div></SelectItem>
|
||||
<SelectItem value="level2" disabled={!isInterviewCompleted(1) || isInterviewCompleted(2) || isInterviewActive(2)}><div className="flex items-center justify-between w-full"><span>Level 2</span>{!isInterviewCompleted(1) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L1)</span>}{isInterviewCompleted(2) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(2) && <Clock className="w-4 h-4 text-re-red ml-2 inline" />}</div></SelectItem>
|
||||
<SelectItem value="level3" disabled={!isInterviewCompleted(2) || isInterviewCompleted(3) || isInterviewActive(3)}><div className="flex items-center justify-between w-full"><span>Level 3</span>{!isInterviewCompleted(2) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L2)</span>}{isInterviewCompleted(3) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(3) && <Clock className="w-4 h-4 text-re-red ml-2 inline" />}</div></SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@ -69,15 +69,12 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
getDocumentsForStage,
|
||||
setPreviewDoc,
|
||||
setShowPreviewModal,
|
||||
flattenedStages,
|
||||
setSelectedStage,
|
||||
uploadDocType,
|
||||
setUploadDocType,
|
||||
setUploadFile,
|
||||
isUploading,
|
||||
handleUpload,
|
||||
uploadFile,
|
||||
documentConfigs,
|
||||
showPreviewModal,
|
||||
previewDoc,
|
||||
showFddFinalizeModal,
|
||||
@ -126,7 +123,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<div className="custom-scrollbar-slim min-h-0 flex-1 overflow-y-auto px-5 py-5">
|
||||
<div className="space-y-6">
|
||||
{ktCriteria.length === 0 && (
|
||||
<div className="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800">
|
||||
KT Matrix configuration is not available. Configure it in Master > Interview Configurations.
|
||||
</div>
|
||||
)}
|
||||
@ -211,7 +208,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
|
||||
<Separator />
|
||||
{l2Fields.length === 0 && (
|
||||
<div className="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800">
|
||||
Level 2 feedback configuration is not available. Configure it in Master > Interview Configurations.
|
||||
</div>
|
||||
)}
|
||||
@ -315,7 +312,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
|
||||
<Separator />
|
||||
{l3Fields.length === 0 && (
|
||||
<div className="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800">
|
||||
Level 3 feedback configuration is not available. Configure it in Master > Interview Configurations.
|
||||
</div>
|
||||
)}
|
||||
@ -371,7 +368,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<Dialog open={showDocumentsModal} onOpenChange={(open) => { setShowDocumentsModal(open); if (!open) setShowUploadForm(false); }}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-2xl md:max-w-3xl lg:max-w-4xl max-h-[90vh] overflow-hidden flex flex-col p-4 sm:p-6" data-testid="onboarding-documents-modal">
|
||||
<DialogHeader className="pb-4">
|
||||
<DialogTitle className="text-xl font-bold flex items-center gap-2"><FileText className="w-5 h-5 text-amber-600" />Documents - {selectedStage || 'General'}</DialogTitle>
|
||||
<DialogTitle className="text-xl font-bold flex items-center gap-2"><FileText className="w-5 h-5 text-re-red" />Documents - {selectedStage || 'General'}</DialogTitle>
|
||||
<DialogDescription className="text-slate-500">View and manage documents uploaded for this stage.</DialogDescription>
|
||||
</DialogHeader>
|
||||
{!showUploadForm ? (
|
||||
@ -398,7 +395,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<TableCell className="text-right py-3">
|
||||
<div className="flex gap-1 justify-end">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-indigo-600 hover:bg-indigo-50 rounded-full" onClick={() => { setPreviewDoc(doc); setShowPreviewModal(true); }} data-testid={`onboarding-document-preview-${index}`}><Eye className="w-4 h-4" /></Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-amber-600 hover:bg-amber-50 rounded-full" onClick={() => { const baseUrl = (import.meta as any).env?.VITE_API_URL || 'http://localhost:5000'; window.open(`${baseUrl}/${doc.filePath}`, '_blank'); }} data-testid={`onboarding-document-download-${index}`}><Download className="w-4 h-4" /></Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-re-red hover:bg-red-50 rounded-full" onClick={() => { const baseUrl = (import.meta as any).env?.VITE_API_URL || 'http://localhost:5000'; window.open(`${baseUrl}/${doc.filePath}`, '_blank'); }} data-testid={`onboarding-document-download-${index}`}><Download className="w-4 h-4" /></Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -410,7 +407,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<div className="flex-1 flex flex-col items-center justify-center py-12 text-center border rounded-lg bg-slate-50/30" data-testid="onboarding-documents-empty"><div className="w-16 h-16 rounded-full bg-slate-100 flex items-center justify-center mb-4"><FileText className="w-8 h-8 text-slate-300" /></div><h3 className="text-slate-900 font-semibold mb-2">No Documents Found</h3><p className="text-slate-600 text-sm max-w-[250px]">No documents have been uploaded for this stage yet.</p></div>
|
||||
)}
|
||||
<div className="flex flex-col sm:flex-row gap-3 pt-2 mt-auto">
|
||||
<Button className="flex-1 bg-amber-600 hover:bg-amber-700 text-white font-bold py-3 sm:py-5 rounded-xl shadow-lg shadow-amber-600/15 transition-all hover:scale-[1.01] active:scale-[0.99]" onClick={() => setShowUploadForm(true)} data-testid="onboarding-documents-upload-button"><Upload className="w-5 h-5 mr-3" />Upload Document</Button>
|
||||
<Button className="flex-1 bg-re-red hover:bg-re-red-hover text-white font-bold py-3 sm:py-5 rounded-xl shadow-lg shadow-re-red/15 transition-all hover:scale-[1.01] active:scale-[0.99]" onClick={() => setShowUploadForm(true)} data-testid="onboarding-documents-upload-button"><Upload className="w-5 h-5 mr-3" />Upload Document</Button>
|
||||
<Button variant="outline" className="flex-1 sm:flex-none py-3 sm:py-5 px-8 rounded-xl border-slate-200 font-semibold text-slate-600 hover:bg-slate-50" onClick={() => setShowDocumentsModal(false)} data-testid="onboarding-documents-close-button">Close</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -425,7 +422,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
placeholder="Enter document name"
|
||||
value={uploadDocType}
|
||||
onChange={(e) => setUploadDocType(e.target.value)}
|
||||
className="bg-white border-slate-200 h-12 rounded-xl focus:ring-amber-500 shadow-sm"
|
||||
className="bg-white border-slate-200 h-12 rounded-xl focus:ring-re-red shadow-sm"
|
||||
data-testid="onboarding-documents-name-input"
|
||||
/>
|
||||
</div>
|
||||
@ -433,7 +430,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<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"
|
||||
className="bg-white border-slate-200 h-12 rounded-xl focus:ring-re-red 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-red-50 file:text-re-red-hover hover:file:bg-red-50 cursor-pointer"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files ? e.target.files[0] : null;
|
||||
setUploadFile(file);
|
||||
@ -449,7 +446,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
</div>
|
||||
<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-1 sm:order-2 bg-amber-600 hover:bg-amber-700 text-white font-bold py-3 sm:py-5 rounded-xl shadow-lg shadow-amber-600/15 transition-all hover:scale-[1.01] active:scale-[0.99]" onClick={async () => { await handleUpload(); setShowUploadForm(false); }} disabled={!uploadFile || !uploadDocType || isUploading} data-testid="onboarding-documents-upload-submit">
|
||||
<Button className="flex-1 order-1 sm:order-2 bg-re-red hover:bg-re-red-hover text-white font-bold py-3 sm:py-5 rounded-xl shadow-lg shadow-re-red/15 transition-all hover:scale-[1.01] active:scale-[0.99]" onClick={async () => { await handleUpload(); setShowUploadForm(false); }} disabled={!uploadFile || !uploadDocType || isUploading} data-testid="onboarding-documents-upload-submit">
|
||||
{isUploading ? <span className="flex items-center gap-2"><div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />Uploading...</span> : <span className="flex items-center gap-2"><Upload className="w-5 h-5" />Confirm Upload</span>}
|
||||
</Button>
|
||||
</div>
|
||||
@ -461,11 +458,11 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
|
||||
<Dialog open={showFddFinalizeModal} onOpenChange={setShowFddFinalizeModal}>
|
||||
<DialogContent className="max-w-md p-0 overflow-hidden border-none shadow-2xl rounded-3xl" data-testid="onboarding-fdd-finalize-modal">
|
||||
<div className="bg-slate-950 p-8 flex items-center justify-center relative overflow-hidden"><div className="absolute inset-0 bg-gradient-to-br from-amber-600/20 to-transparent" /><div className="w-20 h-20 bg-amber-600/20 rounded-full flex items-center justify-center animate-pulse relative z-10 shadow-[0_0_40px_rgba(245,158,11,0.2)]"><ShieldCheck className="w-10 h-10 text-amber-500" /></div></div>
|
||||
<div className="bg-slate-950 p-8 flex items-center justify-center relative overflow-hidden"><div className="absolute inset-0 bg-gradient-to-br from-re-red/20 to-transparent" /><div className="w-20 h-20 bg-re-red/20 rounded-full flex items-center justify-center animate-pulse relative z-10 shadow-[0_0_40px_rgba(218,41,28,0.2)]"><ShieldCheck className="w-10 h-10 text-re-red" /></div></div>
|
||||
<div className="p-8 space-y-6 bg-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-black text-slate-900 text-center tracking-tight">Finalize FDD Audit</DialogTitle>
|
||||
<DialogDescription className="text-slate-500 text-center pt-2 leading-relaxed text-sm font-medium">You are about to submit your final findings. This action will <span className="font-bold text-slate-900 underline decoration-amber-500 decoration-2">lock the audit session</span> and trigger the LOI approval workflow.</DialogDescription>
|
||||
<DialogDescription className="text-slate-500 text-center pt-2 leading-relaxed text-sm font-medium">You are about to submit your final findings. This action will <span className="font-bold text-slate-900 underline decoration-re-red decoration-2">lock the audit session</span> and trigger the LOI approval workflow.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
{(currentUser?.role !== 'FDD' && currentUser?.roleCode !== 'FDD') && (
|
||||
@ -473,7 +470,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<Label className="text-[10px] font-black uppercase tracking-widest text-slate-400">Auditor Recommendation <span className="text-red-500">*</span></Label>
|
||||
<div className="flex gap-2">
|
||||
{['Recommended', 'Qualified with Observations', 'Not Recommended'].map((rec) => (
|
||||
<Button key={rec} variant={fddAuditRecommendation === rec ? 'default' : 'outline'} className={cn("flex-1 h-10 font-bold text-[9px] uppercase tracking-wider rounded-xl transition-all", fddAuditRecommendation === rec && rec === 'Recommended' && "bg-emerald-600 hover:bg-emerald-700", fddAuditRecommendation === rec && rec === 'Qualified with Observations' && "bg-amber-500 hover:bg-amber-600", fddAuditRecommendation === rec && rec === 'Not Recommended' && "bg-red-600 hover:bg-red-700")} onClick={() => setFddAuditRecommendation(rec)} data-testid={`onboarding-fdd-recommendation-${rec.replace(/\s+/g, '-').toLowerCase()}`}>{rec}</Button>
|
||||
<Button key={rec} variant={fddAuditRecommendation === rec ? 'default' : 'outline'} className={cn("flex-1 h-10 font-bold text-[9px] uppercase tracking-wider rounded-xl transition-all", fddAuditRecommendation === rec && rec === 'Recommended' && "bg-emerald-600 hover:bg-emerald-700", fddAuditRecommendation === rec && rec === 'Qualified with Observations' && "bg-red-500 hover:bg-re-red", fddAuditRecommendation === rec && rec === 'Not Recommended' && "bg-red-600 hover:bg-red-700")} onClick={() => setFddAuditRecommendation(rec)} data-testid={`onboarding-fdd-recommendation-${rec.replace(/\s+/g, '-').toLowerCase()}`}>{rec}</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@ -482,18 +479,18 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
<Label className="text-[10px] font-black uppercase tracking-widest text-slate-400">Findings Summary</Label>
|
||||
<Textarea
|
||||
placeholder="Summarize key financial findings or discrepancies..."
|
||||
className="min-h-[100px] rounded-xl border-slate-200 focus:ring-amber-500 text-sm"
|
||||
className="min-h-[100px] rounded-xl border-slate-200 focus:ring-re-red text-sm"
|
||||
value={fddAuditFindings}
|
||||
onChange={(e) => setFddAuditFindings(e.target.value)}
|
||||
data-testid="onboarding-fdd-findings-textarea"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-amber-50 p-4 rounded-2xl flex gap-3 border border-amber-100"><Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" /><p className="text-[11px] text-amber-800 font-medium italic">Ensure the final PDF report is uploaded first. This satisfies the FDD statutory requirement.</p></div>
|
||||
<div className="bg-red-50 p-4 rounded-2xl flex gap-3 border border-red-100"><Info className="w-5 h-5 text-re-red shrink-0 mt-0.5" /><p className="text-[11px] text-red-800 font-medium italic">Ensure the final PDF report is uploaded first. This satisfies the FDD statutory requirement.</p></div>
|
||||
<div className="flex flex-col sm:flex-row gap-3 pt-2">
|
||||
<Button variant="outline" className="w-full sm:flex-1 h-12 rounded-2xl font-bold text-slate-600 hover:bg-slate-50 border-slate-200" onClick={() => setShowFddFinalizeModal(false)} disabled={isFinalizingFdd} data-testid="onboarding-fdd-finalize-cancel">Cancel</Button>
|
||||
<Button
|
||||
className="w-full sm:flex-1 h-12 rounded-2xl font-bold bg-slate-950 hover:bg-slate-900 text-white shadow-lg shadow-slate-200 transition-all active:scale-95 border-b-4 border-amber-500"
|
||||
className="w-full sm:flex-1 h-12 rounded-2xl font-bold bg-slate-950 hover:bg-slate-900 text-white shadow-lg shadow-slate-200 transition-all active:scale-95 border-b-4 border-re-red"
|
||||
disabled={isFinalizingFdd || !fddAuditFindings}
|
||||
data-testid="onboarding-fdd-finalize-submit"
|
||||
onClick={async () => {
|
||||
@ -569,16 +566,16 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
|
||||
<Dialog open={showFirmTypeModal} onOpenChange={setShowFirmTypeModal}>
|
||||
<DialogContent className="max-w-md p-0 overflow-hidden rounded-3xl border-none shadow-2xl" data-testid="onboarding-firm-type-modal">
|
||||
<div className="bg-amber-600 p-8 text-white">
|
||||
<div className="bg-re-red p-8 text-white">
|
||||
<div className="w-16 h-16 rounded-2xl bg-white/20 flex items-center justify-center mb-6 backdrop-blur-sm border border-white/30 shadow-inner"><Building2 className="w-8 h-8 text-white" /></div>
|
||||
<h3 className="text-2xl font-black tracking-tight mb-2">Update Firm Type</h3>
|
||||
<p className="text-amber-100/80 text-sm font-medium leading-relaxed">Select the proposed legal constitution for this dealership application.</p>
|
||||
<p className="text-red-100/80 text-sm font-medium leading-relaxed">Select the proposed legal constitution for this dealership application.</p>
|
||||
</div>
|
||||
<div className="p-8 space-y-6 bg-white">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-[10px] text-slate-400 uppercase tracking-widest font-black">Proposed Legal Constitution <span className="text-red-500">*</span></Label>
|
||||
<Select value={tempFirmType} onValueChange={setTempFirmType}>
|
||||
<SelectTrigger className="h-12 rounded-xl border-slate-200 focus:ring-amber-500" data-testid="onboarding-firm-type-select"><SelectValue placeholder="Select Firm Type" /></SelectTrigger>
|
||||
<SelectTrigger className="h-12 rounded-xl border-slate-200 focus:ring-re-red" data-testid="onboarding-firm-type-select"><SelectValue placeholder="Select Firm Type" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Proprietorship" data-testid="onboarding-firm-type-proprietorship">Proprietorship</SelectItem>
|
||||
<SelectItem value="Partnership" data-testid="onboarding-firm-type-partnership">Partnership</SelectItem>
|
||||
@ -590,7 +587,7 @@ export function ApplicationDetailsExtendedModals(props: ApplicationDetailsExtend
|
||||
</div>
|
||||
<div className="flex gap-3 pt-2">
|
||||
<Button variant="outline" className="flex-1 h-12 rounded-xl font-bold text-slate-600 border-slate-200" onClick={() => setShowFirmTypeModal(false)} disabled={updatingFirmType} data-testid="onboarding-firm-type-cancel">Cancel</Button>
|
||||
<Button className="flex-1 h-12 rounded-xl font-bold bg-amber-600 hover:bg-amber-700 text-white shadow-lg shadow-amber-200 transition-all active:scale-95" disabled={updatingFirmType || !tempFirmType} onClick={handleUpdateFirmType} data-testid="onboarding-firm-type-submit">{updatingFirmType ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Update Type'}</Button>
|
||||
<Button className="flex-1 h-12 rounded-xl font-bold bg-re-red hover:bg-re-red-hover text-white shadow-lg shadow-red-200 transition-all active:scale-95" disabled={updatingFirmType || !tempFirmType} onClick={handleUpdateFirmType} data-testid="onboarding-firm-type-submit">{updatingFirmType ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Update Type'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AlertCircle, CheckCircle, ClipboardList, Download, Eye, FileText, ShieldAlert, ShieldCheck, Upload } from 'lucide-react';
|
||||
import { AlertCircle, CheckCircle, ClipboardList, Download, Eye, FileText, ShieldCheck, Upload } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { onboardingService } from '@/services/onboarding.service';
|
||||
import { cn, formatDateTime } from '@/components/ui/utils';
|
||||
@ -8,13 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
interface Props {
|
||||
application: any;
|
||||
currentUser: any;
|
||||
documents: any[];
|
||||
fddAgencies: any[];
|
||||
selectedAgencyId: string;
|
||||
setSelectedAgencyId: (v: string) => void;
|
||||
isAssigningAgency: boolean;
|
||||
handleAssignAgency: () => void;
|
||||
setPreviewDoc: (d: any) => void;
|
||||
setShowPreviewModal: (v: boolean) => void;
|
||||
setIsUploading: (v: boolean) => void;
|
||||
@ -24,13 +18,7 @@ interface Props {
|
||||
|
||||
export function ApplicationDetailsFddAuditContent({
|
||||
application,
|
||||
currentUser,
|
||||
documents,
|
||||
fddAgencies,
|
||||
selectedAgencyId,
|
||||
setSelectedAgencyId,
|
||||
isAssigningAgency,
|
||||
handleAssignAgency,
|
||||
setPreviewDoc,
|
||||
setShowPreviewModal,
|
||||
setIsUploading,
|
||||
@ -82,47 +70,6 @@ export function ApplicationDetailsFddAuditContent({
|
||||
The Financial Due Diligence process has not been initiated for this application yet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{(currentUser?.role === 'DD Admin' || currentUser?.role === 'Super Admin') && (
|
||||
<Card className="border-amber-100 bg-amber-50/30 overflow-hidden rounded-2xl" data-testid="onboarding-fdd-initiate-card">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-xs font-black uppercase tracking-widest text-amber-800 flex items-center gap-2">
|
||||
<ShieldAlert className="w-4 h-4" />
|
||||
Initiate FDD Audit
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<label className="text-[10px] font-black uppercase tracking-widest text-slate-400 mb-1.5 block">Select FDD Agency <span className="text-red-500">*</span></label>
|
||||
<select
|
||||
className="w-full h-11 bg-white border border-slate-200 rounded-xl px-4 text-sm font-medium focus:ring-2 focus:ring-amber-500/20 focus:border-amber-500 outline-none transition-all shadow-sm"
|
||||
value={selectedAgencyId}
|
||||
onChange={(e) => setSelectedAgencyId(e.target.value)}
|
||||
data-testid="onboarding-fdd-agency-select"
|
||||
>
|
||||
<option value="">Choose partner agency...</option>
|
||||
{(fddAgencies || []).map((agency: any) => (
|
||||
<option key={agency.id} value={agency.id} data-testid={`onboarding-fdd-agency-option-${agency.id}`}>
|
||||
{agency.fullName || agency.name} ({agency.email})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
className="bg-slate-900 text-white hover:bg-slate-800 font-black text-[10px] uppercase tracking-widest px-8 h-11 border-none shadow-lg shadow-slate-900/10 transition-all active:scale-[0.98]"
|
||||
onClick={handleAssignAgency}
|
||||
disabled={isAssigningAgency || !selectedAgencyId}
|
||||
data-testid="onboarding-fdd-assign-button"
|
||||
>
|
||||
{isAssigningAgency ? 'Assigning...' : 'Assign & Start Audit'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -132,8 +79,8 @@ export function ApplicationDetailsFddAuditContent({
|
||||
{hasAssignment && (
|
||||
<div className="flex items-center justify-between p-4 bg-slate-50 border border-slate-200 rounded-xl mb-6" data-testid="onboarding-fdd-assignment-banner">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-amber-100 rounded-lg">
|
||||
<ShieldCheck className="w-5 h-5 text-amber-600" />
|
||||
<div className="p-2 bg-red-50 rounded-lg">
|
||||
<ShieldCheck className="w-5 h-5 text-re-red" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-slate-900">FDD Assignment Active</h4>
|
||||
@ -230,7 +177,7 @@ export function ApplicationDetailsFddAuditContent({
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4" data-testid="onboarding-fdd-support-docs-grid">
|
||||
{(documents || []).filter(isFddSupportDoc).map((doc: any, index: number) => (
|
||||
<div key={doc.id} className="group bg-white border border-slate-200 rounded-xl p-4 flex items-center justify-between hover:border-amber-400 transition-all hover:shadow-md" data-testid={`onboarding-fdd-support-doc-${index}`}>
|
||||
<div key={doc.id} className="group bg-white border border-slate-200 rounded-xl p-4 flex items-center justify-between hover:border-red-300 transition-all hover:shadow-md" data-testid={`onboarding-fdd-support-doc-${index}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-slate-50 flex items-center justify-center"><FileText className="w-5 h-5 text-slate-400" /></div>
|
||||
<div className="overflow-hidden">
|
||||
@ -239,13 +186,13 @@ export function ApplicationDetailsFddAuditContent({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-amber-600 hover:bg-amber-50" onClick={() => {
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-re-red hover:bg-red-50" onClick={() => {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';
|
||||
window.open(`${baseUrl}/${doc.filePath}`, '_blank');
|
||||
}} data-testid={`onboarding-fdd-support-doc-download-${index}`}>
|
||||
<Download className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-amber-600 hover:bg-amber-50" onClick={() => { setPreviewDoc(doc); setShowPreviewModal(true); }} data-testid={`onboarding-fdd-support-doc-preview-${index}`}>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-re-red hover:bg-red-50" onClick={() => { setPreviewDoc(doc); setShowPreviewModal(true); }} data-testid={`onboarding-fdd-support-doc-preview-${index}`}>
|
||||
<Eye className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -72,7 +72,7 @@ export function ApplicationDetailsHeader({
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="relative hover:bg-amber-50 hover:border-amber-300 hover:text-amber-700 transition-all shadow-sm"
|
||||
className="relative hover:bg-red-50 hover:border-red-300 hover:text-re-red-hover transition-all shadow-sm"
|
||||
onClick={onOpenWorknotes}
|
||||
data-testid="onboarding-details-view-work-notes"
|
||||
>
|
||||
|
||||
@ -16,6 +16,10 @@ import {
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { cn, formatDateTime } from '@/components/ui/utils';
|
||||
import {
|
||||
getRequestStatusBadgeSolidClass,
|
||||
getStatusProgressBarClass,
|
||||
} from '@/lib/statusProgressTheme';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -128,13 +132,8 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-slate-600">Current Status</p>
|
||||
<Badge
|
||||
className={cn(
|
||||
"mt-1",
|
||||
application.status === 'Onboarded' ? "bg-green-600 hover:bg-green-700 text-white" :
|
||||
application.status === 'Rejected' ? "bg-red-600" :
|
||||
"bg-amber-600"
|
||||
)}
|
||||
<Badge
|
||||
className={cn('mt-1', getRequestStatusBadgeSolidClass(application.status))}
|
||||
data-testid="onboarding-details-summary-status"
|
||||
>
|
||||
{application.status}
|
||||
@ -152,7 +151,12 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
<div>
|
||||
<p className="text-slate-600">Progress</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<Progress value={application.progress} className="flex-1" data-testid="onboarding-details-summary-progress-bar" />
|
||||
<Progress
|
||||
value={application.progress}
|
||||
className="flex-1 bg-red-50"
|
||||
indicatorClassName={getStatusProgressBarClass(application.status)}
|
||||
data-testid="onboarding-details-summary-progress-bar"
|
||||
/>
|
||||
<span className="text-slate-900" data-testid="onboarding-details-summary-progress-text">{application.progress}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,10 +176,10 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{permissions.isLoaLocked && (
|
||||
<Alert variant="destructive" className="mb-4 bg-amber-50 border-amber-200 text-amber-800" data-testid="onboarding-details-loa-locked-alert">
|
||||
<Lock className="w-4 h-4 text-amber-600" />
|
||||
<AlertTitle className="text-amber-900 font-semibold">LOA approval locked</AlertTitle>
|
||||
<AlertDescription className="text-amber-800">
|
||||
<Alert variant="destructive" className="mb-4 bg-red-50 border-red-200 text-red-800" data-testid="onboarding-details-loa-locked-alert">
|
||||
<Lock className="w-4 h-4 text-re-red" />
|
||||
<AlertTitle className="text-red-900 font-semibold">LOA approval locked</AlertTitle>
|
||||
<AlertDescription className="text-red-800">
|
||||
<span className="font-medium">First Fill</span> (later-stage payment) must be verified by Finance
|
||||
before LOA approval can proceed. This is separate from the initial Security Deposit before LOI Issued.
|
||||
</AlertDescription>
|
||||
@ -201,10 +205,10 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
)}
|
||||
|
||||
{permissions.isSecurityDetailsLocked && (
|
||||
<Alert variant="destructive" className="mb-4 bg-amber-50 border-amber-200 text-amber-800" data-testid="onboarding-details-security-locked-alert">
|
||||
<Lock className="w-4 h-4 text-amber-600" />
|
||||
<AlertTitle className="text-amber-900 font-semibold">Security Deposit approval locked</AlertTitle>
|
||||
<AlertDescription className="text-amber-800">
|
||||
<Alert variant="destructive" className="mb-4 bg-red-50 border-red-200 text-red-800" data-testid="onboarding-details-security-locked-alert">
|
||||
<Lock className="w-4 h-4 text-re-red" />
|
||||
<AlertTitle className="text-red-900 font-semibold">Security Deposit approval locked</AlertTitle>
|
||||
<AlertDescription className="text-red-800">
|
||||
Finance must verify the <span className="font-medium">Security Deposit</span> before this stage can be approved.
|
||||
You can still use <span className="font-medium">Reject</span> if needed.
|
||||
</AlertDescription>
|
||||
@ -234,10 +238,10 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
)}
|
||||
|
||||
{isAdmin && (application.status === 'Level 3 Approved' || application.status === 'FDD Verification') && (!application.fddAssignments || application.fddAssignments.length === 0) && (
|
||||
<Alert className="mb-4 bg-amber-50 border-amber-200 text-amber-800" data-testid="onboarding-details-fdd-assignment-alert">
|
||||
<AlertCircle className="w-4 h-4 text-amber-600" />
|
||||
<AlertTitle className="text-amber-900 font-bold">FDD Assignment Required</AlertTitle>
|
||||
<AlertDescription className="text-amber-800 font-medium">
|
||||
<Alert className="mb-4 bg-red-50 border-red-200 text-red-800" data-testid="onboarding-details-fdd-assignment-alert">
|
||||
<AlertCircle className="w-4 h-4 text-re-red" />
|
||||
<AlertTitle className="text-red-900 font-bold">FDD Assignment Required</AlertTitle>
|
||||
<AlertDescription className="text-red-800 font-medium">
|
||||
This application is pending financial due diligence. Please assign an FDD Agency to proceed with the audit.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@ -341,7 +345,7 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
</Select>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full bg-amber-600 hover:bg-amber-700 font-bold h-11"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover font-bold h-11"
|
||||
onClick={handleAssignAgency}
|
||||
disabled={isAssigningAgency || !selectedAgencyId}
|
||||
data-testid="onboarding-details-assign-fdd-submit"
|
||||
@ -460,7 +464,7 @@ export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps)
|
||||
</Select>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full bg-amber-600 hover:bg-amber-700 font-bold h-11"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover font-bold h-11"
|
||||
onClick={handleAddParticipant}
|
||||
disabled={isAssigningParticipant}
|
||||
data-testid="onboarding-details-assign-user-submit"
|
||||
|
||||
@ -19,6 +19,12 @@ import {
|
||||
User,
|
||||
} from 'lucide-react';
|
||||
import { cn, formatDateTime } from '@/components/ui/utils';
|
||||
import {
|
||||
getPercentProgressBadgeSolidClass,
|
||||
getPercentProgressBarClass,
|
||||
getStatusProgressBadgeSolidClass,
|
||||
getStatusProgressBarClass,
|
||||
} from '@/lib/statusProgressTheme';
|
||||
import QuestionnaireResponseView from '../QuestionnaireResponseView';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@ -149,12 +155,17 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-slate-900">Application Journey</h3>
|
||||
<Badge className="bg-amber-600" data-testid="onboarding-progress-percentage-badge">{application.progress}% Complete</Badge>
|
||||
<Badge className={getStatusProgressBadgeSolidClass(application.status)} data-testid="onboarding-progress-percentage-badge">{application.progress}% Complete</Badge>
|
||||
</div>
|
||||
<Progress value={application.progress} className="h-3 mb-6" data-testid="onboarding-progress-bar" />
|
||||
<Progress
|
||||
value={application.progress}
|
||||
className="h-3 mb-6 bg-status-progress-soft"
|
||||
indicatorClassName={getStatusProgressBarClass(application.status)}
|
||||
data-testid="onboarding-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative" data-testid="onboarding-progress-stages-container">
|
||||
<div className="relative status-progress-ui" data-testid="onboarding-progress-stages-container">
|
||||
{(() => {
|
||||
const interviewRoleMap: Record<number, string[]> = {
|
||||
1: ['DD-ZM', 'RBM'],
|
||||
@ -236,7 +247,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
|
||||
<div className={cn(
|
||||
"absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full border border-white",
|
||||
approver.status === 'approved' ? "bg-green-500" : approver.status === 'rejected' ? "bg-red-500" : "bg-amber-400"
|
||||
approver.status === 'approved' ? "bg-green-500" : approver.status === 'rejected' ? "bg-red-500" : "bg-status-progress"
|
||||
)} data-testid={`onboarding-stage-approver-status-dot-${stageIndex}-${i}`} />
|
||||
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-slate-900 text-white text-[10px] rounded opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
||||
@ -255,7 +266,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center border-2 z-10 relative ${stage.status === 'completed'
|
||||
? 'bg-green-500 border-green-500 text-white shadow-md'
|
||||
: stage.status === 'active'
|
||||
? stage.isLocked ? 'bg-slate-400 border-slate-400 text-white' : 'bg-amber-500 border-amber-500 text-white animate-pulse-subtle'
|
||||
? stage.isLocked ? 'bg-slate-400 border-slate-400 text-white' : 'bg-status-workflow-active border-status-workflow-active text-white animate-pulse-subtle'
|
||||
: 'bg-white border-slate-300 text-slate-400 shadow-none'
|
||||
}`} data-testid={`onboarding-progress-stage-icon-${index}`}>
|
||||
{stage.isParallel ? (
|
||||
@ -265,7 +276,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Lock className="w-5 h-5 text-white cursor-help" />
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-1.5 bg-slate-900 text-white text-[10px] rounded shadow-xl opacity-0 group-hover:opacity-100 pointer-events-none transition-all duration-200 whitespace-nowrap z-[100] border border-slate-700">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-bold text-amber-400 flex items-center gap-1">
|
||||
<span className="font-bold text-status-progress flex items-center gap-1">
|
||||
<AlertCircle className="w-3 h-3" /> Stage Locked
|
||||
</span>
|
||||
<span>{stage.lockMessage}</span>
|
||||
@ -293,7 +304,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<div className="flex-1 pt-1">
|
||||
<p className={cn(
|
||||
"font-bold transition-colors",
|
||||
stage.status === 'completed' ? "text-green-700" : stage.status === 'active' ? "text-amber-700" : "text-slate-900"
|
||||
stage.status === 'completed' ? "text-green-700" : stage.status === 'active' ? "text-status-progress-muted" : "text-slate-900"
|
||||
)} data-testid={`onboarding-progress-stage-name-${index}`}>{stage.name}</p>
|
||||
{stage.description && (
|
||||
<p className="text-slate-600 text-sm mt-0.5 leading-relaxed" data-testid={`onboarding-progress-stage-desc-${index}`}>{stage.description}</p>
|
||||
@ -302,7 +313,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
{renderApprovers(stage.name as string, index)}
|
||||
|
||||
{stage.evaluators && stage.evaluators.length > 0 && !['LOI Approval', 'LOA', '1st Level Interview', '2nd Level Interview', '3rd Level Interview'].includes(stage.name as string) && (
|
||||
<p className="text-amber-600 text-xs mt-1.5 flex items-center gap-1 bg-amber-50 w-fit px-2 py-0.5 rounded border border-amber-100" data-testid={`onboarding-progress-stage-evaluators-${index}`}>
|
||||
<p className="text-status-progress text-xs mt-1.5 flex items-center gap-1 bg-status-progress-soft w-fit px-2 py-0.5 rounded border border-status-progress" data-testid={`onboarding-progress-stage-evaluators-${index}`}>
|
||||
<User className="w-3 h-3" />
|
||||
Evaluators: {stage.evaluators.join(' + ')}
|
||||
</p>
|
||||
@ -336,8 +347,8 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
if (expectedCount && actualCount < expectedCount && application.status !== 'Rejected' && isEligibleForWarning) {
|
||||
return (
|
||||
<div className="mt-2" data-testid={`onboarding-progress-stage-warning-${index}`}>
|
||||
<Alert variant="destructive" className="py-2 px-3 border-amber-200 bg-amber-50 text-amber-800">
|
||||
<AlertCircle className="h-4 w-4 text-amber-600" />
|
||||
<Alert variant="destructive" className="py-2 px-3 border-red-200 bg-red-50 text-red-800">
|
||||
<AlertCircle className="h-4 w-4 text-re-red" />
|
||||
<AlertTitle className="text-xs font-semibold">Missing Evaluators</AlertTitle>
|
||||
<AlertDescription className="text-xs">
|
||||
{actualCount === 0
|
||||
@ -347,7 +358,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="h-auto p-0 ml-1 text-xs text-amber-700 underline"
|
||||
className="h-auto p-0 ml-1 text-xs text-re-red-hover underline"
|
||||
onClick={handleRetriggerEvaluators}
|
||||
data-testid={`onboarding-progress-stage-retrigger-${index}`}
|
||||
>
|
||||
@ -460,7 +471,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${isDone
|
||||
? `${branchColor === 'blue' ? 'bg-blue-500 border-blue-500' : 'bg-green-500 border-green-500'}`
|
||||
: branchStage.status === 'active'
|
||||
? 'bg-amber-500 border-amber-500 text-white shadow-sm'
|
||||
? 'bg-status-workflow-active border-status-workflow-active text-white shadow-sm'
|
||||
: 'bg-white border-slate-300 text-slate-400'
|
||||
}`} data-testid={`onboarding-progress-branch-stage-icon-${branchKey}-${bsIdx}`}>
|
||||
{isDone ? (
|
||||
@ -531,7 +542,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<TabsContent value="documents" className="space-y-4" data-testid="onboarding-tab-content-documents">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-slate-900">Uploaded Documents</h3>
|
||||
<Button size="sm" className="bg-amber-600 hover:bg-amber-700" data-testid="onboarding-documents-upload-tab-button" onClick={() => {
|
||||
<Button size="sm" className="bg-re-red hover:bg-re-red-hover" data-testid="onboarding-documents-upload-tab-button" onClick={() => {
|
||||
setSelectedStage(null);
|
||||
setShowDocumentsModal(true);
|
||||
setShowUploadForm(true);
|
||||
@ -763,12 +774,17 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
{renderFddAuditContent()}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="eor" className="space-y-4" data-testid="onboarding-tab-content-eor">
|
||||
<TabsContent value="eor" className="space-y-4 status-progress-ui" data-testid="onboarding-tab-content-eor">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-slate-900">Essential Operating Requirements</h3>
|
||||
<Badge className="bg-amber-600" data-testid="onboarding-eor-progress-badge">{Math.round(eorProgress)}% Complete</Badge>
|
||||
<Badge className={getPercentProgressBadgeSolidClass(eorProgress)} data-testid="onboarding-eor-progress-badge">{Math.round(eorProgress)}% Complete</Badge>
|
||||
</div>
|
||||
<Progress value={eorProgress} className="h-3 mb-6" data-testid="onboarding-eor-progress-bar" />
|
||||
<Progress
|
||||
value={eorProgress}
|
||||
className="h-3 mb-6 bg-status-progress-soft"
|
||||
indicatorClassName={getPercentProgressBarClass(eorProgress)}
|
||||
data-testid="onboarding-eor-progress-bar"
|
||||
/>
|
||||
|
||||
<div className="space-y-3" data-testid="onboarding-eor-checklist">
|
||||
{(eorData?.items || eorChecklist).map((item: any, idx: number) => {
|
||||
@ -803,7 +819,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
{docType}
|
||||
</span>
|
||||
{hasDocument && !item.isCompliant && (
|
||||
<Badge variant="outline" className="text-[10px] h-4 px-1.5 bg-amber-50 text-amber-600 border-amber-200 uppercase tracking-wider font-bold">
|
||||
<Badge variant="outline" className="text-[10px] h-4 px-1.5 bg-red-50 text-re-red border-red-200 uppercase tracking-wider font-bold">
|
||||
Needs Verification
|
||||
</Badge>
|
||||
)}
|
||||
@ -864,7 +880,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
)}
|
||||
|
||||
{!hasDocument && (
|
||||
<div className="p-2 text-slate-300 group-hover:text-amber-500 transition-colors" data-testid={`onboarding-eor-upload-hint-${idx}`}>
|
||||
<div className="p-2 text-slate-300 group-hover:text-re-red transition-colors" data-testid={`onboarding-eor-upload-hint-${idx}`}>
|
||||
<Upload className="w-4 h-4" />
|
||||
</div>
|
||||
)}
|
||||
@ -929,12 +945,12 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Card className={cn(
|
||||
"border-l-4",
|
||||
deposit?.status === 'Verified' ? "border-l-green-500" :
|
||||
deposit?.status === 'Rejected' ? "border-l-red-500" : "border-l-amber-500"
|
||||
deposit?.status === 'Rejected' ? "border-l-red-500" : "border-l-re-red"
|
||||
)} data-testid="onboarding-payment-card-security">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded bg-amber-50 flex items-center justify-center text-amber-600">
|
||||
<div className="w-8 h-8 rounded bg-red-50 flex items-center justify-center text-re-red">
|
||||
<ClipboardList className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="font-semibold text-slate-700">Security Deposit</span>
|
||||
@ -942,7 +958,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Badge className={cn(
|
||||
deposit?.status === 'Verified' ? "bg-green-100 text-green-700 hover:bg-green-100" :
|
||||
deposit?.status === 'Rejected' ? "bg-red-100 text-red-700 hover:bg-red-100" :
|
||||
"bg-amber-100 text-amber-700 hover:bg-amber-100"
|
||||
"bg-red-50 text-re-red-hover hover:bg-red-50"
|
||||
)} data-testid="onboarding-payment-status-security">
|
||||
{deposit?.status || 'Awaiting'}
|
||||
</Badge>
|
||||
@ -983,7 +999,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-[10px] text-amber-600 hover:text-amber-700 hover:bg-amber-50"
|
||||
className="h-6 px-2 text-[10px] text-re-red hover:text-re-red-hover hover:bg-red-50"
|
||||
data-testid={`onboarding-payment-doc-view-security-${idx}`}
|
||||
onClick={() => { setPreviewDoc(doc); setShowPreviewModal(true); }}
|
||||
>
|
||||
@ -1011,7 +1027,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Card className={cn(
|
||||
"border-l-4",
|
||||
deposit?.status === 'Verified' ? "border-l-green-500" :
|
||||
deposit?.status === 'Rejected' ? "border-l-red-500" : "border-l-amber-500"
|
||||
deposit?.status === 'Rejected' ? "border-l-red-500" : "border-l-re-red"
|
||||
)} data-testid="onboarding-payment-card-first-fill">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
@ -1024,7 +1040,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<Badge className={cn(
|
||||
deposit?.status === 'Verified' ? "bg-green-100 text-green-700 hover:bg-green-100" :
|
||||
deposit?.status === 'Rejected' ? "bg-red-100 text-red-700 hover:bg-red-100" :
|
||||
"bg-amber-100 text-amber-700 hover:bg-amber-100"
|
||||
"bg-red-50 text-re-red-hover hover:bg-red-50"
|
||||
)} data-testid="onboarding-payment-status-first-fill">
|
||||
{deposit?.status || 'Awaiting'}
|
||||
</Badge>
|
||||
@ -1091,7 +1107,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
<div className="space-y-2.5 p-3 pr-4" data-testid="onboarding-audit-logs-container">
|
||||
{auditLoading ? (
|
||||
<div className="flex items-center justify-center py-10" data-testid="onboarding-audit-loading">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-amber-600" />
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-re-red" />
|
||||
<span className="ml-2 text-sm text-slate-500">Loading audit trail…</span>
|
||||
</div>
|
||||
) : auditLogs.length === 0 ? (
|
||||
|
||||
@ -171,6 +171,6 @@ export function auditLogActionBadgeClass(action: string): string {
|
||||
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';
|
||||
if (a.includes('FDD') || a.includes('QUESTIONNAIRE') || a.includes('INTERVIEW')) return 'border-red-200 bg-red-50/80 text-red-900';
|
||||
return 'border-slate-200 bg-slate-50 text-slate-700';
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ApplicationProgressBar } from '@/features/onboarding/components/ApplicationProgressBar';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
@ -215,7 +215,7 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
'In Review': 'bg-slate-100 text-slate-800',
|
||||
'Level 3 Approved': 'bg-green-100 text-green-800',
|
||||
'FDD Verification': 'bg-indigo-100 text-indigo-800',
|
||||
'Payment Pending': 'bg-amber-100 text-amber-800',
|
||||
'Payment Pending': 'bg-red-50 text-red-800',
|
||||
'LOI In Progress': 'bg-sky-100 text-sky-800',
|
||||
'LOI Issued': 'bg-sky-100 text-sky-800',
|
||||
'Dealer Code Generation': 'bg-purple-100 text-purple-800',
|
||||
@ -236,19 +236,19 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
'EOR In Progress': 'bg-violet-100 text-violet-800',
|
||||
'EOR Complete': 'bg-violet-100 text-violet-800',
|
||||
'LOA Pending': 'bg-pink-100 text-pink-800',
|
||||
'Inauguration': 'bg-amber-100 text-amber-800',
|
||||
'Inauguration': 'bg-red-50 text-red-800',
|
||||
'Approved': 'bg-green-100 text-green-800',
|
||||
'Rejected': 'bg-red-100 text-red-800',
|
||||
'Disqualified': 'bg-gray-100 text-gray-800',
|
||||
'Onboarded': 'bg-emerald-100 text-emerald-800',
|
||||
'LOI Approved': 'bg-sky-100 text-sky-800',
|
||||
'Security Deposit In Progress': 'bg-amber-100 text-amber-800',
|
||||
'Security Deposit In Progress': 'bg-red-50 text-red-800',
|
||||
'Security Deposit Approved': 'bg-green-100 text-green-800',
|
||||
'Security Deposit': 'bg-amber-100 text-amber-800',
|
||||
'Security Deposit': 'bg-red-50 text-red-800',
|
||||
/** Legacy overallStatus until DB migrated */
|
||||
'Security Details In Progress': 'bg-amber-100 text-amber-800',
|
||||
'Security Details In Progress': 'bg-red-50 text-red-800',
|
||||
'Security Details Approved': 'bg-green-100 text-green-800',
|
||||
'Security Details': 'bg-amber-100 text-amber-800',
|
||||
'Security Details': 'bg-red-50 text-red-800',
|
||||
'LOA Issued': 'bg-pink-100 text-pink-800',
|
||||
};
|
||||
return colors[status] || 'bg-gray-100 text-gray-800';
|
||||
@ -257,12 +257,12 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Info Banner */}
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4" data-testid="onboarding-all-apps-banner">
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4" data-testid="onboarding-all-apps-banner">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<AlertCircle className="w-5 h-5 text-re-red flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="text-amber-900 mb-1">DD Workflow - Initial Application Review</h3>
|
||||
<p className="text-amber-800">
|
||||
<h3 className="text-red-900 mb-1">DD Workflow - Initial Application Review</h3>
|
||||
<p className="text-red-800">
|
||||
This page shows <strong>only applications that haven't been shortlisted yet</strong>. Review and select promising candidates using the <strong>Shortlist</strong> button.
|
||||
Once shortlisted, applications will be removed from here and moved to the <strong>Dealership Requests</strong> page for further processing.
|
||||
</p>
|
||||
@ -331,7 +331,7 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
variant={viewMode === 'grid' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={viewMode === 'grid' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={viewMode === 'grid' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
data-testid="onboarding-all-apps-grid-view-btn"
|
||||
>
|
||||
<Grid3x3 className="w-4 h-4 mr-2" />
|
||||
@ -341,7 +341,7 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
variant={viewMode === 'table' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('table')}
|
||||
className={viewMode === 'table' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={viewMode === 'table' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
data-testid="onboarding-all-apps-table-view-btn"
|
||||
>
|
||||
<List className="w-4 h-4 mr-2" />
|
||||
@ -380,7 +380,7 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
{/* Applications Grid/Table */}
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center h-96 bg-white rounded-lg border border-slate-200">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-re-red" />
|
||||
</div>
|
||||
) : viewMode === 'grid' ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" data-testid="onboarding-all-apps-grid-container">
|
||||
@ -465,8 +465,12 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Progress value={app.progress} className="w-20" data-testid={`onboarding-all-apps-progress-bar-${idx}`} />
|
||||
<span className="text-slate-600" data-testid={`onboarding-all-apps-progress-text-${idx}`}>{app.progress}%</span>
|
||||
<ApplicationProgressBar
|
||||
value={app.progress}
|
||||
status={app.status}
|
||||
showPercent
|
||||
data-testid={`onboarding-all-apps-progress-bar-${idx}`}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@ -283,7 +283,6 @@ export const ApplicationDetails = () => {
|
||||
maybeFetchUsersForModal,
|
||||
handleScheduleInterview,
|
||||
handleRescheduleInterview,
|
||||
handleCancelInterview,
|
||||
handleConfirmCancelInterview,
|
||||
handleUpload,
|
||||
handleApprove,
|
||||
@ -368,7 +367,7 @@ export const ApplicationDetails = () => {
|
||||
if (loading && !application) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<Loader2 className="w-10 h-10 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-10 h-10 animate-spin text-re-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -405,13 +404,7 @@ export const ApplicationDetails = () => {
|
||||
const renderFddAuditContent = () => (
|
||||
<ApplicationDetailsFddAuditContent
|
||||
application={application}
|
||||
currentUser={currentUser}
|
||||
documents={documents}
|
||||
fddAgencies={fddAgencies}
|
||||
selectedAgencyId={selectedAgencyId}
|
||||
setSelectedAgencyId={setSelectedAgencyId}
|
||||
isAssigningAgency={isAssigningAgency}
|
||||
handleAssignAgency={handleAssignAgency}
|
||||
setPreviewDoc={setPreviewDoc}
|
||||
setShowPreviewModal={setShowPreviewModal}
|
||||
setIsUploading={setIsUploading}
|
||||
@ -469,7 +462,6 @@ export const ApplicationDetails = () => {
|
||||
setShowDocumentsModal={setShowDocumentsModal}
|
||||
setShowUploadForm={setShowUploadForm}
|
||||
handleRetriggerEvaluators={handleRetriggerEvaluators}
|
||||
handleCancelInterview={handleCancelInterview}
|
||||
handleRescheduleInterview={handleRescheduleInterview}
|
||||
setSelectedEvaluationForView={setSelectedEvaluationForView}
|
||||
setShowFeedbackDetailsModal={setShowFeedbackDetailsModal}
|
||||
|
||||
@ -30,7 +30,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ApplicationProgressBar } from '@/features/onboarding/components/ApplicationProgressBar';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -58,7 +58,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
||||
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [isSendingReminders, setIsSendingReminders] = useState(false);
|
||||
const [sortBy, setSortBy] = useState<'date'>('date');
|
||||
const [sortBy] = useState<'date'>('date');
|
||||
const [showNewApplicationModal, setShowNewApplicationModal] = useState(false);
|
||||
const [showMyAssignments, setShowMyAssignments] = useState(false);
|
||||
|
||||
@ -203,7 +203,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
||||
'Questionnaire Pending': 'bg-orange-500',
|
||||
'Questionnaire Completed': 'bg-blue-500',
|
||||
'Shortlisted': 'bg-cyan-500',
|
||||
'Level 1 Pending': 'bg-amber-500',
|
||||
'Level 1 Pending': 'bg-red-500',
|
||||
'Level 1 Approved': 'bg-green-500',
|
||||
'Level 2 Pending': 'bg-purple-500',
|
||||
'Level 2 Approved': 'bg-green-600',
|
||||
@ -375,10 +375,12 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
||||
{app.residentialAddress}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Progress value={app.progress} className="h-2 w-20" data-testid={`onboarding-application-progress-bar-${idx}`} />
|
||||
<span className="text-slate-600" data-testid={`onboarding-application-progress-text-${idx}`}>{app.progress}%</span>
|
||||
</div>
|
||||
<ApplicationProgressBar
|
||||
value={app.progress}
|
||||
status={app.status}
|
||||
showPercent
|
||||
data-testid={`onboarding-application-progress-bar-${idx}`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell data-testid={`onboarding-application-date-${idx}`}>
|
||||
{formatDateTime(app.submissionDate)}
|
||||
@ -488,7 +490,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
||||
<Button variant="outline" onClick={() => setShowNewApplicationModal(false)} data-testid="onboarding-new-app-cancel">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button className="bg-amber-600 hover:bg-amber-700" data-testid="onboarding-new-app-submit">
|
||||
<Button className="bg-re-red hover:bg-re-red-hover" data-testid="onboarding-new-app-submit">
|
||||
Create Application
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -408,20 +408,20 @@ export function FDDApplicationDetails() {
|
||||
{/* SECTION 2: MY SUBMISSIONS */}
|
||||
<div data-testid="onboarding-fdd-details-my-submissions">
|
||||
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2 flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-500"></div>
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-red-500"></div>
|
||||
My Uploaded Reports
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{application.uploadedDocuments?.filter((d: any) => d.uploader?.roleCode === 'FDD').map((doc: any, i: number) => (
|
||||
<div key={i} className="p-3 border border-amber-100 bg-amber-50/30 rounded flex items-center justify-between hover:bg-amber-50 transition-all group" data-testid={`onboarding-fdd-details-my-report-row-${i}`}>
|
||||
<div key={i} className="p-3 border border-red-100 bg-red-50/30 rounded flex items-center justify-between hover:bg-red-50 transition-all group" data-testid={`onboarding-fdd-details-my-report-row-${i}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded bg-amber-100 flex items-center justify-center text-amber-500">
|
||||
<div className="w-8 h-8 rounded bg-red-50 flex items-center justify-center text-re-red">
|
||||
<FileText className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-xs font-bold text-slate-900" data-testid={`onboarding-fdd-details-my-report-name-${i}`}>{doc.originalName || doc.fileName}</p>
|
||||
<span className="text-[8px] bg-amber-500 text-white px-1 py-0.5 rounded uppercase font-bold tracking-tighter">YOUR AUDIT REPORT</span>
|
||||
<span className="text-[8px] bg-red-500 text-white px-1 py-0.5 rounded uppercase font-bold tracking-tighter">YOUR AUDIT REPORT</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-slate-400 font-medium" data-testid={`onboarding-fdd-details-my-report-meta-${i}`}>
|
||||
{doc.documentType} • {formatDateTime(doc.createdAt)}
|
||||
@ -433,7 +433,7 @@ export function FDDApplicationDetails() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handlePreview(doc)}
|
||||
className="p-1.5 hover:bg-white rounded text-slate-400 hover:text-amber-600 transition-all"
|
||||
className="p-1.5 hover:bg-white rounded text-slate-400 hover:text-re-red transition-all"
|
||||
data-testid={`onboarding-fdd-details-my-report-preview-${i}`}
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
@ -576,22 +576,22 @@ export function FDDApplicationDetails() {
|
||||
<Dialog open={showFinalizeModal} onOpenChange={setShowFinalizeModal}>
|
||||
<DialogContent className="max-w-md p-0 overflow-hidden border-none shadow-2xl" data-testid="onboarding-fdd-details-finalize-modal">
|
||||
<div className="bg-slate-950 p-6 flex items-center justify-center relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-amber-600/20 to-transparent" />
|
||||
<div className="w-16 h-16 bg-amber-600/20 rounded-full flex items-center justify-center animate-pulse relative z-10">
|
||||
<ShieldCheck className="w-8 h-8 text-amber-500" />
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-re-red/20 to-transparent" />
|
||||
<div className="w-16 h-16 bg-re-red/20 rounded-full flex items-center justify-center animate-pulse relative z-10">
|
||||
<ShieldCheck className="w-8 h-8 text-re-red" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-8 space-y-4">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold text-slate-900 text-center" data-testid="onboarding-fdd-details-finalize-title">Submit Audit Report</DialogTitle>
|
||||
<DialogDescription className="text-slate-500 text-center pt-2 leading-relaxed text-base" data-testid="onboarding-fdd-details-finalize-desc">
|
||||
You are about to submit your final findings. This action will <span className="font-bold text-slate-800 underline decoration-amber-500 decoration-2">notify the Admin</span> for review and approval.
|
||||
You are about to submit your final findings. This action will <span className="font-bold text-slate-800 underline decoration-re-red decoration-2">notify the Admin</span> for review and approval.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="bg-amber-50 p-4 rounded-xl flex gap-3 border border-amber-100 italic" data-testid="onboarding-fdd-details-finalize-info">
|
||||
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-amber-800 leading-normal">
|
||||
<div className="bg-red-50 p-4 rounded-xl flex gap-3 border border-red-100 italic" data-testid="onboarding-fdd-details-finalize-info">
|
||||
<Info className="w-5 h-5 text-re-red shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-red-800 leading-normal">
|
||||
Once submitted, you cannot edit the findings. Ensure all documents are uploaded.
|
||||
</p>
|
||||
</div>
|
||||
@ -602,7 +602,7 @@ export function FDDApplicationDetails() {
|
||||
<Label className="text-[10px] font-black uppercase tracking-widest text-slate-400">Detailed Audit Findings & Remarks</Label>
|
||||
<Textarea
|
||||
placeholder="Enter detailed financial observations..."
|
||||
className="min-h-[120px] bg-slate-50 border-slate-200 rounded-xl focus:ring-amber-500 text-sm resize-none"
|
||||
className="min-h-[120px] bg-slate-50 border-slate-200 rounded-xl focus:ring-re-red text-sm resize-none"
|
||||
value={fddAuditFindings}
|
||||
onChange={(e) => setFddAuditFindings(e.target.value)}
|
||||
data-testid="onboarding-fdd-details-finalize-remarks"
|
||||
@ -621,7 +621,7 @@ export function FDDApplicationDetails() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full sm:flex-1 h-12 rounded-xl font-bold bg-slate-950 hover:bg-slate-900 text-white shadow-lg shadow-slate-200 transition-all active:scale-95 border-b-2 border-amber-600"
|
||||
className="w-full sm:flex-1 h-12 rounded-xl font-bold bg-slate-950 hover:bg-slate-900 text-white shadow-lg shadow-slate-200 transition-all active:scale-95 border-b-2 border-re-red"
|
||||
data-testid="onboarding-fdd-details-finalize-confirm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
|
||||
@ -137,7 +137,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-20">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-amber-600"></div>
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-re-red"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -184,7 +184,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<Badge variant="outline" className="bg-slate-50 text-slate-600 border-slate-200 py-1.5 px-3 rounded-full text-[10px] font-black uppercase tracking-widest" data-testid="onboarding-finance-fdd-app-id">
|
||||
APP ID: {application.applicationId || application.id}
|
||||
</Badge>
|
||||
<Badge className="bg-amber-100 text-amber-700 hover:bg-amber-100 py-1.5 px-3 rounded-full text-[10px] font-black uppercase tracking-widest border border-amber-200" data-testid="onboarding-finance-fdd-status">
|
||||
<Badge className="bg-red-50 text-re-red-hover hover:bg-red-50 py-1.5 px-3 rounded-full text-[10px] font-black uppercase tracking-widest border border-red-200" data-testid="onboarding-finance-fdd-status">
|
||||
{application.status}
|
||||
</Badge>
|
||||
</div>
|
||||
@ -293,7 +293,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<div className="space-y-4" data-testid="onboarding-finance-fdd-reports-section">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<h3 className="text-lg font-black text-slate-900 flex items-center gap-2">
|
||||
<ShieldCheck className="w-5 h-5 text-amber-600" /> Audit Findings & Reports
|
||||
<ShieldCheck className="w-5 h-5 text-re-red" /> Audit Findings & Reports
|
||||
</h3>
|
||||
<Badge variant="outline" className="bg-white text-slate-500 font-bold border-slate-200" data-testid="onboarding-finance-fdd-reports-count-badge">
|
||||
{assignments.length} Reports Found
|
||||
@ -309,12 +309,12 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{assignments.map((assignment: any, idx: number) => (
|
||||
<Card key={assignment.id} className="border-slate-200 shadow-sm overflow-hidden rounded-2xl group hover:border-amber-400 transition-all duration-300" data-testid={`onboarding-finance-fdd-assignment-card-${idx}`}>
|
||||
<Card key={assignment.id} className="border-slate-200 shadow-sm overflow-hidden rounded-2xl group hover:border-red-300 transition-all duration-300" data-testid={`onboarding-finance-fdd-assignment-card-${idx}`}>
|
||||
<div className="bg-white p-6 border-b border-slate-50">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 bg-amber-50 rounded-2xl flex items-center justify-center border border-amber-100 shadow-inner group-hover:scale-105 transition-transform">
|
||||
<ShieldCheck className="w-6 h-6 text-amber-600" />
|
||||
<div className="w-12 h-12 bg-red-50 rounded-2xl flex items-center justify-center border border-red-100 shadow-inner group-hover:scale-105 transition-transform">
|
||||
<ShieldCheck className="w-6 h-6 text-re-red" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-slate-900 font-black text-lg leading-none">FDD Audit Assignment</h4>
|
||||
@ -323,7 +323,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
</div>
|
||||
<Badge className={cn(
|
||||
"py-1 px-3 rounded-full text-[10px] font-black uppercase tracking-widest",
|
||||
assignment.status === 'completed' ? "bg-green-100 text-green-700" : "bg-amber-100 text-amber-700"
|
||||
assignment.status === 'completed' ? "bg-green-100 text-green-700" : "bg-red-50 text-re-red-hover"
|
||||
)} data-testid={`onboarding-finance-fdd-assignment-status-${idx}`}>
|
||||
{assignment.status}
|
||||
</Badge>
|
||||
@ -344,12 +344,12 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<div className={cn(
|
||||
"inline-flex items-center gap-2 px-4 py-2 rounded-xl border text-xs font-black shadow-sm",
|
||||
report.recommendation === 'Green' ? "bg-green-50 border-green-200 text-green-700" :
|
||||
report.recommendation === 'Amber' ? "bg-amber-50 border-amber-200 text-amber-700" :
|
||||
report.recommendation === 'Amber' ? "bg-red-50 border-red-200 text-re-red-hover" :
|
||||
"bg-red-50 border-red-200 text-red-700"
|
||||
)} data-testid={`onboarding-finance-fdd-report-signal-${idx}-${reportIdx}`}>
|
||||
<div className={cn("w-2.5 h-2.5 rounded-full animate-pulse",
|
||||
report.recommendation === 'Green' ? "bg-green-500" :
|
||||
report.recommendation === 'Amber' ? "bg-amber-500" : "bg-red-500"
|
||||
report.recommendation === 'Amber' ? "bg-red-500" : "bg-red-500"
|
||||
)} />
|
||||
{report.recommendation?.toUpperCase()} SIGNAL
|
||||
</div>
|
||||
@ -366,7 +366,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<div className="space-y-5">
|
||||
<Label className="text-[10px] text-slate-400 uppercase tracking-widest font-black mb-2 block">Available Documents</Label>
|
||||
{report.reportDocument ? (
|
||||
<div className="bg-white border-2 border-slate-100 rounded-2xl p-4 flex items-center justify-between hover:border-amber-400 transition-all hover:shadow-lg cursor-default" data-testid={`onboarding-finance-fdd-report-doc-${idx}-${reportIdx}`}>
|
||||
<div className="bg-white border-2 border-slate-100 rounded-2xl p-4 flex items-center justify-between hover:border-red-300 transition-all hover:shadow-lg cursor-default" data-testid={`onboarding-finance-fdd-report-doc-${idx}-${reportIdx}`}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center shadow-sm">
|
||||
<FileText className="w-6 h-6 text-red-500" />
|
||||
@ -380,7 +380,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 text-slate-400 hover:text-amber-600 hover:bg-amber-50 rounded-xl"
|
||||
className="h-10 w-10 text-slate-400 hover:text-re-red hover:bg-red-50 rounded-xl"
|
||||
onClick={() => window.open(`http://localhost:5000/${report.reportDocument.filePath}`, '_blank')}
|
||||
data-testid={`onboarding-finance-fdd-report-download-${idx}-${reportIdx}`}
|
||||
>
|
||||
@ -389,7 +389,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 text-slate-400 hover:text-amber-600 hover:bg-amber-50 rounded-xl"
|
||||
className="h-10 w-10 text-slate-400 hover:text-re-red hover:bg-red-50 rounded-xl"
|
||||
onClick={() => {
|
||||
setPreviewDoc(report.reportDocument);
|
||||
setShowPreviewModal(true);
|
||||
@ -413,9 +413,9 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<span className="text-[10px] font-black text-green-700 uppercase tracking-widest">Audited & Verified</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 bg-amber-50 border border-amber-100 px-4 py-2 rounded-full shadow-sm" data-testid={`onboarding-finance-fdd-report-pending-${idx}-${reportIdx}`}>
|
||||
<Clock className="w-4 h-4 text-amber-600" />
|
||||
<span className="text-[10px] font-black text-amber-700 uppercase tracking-widest">Pending Verification</span>
|
||||
<div className="flex items-center gap-2 bg-red-50 border border-red-100 px-4 py-2 rounded-full shadow-sm" data-testid={`onboarding-finance-fdd-report-pending-${idx}-${reportIdx}`}>
|
||||
<Clock className="w-4 h-4 text-re-red" />
|
||||
<span className="text-[10px] font-black text-re-red-hover uppercase tracking-widest">Pending Verification</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -437,7 +437,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<Card className="border-slate-200 shadow-sm overflow-hidden rounded-2xl sticky top-6" data-testid="onboarding-finance-fdd-action-sidebar">
|
||||
<CardHeader className="bg-slate-900 border-b border-slate-800 py-5">
|
||||
<CardTitle className="text-white text-sm font-black uppercase tracking-widest flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-amber-400" /> Finance Action
|
||||
<CheckCircle className="w-4 h-4 text-re-red" /> Finance Action
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6 space-y-6">
|
||||
@ -446,7 +446,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<Textarea
|
||||
id="remarks"
|
||||
placeholder="Enter your assessment or audit sign-off remarks here..."
|
||||
className="min-h-[150px] bg-slate-50 border-slate-200 rounded-xl focus:ring-amber-500 focus:border-amber-500 text-sm font-medium"
|
||||
className="min-h-[150px] bg-slate-50 border-slate-200 rounded-xl focus:ring-re-red focus:border-re-red text-sm font-medium"
|
||||
value={approvalRemark}
|
||||
onChange={(e) => setApprovalRemark(e.target.value)}
|
||||
disabled={hasMadeDecision || isSubmitting}
|
||||
@ -458,7 +458,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
!isReadOnly ? (
|
||||
<div className="space-y-3 pt-2">
|
||||
<Button
|
||||
className="w-full h-14 bg-amber-600 hover:bg-amber-700 text-white font-black uppercase tracking-widest rounded-xl shadow-lg shadow-amber-200/50 transition-all active:scale-95"
|
||||
className="w-full h-14 bg-re-red hover:bg-re-red-hover text-white font-black uppercase tracking-widest rounded-xl shadow-lg shadow-red-200/50 transition-all active:scale-95"
|
||||
onClick={() => handleDecision('Approved')}
|
||||
disabled={isSubmitting}
|
||||
data-testid="onboarding-finance-fdd-approve-btn"
|
||||
@ -473,7 +473,7 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full h-12 text-amber-600 border-amber-200 hover:bg-amber-50 font-black uppercase tracking-widest rounded-xl"
|
||||
className="w-full h-12 text-re-red border-red-200 hover:bg-red-50 font-black uppercase tracking-widest rounded-xl"
|
||||
onClick={() => toast.info('Clarification request functionality coming soon')}
|
||||
disabled={isSubmitting}
|
||||
data-testid="onboarding-finance-fdd-revision-btn"
|
||||
@ -509,15 +509,15 @@ export function FinanceFddDetailPage({ applicationId, onBack }: FinanceFddDetail
|
||||
<h5 className="text-[10px] text-slate-400 font-black uppercase tracking-widest mb-4">Verification Policy</h5>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start gap-3" data-testid="onboarding-finance-fdd-policy-1">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 mt-1.5 shrink-0" />
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-red-500 mt-1.5 shrink-0" />
|
||||
<p className="text-[10px] text-slate-500 font-bold leading-tight">Must review PDF audit report for financial discrepancies before approval.</p>
|
||||
</li>
|
||||
<li className="flex items-start gap-3" data-testid="onboarding-finance-fdd-policy-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 mt-1.5 shrink-0" />
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-red-500 mt-1.5 shrink-0" />
|
||||
<p className="text-[10px] text-slate-500 font-bold leading-tight">Approval triggers the progression to Security Deposit payment state.</p>
|
||||
</li>
|
||||
<li className="flex items-start gap-3" data-testid="onboarding-finance-fdd-policy-3">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 mt-1.5 shrink-0" />
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-red-500 mt-1.5 shrink-0" />
|
||||
<p className="text-[10px] text-slate-500 font-bold leading-tight">Remarks are mandatory for audit trail and compliance tracking.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -231,7 +231,7 @@ export function FinanceOnboardingPage({ onViewPaymentDetails }: FinanceOnboardin
|
||||
className={
|
||||
statusLabel === 'Verified' ? 'bg-emerald-50 text-emerald-700 border-emerald-100 px-3 py-1 rounded-full' :
|
||||
statusLabel === 'Rejected' ? 'bg-rose-50 text-rose-700 border-rose-100 px-3 py-1 rounded-full' :
|
||||
'bg-amber-50 text-amber-700 border-amber-100 px-3 py-1 rounded-full'
|
||||
'bg-red-50 text-re-red-hover border-red-100 px-3 py-1 rounded-full'
|
||||
}
|
||||
variant="outline"
|
||||
data-testid={`onboarding-finance-queue-status-${idx}`}
|
||||
|
||||
@ -237,7 +237,7 @@ export function NonOpportunitiesPage({ onViewDetails }: NonOpportunitiesPageProp
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-slate-200 p-4" data-testid="onboarding-non-opps-stat-exp">
|
||||
<p className="text-slate-600">With Experience</p>
|
||||
<p className="text-2xl text-amber-600 mt-1">
|
||||
<p className="text-2xl text-re-red mt-1">
|
||||
{paginationMeta?.stats?.withExperience || 0}
|
||||
</p>
|
||||
</div>
|
||||
@ -355,7 +355,7 @@ export function NonOpportunitiesPage({ onViewDetails }: NonOpportunitiesPageProp
|
||||
|
||||
{selectedIds.length > 0 && (
|
||||
<Button
|
||||
className="bg-amber-600 hover:bg-amber-700 font-bold"
|
||||
className="bg-re-red hover:bg-re-red-hover font-bold"
|
||||
onClick={handleBulkConvert}
|
||||
disabled={isBulkConverting}
|
||||
>
|
||||
@ -396,7 +396,7 @@ export function NonOpportunitiesPage({ onViewDetails }: NonOpportunitiesPageProp
|
||||
{isGlobalLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={11} className="text-center py-20">
|
||||
<Loader2 className="w-8 h-8 mx-auto animate-spin text-amber-600 mb-2" />
|
||||
<Loader2 className="w-8 h-8 mx-auto animate-spin text-re-red mb-2" />
|
||||
<p className="text-slate-500 text-sm">Loading applications...</p>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -413,7 +413,7 @@ export function NonOpportunitiesPage({ onViewDetails }: NonOpportunitiesPageProp
|
||||
<TableRow
|
||||
key={lead.id}
|
||||
data-testid={`onboarding-non-opps-row-${idx}`}
|
||||
className={selectedIds.includes(lead.id) ? 'bg-amber-50/50' : ''}
|
||||
className={selectedIds.includes(lead.id) ? 'bg-red-50/50' : ''}
|
||||
>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
|
||||
@ -48,7 +48,7 @@ import {
|
||||
TableRow,
|
||||
TableCell
|
||||
} from '@/components/ui/table';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ApplicationProgressBar } from '@/features/onboarding/components/ApplicationProgressBar';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@ -351,7 +351,7 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
|
||||
'Level 2 Recommended': 'bg-teal-100 text-teal-800',
|
||||
'Level 3 Interview Pending': 'bg-orange-100 text-orange-800',
|
||||
'FDD Verification': 'bg-indigo-100 text-indigo-800',
|
||||
'Payment Pending': 'bg-amber-100 text-amber-800',
|
||||
'Payment Pending': 'bg-red-50 text-red-800',
|
||||
'LOI Issued': 'bg-sky-100 text-sky-800',
|
||||
'Dealer Code Generation': 'bg-purple-100 text-purple-800',
|
||||
'Architecture Team Assigned': 'bg-blue-100 text-blue-800',
|
||||
@ -401,7 +401,7 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-96">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-re-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -554,7 +554,7 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
|
||||
variant={viewMode === 'grid' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={viewMode === 'grid' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={viewMode === 'grid' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
data-testid="onboarding-opp-requests-view-grid-btn"
|
||||
>
|
||||
<Grid3x3 className="w-4 h-4 mr-2" />
|
||||
@ -564,7 +564,7 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
|
||||
variant={viewMode === 'table' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('table')}
|
||||
className={viewMode === 'table' ? 'bg-amber-600 hover:bg-amber-700' : ''}
|
||||
className={viewMode === 'table' ? 'bg-re-red hover:bg-re-red-hover' : ''}
|
||||
data-testid="onboarding-opp-requests-view-table-btn"
|
||||
>
|
||||
<List className="w-4 h-4 mr-2" />
|
||||
@ -706,10 +706,12 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
|
||||
<Badge variant="outline" data-testid={`onboarding-opp-requests-shortlisted-badge-${idx}`}>No</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2" data-testid={`onboarding-opp-requests-progress-container-${idx}`}>
|
||||
<Progress value={app.progress} className="w-20" />
|
||||
<span className="text-slate-600">{app.progress}%</span>
|
||||
</div>
|
||||
<ApplicationProgressBar
|
||||
value={app.progress}
|
||||
status={app.status}
|
||||
showPercent
|
||||
data-testid={`onboarding-opp-requests-progress-${idx}`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-slate-600" data-testid={`onboarding-opp-requests-date-${idx}`}>{formatDateTime(app.submissionDate)}</span>
|
||||
|
||||
@ -211,7 +211,7 @@ export function WorkNotesPage(props: Partial<WorkNotesPageProps>) {
|
||||
'bg-green-600',
|
||||
'bg-blue-600',
|
||||
'bg-purple-600',
|
||||
'bg-amber-600',
|
||||
'bg-re-red',
|
||||
'bg-pink-600',
|
||||
'bg-indigo-600',
|
||||
'bg-teal-600',
|
||||
|
||||
@ -14,18 +14,15 @@ import { toast } from 'sonner';
|
||||
import { dealerService } from '@/services/dealer.service';
|
||||
import { masterService } from '@/services/master.service';
|
||||
import { formatDateTime } from '@/components/ui/utils';
|
||||
import { getRequestStatusBadgeClass } from '@/lib/statusProgressTheme';
|
||||
|
||||
interface DealerRelocationPageProps {
|
||||
currentUser: UserType | null;
|
||||
onViewDetails?: (id: string) => void;
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
|
||||
if (status.includes('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||
if (status.includes('Rejected') || status.includes('Revoked')) return 'bg-red-100 text-red-700 border-red-300';
|
||||
return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
};
|
||||
const getStatusColor = (status: string, currentStage?: string) =>
|
||||
getRequestStatusBadgeClass(status, currentStage);
|
||||
|
||||
const getApiErrorMessage = (error: any, fallback: string) =>
|
||||
error?.response?.data?.message || error?.data?.message || error?.message || fallback;
|
||||
@ -203,7 +200,7 @@ export function DealerRelocationPage({ onViewDetails }: DealerRelocationPageProp
|
||||
title: 'Pending',
|
||||
value: requests.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
|
||||
icon: Calendar,
|
||||
color: 'bg-yellow-500',
|
||||
color: 'bg-re-red',
|
||||
},
|
||||
{
|
||||
title: 'Approved',
|
||||
|
||||
@ -17,6 +17,14 @@ import { toast } from 'sonner';
|
||||
import { API } from '@/api/API';
|
||||
import { SlaBadge } from '@/components/sla/SlaBadge';
|
||||
import { useSlaBatchStatus } from '@/hooks/useSlaBatchStatus';
|
||||
import {
|
||||
getCurrentStageBadgeClass,
|
||||
getOffboardingRequestStatusBadgeClass,
|
||||
getStatusLabelBadgeClass,
|
||||
getStatusProgressBarClass,
|
||||
isOffboardingTerminalNegative,
|
||||
WORKFLOW_IN_PROGRESS_ACCENT,
|
||||
} from '@/lib/offboardingDisplay';
|
||||
|
||||
interface RelocationRequestDetailsProps {
|
||||
requestId: string;
|
||||
@ -174,13 +182,12 @@ const requiredDocuments = [
|
||||
'Water supply documents'
|
||||
];
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
if (status === 'Completed' || status === 'Verified' || status === 'Closed') return 'bg-green-100 text-green-700 border-green-300';
|
||||
if (status.includes('Review') || status.includes('Pending') || status === 'In Progress') return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||
if (status.includes('Rejected') || status.includes('Revoked')) return 'bg-red-100 text-red-700 border-red-300';
|
||||
if (status.includes('Collection') || status.includes('Completion') || status.includes('Infra')) return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
};
|
||||
const getStatusColor = (status: string) => getStatusLabelBadgeClass(status);
|
||||
|
||||
const getDocChecklistUploadButtonClass = (isRejected: boolean) =>
|
||||
isRejected
|
||||
? 'h-7 px-2 text-red-700 hover:bg-red-50 hover:text-red-800 flex-shrink-0'
|
||||
: 'h-7 px-2 text-slate-700 hover:bg-slate-50 flex-shrink-0';
|
||||
|
||||
const getApiErrorMessage = (error: any, fallback: string) => {
|
||||
const responseData = error?.response?.data || error?.data;
|
||||
@ -224,6 +231,10 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
const [activeTab, setActiveTab] = useState('workflow');
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
const [selectedDoc, setSelectedDoc] = useState<any>(null);
|
||||
const [rejectDocDialogOpen, setRejectDocDialogOpen] = useState(false);
|
||||
const [rejectDocId, setRejectDocId] = useState<string | null>(null);
|
||||
const [rejectDocReason, setRejectDocReason] = useState('');
|
||||
const [isRejectingDoc, setIsRejectingDoc] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequestDetails();
|
||||
@ -368,6 +379,15 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
? 100
|
||||
: Math.min(100, Math.round((dbOrdinal / workflowStages.length) * 100));
|
||||
const displayProgressPct = allWorkflowComplete ? 100 : timelineProgressPct;
|
||||
const workflowTerminalNegative = request
|
||||
? isOffboardingTerminalNegative(request.status, request.currentStage)
|
||||
: false;
|
||||
const statusProgressBarClass = request
|
||||
? getStatusProgressBarClass(request.status, request.currentStage)
|
||||
: 'bg-status-progress';
|
||||
const requestStatusBadgeClass = request
|
||||
? getOffboardingRequestStatusBadgeClass(request.status, request.currentStage)
|
||||
: 'bg-re-red hover:bg-re-red-hover text-white border-transparent';
|
||||
|
||||
const missingRequiredDocs = request
|
||||
? requiredDocuments.filter((doc) => !request.documents?.some((d: any) =>
|
||||
@ -524,17 +544,31 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
}
|
||||
};
|
||||
|
||||
const handleRejectDocument = async (documentId: string) => {
|
||||
const submitRejectDocument = async () => {
|
||||
if (!rejectDocId || !String(rejectDocReason).trim()) {
|
||||
toast.error('Please enter a rejection reason.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await API.rejectRelocationDocument(requestId, documentId, { remarks: 'Rejected by reviewer' }) as any;
|
||||
if (response.data.success) {
|
||||
setIsRejectingDoc(true);
|
||||
const response = await API.rejectRelocationDocument(requestId, rejectDocId, {
|
||||
remarks: rejectDocReason.trim()
|
||||
}) as any;
|
||||
if (response.data?.success) {
|
||||
toast.success('Document rejected successfully');
|
||||
setRejectDocDialogOpen(false);
|
||||
setRejectDocId(null);
|
||||
setRejectDocReason('');
|
||||
fetchRequestDetails(true);
|
||||
fetchAuditLogs();
|
||||
} else {
|
||||
toast.error(response.data?.message || 'Failed to reject document');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Reject document error:', error);
|
||||
toast.error(getApiErrorMessage(error, 'Failed to reject document'));
|
||||
} finally {
|
||||
setIsRejectingDoc(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -552,7 +586,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[400px] space-y-4">
|
||||
<Loader2 className="w-10 h-10 text-amber-600 animate-spin" />
|
||||
<Loader2 className="w-10 h-10 text-re-red animate-spin" />
|
||||
<p className="text-slate-500 font-medium">Loading request details...</p>
|
||||
</div>
|
||||
);
|
||||
@ -594,7 +628,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="relative hover:bg-amber-50 hover:border-amber-300 hover:text-amber-700 transition-all shadow-sm"
|
||||
className="relative hover:bg-red-50 hover:border-red-300 hover:text-re-red-hover transition-all shadow-sm"
|
||||
onClick={() => navigate(`/worknotes/relocation/${requestId}`, {
|
||||
state: {
|
||||
applicationName: request?.outlet?.name || 'Relocation',
|
||||
@ -606,13 +640,13 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
View Work Notes
|
||||
{request?.worknotes?.length > 0 && (
|
||||
<Badge className="ml-2 bg-amber-600 hover:bg-amber-700 text-white h-5 px-2">
|
||||
<Badge className="ml-2 bg-re-red hover:bg-re-red-hover text-white h-5 px-2">
|
||||
{request.worknotes.length}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Badge className={getStatusColor(request.status)}>
|
||||
<Badge className={requestStatusBadgeClass}>
|
||||
{request.status}
|
||||
</Badge>
|
||||
</div>
|
||||
@ -642,7 +676,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Navigation className="w-4 h-4 text-amber-600" />
|
||||
<Navigation className="w-4 h-4 text-re-red" />
|
||||
<div>
|
||||
<p className="text-slate-600 text-xs">To (Proposed)</p>
|
||||
<p className="text-slate-900 text-sm">{request.proposedLocation || `${request.newAddress}, ${request.newCity}`}</p>
|
||||
@ -653,7 +687,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
Type: {request.relocationType}
|
||||
</Badge>
|
||||
{request.distance && (
|
||||
<Badge variant="outline" className="border-amber-200 bg-amber-50 text-amber-700">
|
||||
<Badge variant="outline" className="border-red-200 bg-red-50 text-re-red-hover">
|
||||
Distance: {request.distance}
|
||||
</Badge>
|
||||
)}
|
||||
@ -674,9 +708,12 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<p className="text-slate-600 text-sm mb-1">Request Information</p>
|
||||
<p className="text-slate-900 text-sm">Submitted: {formatDateTime(request.createdAt)}</p>
|
||||
<p className="text-slate-600 text-sm">By: {request.dealer?.fullName}</p>
|
||||
<p className="text-slate-900 text-sm mt-2">
|
||||
Current Stage: {String(request.currentStage || '').replace(/_/g, ' ')}
|
||||
</p>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2">
|
||||
<span className="text-slate-600 text-sm">Current Stage:</span>
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{String(request.currentStage || '').replace(/_/g, ' ')}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -704,23 +741,32 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
|
||||
<CardContent>
|
||||
{/* Workflow Progress Tab */}
|
||||
<TabsContent value="workflow" className="mt-0">
|
||||
<TabsContent value="workflow" className="mt-0 status-progress-ui">
|
||||
{/* Progress Bar */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-slate-900">Overall Progress</span>
|
||||
<span className="text-slate-600">{displayProgressPct}%</span>
|
||||
<Badge className={`${statusProgressBarClass} text-white border-transparent hover:opacity-90`}>
|
||||
{displayProgressPct}% Complete
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="h-3 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-600 transition-all duration-500"
|
||||
className={`h-full transition-all duration-500 ${statusProgressBarClass}`}
|
||||
style={{ width: `${displayProgressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{workflowProgressMismatch && (
|
||||
<div className="mb-6 rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
||||
{workflowTerminalNegative && (
|
||||
<div className="mb-6 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
This request is closed as <strong>{String(request.status)}</strong>. The approval path below is
|
||||
for reference only.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{workflowProgressMismatch && !workflowTerminalNegative && (
|
||||
<div className="mb-6 rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-900">
|
||||
<span className="font-medium">Activity ahead of current stage:</span> Some timeline or audit
|
||||
entries reference steps after the official current stage ({String(request.currentStage)}).
|
||||
Per-step history below may include future steps; the highlighted step and approvals follow the
|
||||
@ -737,7 +783,16 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
|
||||
{/* Workflow stages + per-stage timeline (same pattern as ResignationDetails progress tab) */}
|
||||
<div className="space-y-4">
|
||||
{workflowStages.map((stage: any, index: number) => {
|
||||
{workflowTerminalNegative ? (
|
||||
<ul className="list-disc space-y-1 pl-5 text-sm text-slate-600">
|
||||
{workflowStages.map((stage: any) => (
|
||||
<li key={stage.id}>
|
||||
<span className="text-slate-900">{stage.name}</span> — {stage.role}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
workflowStages.map((stage: any, index: number) => {
|
||||
const isCompleted = allWorkflowComplete || index < dbOrdinal - 1;
|
||||
const isCurrent = !allWorkflowComplete && index === dbOrdinal - 1;
|
||||
const stageTimelineEntries = getRelocationTimelineEntriesForStage(
|
||||
@ -758,7 +813,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
isCompleted
|
||||
? 'bg-green-100 text-green-600'
|
||||
: isCurrent
|
||||
? 'bg-amber-100 text-amber-600'
|
||||
? WORKFLOW_IN_PROGRESS_ACCENT.icon
|
||||
: 'bg-slate-100 text-slate-400'
|
||||
}`}
|
||||
>
|
||||
@ -779,9 +834,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
|
||||
<div
|
||||
className={`flex-1 pb-8 ${
|
||||
isCurrent
|
||||
? 'bg-amber-50 -ml-4 pl-4 pr-4 py-3 rounded-lg border border-amber-200'
|
||||
: ''
|
||||
isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.panel : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1 gap-2">
|
||||
@ -791,7 +844,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
isCompleted
|
||||
? 'text-green-700'
|
||||
: isCurrent
|
||||
? 'text-amber-900'
|
||||
? WORKFLOW_IN_PROGRESS_ACCENT.title
|
||||
: 'text-slate-900'
|
||||
}
|
||||
>
|
||||
@ -799,7 +852,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
</h4>
|
||||
<p
|
||||
className={`text-sm ${
|
||||
isCurrent ? 'text-amber-700' : 'text-slate-600'
|
||||
isCurrent ? WORKFLOW_IN_PROGRESS_ACCENT.subtitle : 'text-slate-600'
|
||||
}`}
|
||||
>
|
||||
Responsible: {stage.role}
|
||||
@ -816,7 +869,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
isCompleted
|
||||
? 'bg-green-100 text-green-700 border-green-300'
|
||||
: isCurrent
|
||||
? 'bg-amber-100 text-amber-700 border-amber-300'
|
||||
? WORKFLOW_IN_PROGRESS_ACCENT.stageBadge
|
||||
: 'bg-slate-100 text-slate-500 border-slate-300'
|
||||
}
|
||||
>
|
||||
@ -862,7 +915,8 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
@ -893,7 +947,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
onClick={() => {
|
||||
setDocTypeLocked(false);
|
||||
setSelectedDocType(requiredDocuments[0]);
|
||||
@ -917,8 +971,8 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
{docTypeLocked ? (
|
||||
<div>
|
||||
<Label>Document</Label>
|
||||
<div className="mt-1 flex items-center gap-2 bg-amber-50 border border-amber-200 rounded-md px-3 h-10">
|
||||
<Badge className="bg-amber-600 text-white border-transparent">
|
||||
<div className="mt-1 flex items-center gap-2 bg-red-50 border border-red-200 rounded-md px-3 h-10">
|
||||
<Badge className="bg-re-red text-white border-transparent">
|
||||
{selectedDocType}
|
||||
</Badge>
|
||||
</div>
|
||||
@ -958,7 +1012,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-amber-600 hover:bg-amber-700"
|
||||
className="bg-re-red hover:bg-re-red-hover"
|
||||
onClick={handleUploadDocument}
|
||||
disabled={isUploading}
|
||||
>
|
||||
@ -982,27 +1036,40 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
const uploaded = request.documents?.find((d: any) =>
|
||||
d.type === doc || d.name.toLowerCase().includes(doc.toLowerCase().split(' ')[0])
|
||||
);
|
||||
const isRejected = uploaded && String(uploaded.status) === 'Rejected';
|
||||
const ok = uploaded && !isRejected;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
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'
|
||||
}`}
|
||||
className={`flex items-center justify-between gap-2 p-2 rounded border text-sm ${
|
||||
isRejected
|
||||
? 'bg-red-50 border-red-200'
|
||||
: ok
|
||||
? 'bg-green-50 border-green-200'
|
||||
: 'bg-slate-50 border-slate-200'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
{uploaded ? (
|
||||
{isRejected ? (
|
||||
<AlertCircle className="w-4 h-4 text-red-600 flex-shrink-0" />
|
||||
) : ok ? (
|
||||
<CheckCircle2 className="w-4 h-4 text-green-600 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'}`}>
|
||||
<span
|
||||
className={`truncate ${
|
||||
isRejected ? 'text-red-900' : ok ? 'text-green-900' : 'text-slate-700'
|
||||
}`}
|
||||
>
|
||||
{doc}
|
||||
</span>
|
||||
</div>
|
||||
{!uploaded && (
|
||||
{!ok && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 px-2 text-amber-700 hover:bg-amber-50 flex-shrink-0"
|
||||
className={getDocChecklistUploadButtonClass(!!isRejected)}
|
||||
onClick={() => {
|
||||
setSelectedDocType(doc);
|
||||
setSelectedFile(null);
|
||||
@ -1011,7 +1078,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
}}
|
||||
>
|
||||
<Upload className="w-3.5 h-3.5 mr-1" />
|
||||
Upload
|
||||
{isRejected ? 'Re-upload' : 'Upload'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -1040,7 +1107,10 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{request.documents.map((doc: any) => (
|
||||
<TableRow key={doc.id}>
|
||||
<TableRow
|
||||
key={doc.id}
|
||||
className={String(doc.status) === 'Rejected' ? 'bg-red-50/80' : undefined}
|
||||
>
|
||||
<TableCell className="text-slate-900">
|
||||
{doc.name}
|
||||
</TableCell>
|
||||
@ -1101,7 +1171,11 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
className="h-8 gap-1"
|
||||
onClick={() => handleRejectDocument(doc.id)}
|
||||
onClick={() => {
|
||||
setRejectDocId(doc.id);
|
||||
setRejectDocReason('');
|
||||
setRejectDocDialogOpen(true);
|
||||
}}
|
||||
title="Reject Document"
|
||||
>
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
@ -1148,7 +1222,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<div className="bg-slate-50 border border-dashed border-slate-300 rounded-lg p-12 text-center">
|
||||
{isEorLoading ? (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
|
||||
<Loader2 className="w-8 h-8 text-re-red animate-spin" />
|
||||
<p className="text-slate-500">Fetching checklist...</p>
|
||||
</div>
|
||||
) : (
|
||||
@ -1192,7 +1266,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
checked={item.isCompliant}
|
||||
onChange={(e) => handleUpdateEorItem(item.description, e.target.checked, item.itemType)}
|
||||
disabled={eorChecklist.status === 'Completed' || (currentUser?.role !== 'NBH' && currentUser?.role !== 'Super Admin')}
|
||||
className="w-4 h-4 rounded border-slate-300 text-amber-600"
|
||||
className="w-4 h-4 rounded border-slate-300 text-re-red"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@ -1230,7 +1304,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
</Button>
|
||||
</>
|
||||
) : item.proofDocumentId ? (
|
||||
<span className="text-xs text-amber-700">Proof linked (refresh if file details are missing)</span>
|
||||
<span className="text-xs text-re-red-hover">Proof linked (refresh if file details are missing)</span>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
@ -1275,7 +1349,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
)}
|
||||
|
||||
{!eorChecklist.items?.every((i: any) => i.isCompliant) && (
|
||||
<p className="text-right text-xs text-amber-600 italic">
|
||||
<p className="text-right text-xs text-re-red italic">
|
||||
All items must be marked as compliant before final submission.
|
||||
</p>
|
||||
)}
|
||||
@ -1298,7 +1372,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
${(entry.description || entry.action || entry.details?.action || '').toLowerCase().includes('reject') || (entry.description || entry.action || entry.details?.action || '').toLowerCase().includes('revok')
|
||||
? 'bg-red-100 text-red-700 border-red-200'
|
||||
: (entry.description || entry.action || entry.details?.action || '').toLowerCase().includes('sent back') || (entry.description || entry.action || entry.details?.action || '').toLowerCase().includes('send back')
|
||||
? 'bg-amber-100 text-amber-700 border-amber-200'
|
||||
? 'bg-red-50 text-re-red-hover border-red-200'
|
||||
: (entry.description || entry.action || entry.details?.action || '').toLowerCase().includes('approv') || (entry.description || entry.action || entry.details?.action || '').toLowerCase().includes('initi') || (entry.action || '').toLowerCase().includes('complete')
|
||||
? 'bg-emerald-100 text-emerald-700 border-emerald-200'
|
||||
: 'bg-slate-100 text-slate-700 border-slate-200'}
|
||||
@ -1346,14 +1420,16 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<p className="text-slate-600 text-sm">Current Stage</p>
|
||||
<p className="text-slate-900">{request.currentStage}</p>
|
||||
<Badge className={getCurrentStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-slate-600 text-sm">Progress</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-600 transition-all duration-300"
|
||||
className={`h-full transition-all duration-300 ${statusProgressBarClass}`}
|
||||
style={{ width: `${displayProgressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
@ -1388,9 +1464,9 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
Approve Request
|
||||
</Button>
|
||||
{!canApprove && (
|
||||
<p className="text-xs text-amber-700">
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-800">
|
||||
Approval is blocked until mandatory documents are uploaded and verified for this stage.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
@ -1410,7 +1486,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
{canSendBack && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full border-amber-400 text-amber-900 hover:bg-amber-50"
|
||||
className="w-full border-red-300 text-red-900 hover:bg-red-50"
|
||||
onClick={() => handleAction('sendBack')}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
@ -1460,34 +1536,6 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Assigned Evaluators Card */}
|
||||
{request.participants && request.participants.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Assigned Evaluators</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{request.participants.map((participant: any) => (
|
||||
<div key={participant.id} className="flex items-center justify-between p-2 bg-slate-50 rounded-lg">
|
||||
<div>
|
||||
<p className="text-slate-900 text-sm font-medium">{participant.user?.fullName || 'Unknown'}</p>
|
||||
<p className="text-slate-600 text-xs">{participant.user?.roleCode || 'User'}</p>
|
||||
{participant.metadata?.stage && (
|
||||
<Badge variant="outline" className="mt-1 text-xs">
|
||||
{participant.metadata.stage.replace(/_/g, ' ')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{participant.metadata?.autoAssigned && (
|
||||
<Badge className="bg-blue-100 text-blue-700 border-blue-300 text-xs">
|
||||
Auto-assigned
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1551,12 +1599,12 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
actionType === 'approve'
|
||||
? 'bg-green-600 hover:bg-green-700'
|
||||
: actionType === 'reject'
|
||||
? 'bg-red-600 hover:bg-red-700'
|
||||
? 'bg-re-red hover:bg-re-red-hover'
|
||||
: actionType === 'sendBack'
|
||||
? 'bg-amber-600 hover:bg-amber-700'
|
||||
? 'bg-re-red hover:bg-re-red-hover'
|
||||
: actionType === 'revoke'
|
||||
? 'bg-red-700 hover:bg-red-800'
|
||||
: 'bg-amber-600 hover:bg-amber-700'
|
||||
? 'bg-re-red hover:bg-re-red-hover'
|
||||
: 'bg-re-red hover:bg-re-red-hover'
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
@ -1582,6 +1630,40 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
{/* Worknotes Dialog */}
|
||||
{/* Worknotes Dialog - handled in Header */}
|
||||
|
||||
<Dialog open={rejectDocDialogOpen} onOpenChange={setRejectDocDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Reject document</DialogTitle>
|
||||
<DialogDescription>
|
||||
Mark this upload as rejected and provide a reason. The action is recorded in the audit trail.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="rejectDocReason">Rejection reason *</Label>
|
||||
<Textarea
|
||||
id="rejectDocReason"
|
||||
rows={4}
|
||||
value={rejectDocReason}
|
||||
onChange={(e) => setRejectDocReason(e.target.value)}
|
||||
placeholder="Explain what must be corrected or re-uploaded…"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => setRejectDocDialogOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
disabled={isRejectingDoc}
|
||||
onClick={() => void submitRejectDocument()}
|
||||
>
|
||||
{isRejectingDoc ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Confirm reject'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DocumentPreviewModal
|
||||
isOpen={isPreviewOpen}
|
||||
onClose={() => setIsPreviewOpen(false)}
|
||||
|
||||
@ -17,6 +17,10 @@ import { API } from '@/api/API';
|
||||
import { SlaBadge } from '@/components/sla/SlaBadge';
|
||||
import { useSlaBatchStatus } from '@/hooks/useSlaBatchStatus';
|
||||
import { formatDateTime } from '@/components/ui/utils';
|
||||
import {
|
||||
getCurrentStageBadgeClass,
|
||||
getStatusProgressBarClass,
|
||||
} from '@/lib/statusProgressTheme';
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
@ -35,13 +39,8 @@ interface RelocationRequestPageProps {
|
||||
const getApiErrorMessage = (error: any, fallback: string) =>
|
||||
error?.response?.data?.message || error?.data?.message || error?.message || fallback;
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
if (status === 'Completed' || status === 'Closed') return 'bg-green-100 text-green-700 border-green-300';
|
||||
if (status.includes('Review') || status.includes('Pending') || status === 'In Progress') return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||
if (status.includes('Rejected') || status.includes('Revoked')) return 'bg-red-100 text-red-700 border-red-300';
|
||||
if (status.includes('Collection') || status.includes('Completion') || status.includes('Infra')) return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
};
|
||||
const getStageBadgeClass = (stage: string, requestStatus?: string) =>
|
||||
getCurrentStageBadgeClass(stage, requestStatus);
|
||||
|
||||
export function RelocationRequestPage({ currentUser, onViewDetails }: RelocationRequestPageProps) {
|
||||
const [requests, setRequests] = useState<any[]>([]);
|
||||
@ -251,7 +250,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
title: 'Pending Review',
|
||||
value: requests.filter((r: any) => isPendingReviewRequest(r)).length,
|
||||
icon: Calendar,
|
||||
color: 'bg-yellow-500',
|
||||
color: 'bg-re-red',
|
||||
},
|
||||
{
|
||||
title: 'Completed',
|
||||
@ -284,7 +283,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
{isSuperAdmin && (
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-amber-600 hover:bg-amber-700 text-white">
|
||||
<Button className="bg-re-red hover:bg-re-red-hover text-white">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Relocation Request
|
||||
</Button>
|
||||
@ -443,7 +442,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-amber-600 hover:bg-amber-700 text-white"
|
||||
className="bg-re-red hover:bg-re-red-hover text-white"
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? (
|
||||
@ -522,7 +521,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-32 text-center">
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
<Loader2 className="w-6 h-6 text-amber-600 animate-spin" />
|
||||
<Loader2 className="w-6 h-6 text-re-red animate-spin" />
|
||||
<p className="text-slate-500 text-sm">Loading requests...</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
@ -550,7 +549,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
<span>{request.currentLocation}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-slate-900 text-sm">
|
||||
<Navigation className="w-3 h-3 text-amber-600" />
|
||||
<Navigation className="w-3 h-3 text-re-red" />
|
||||
<span className="text-slate-500">To:</span>
|
||||
<span>{request.proposedLocation}</span>
|
||||
</div>
|
||||
@ -563,7 +562,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SlaBadge status={getSla('relocation', request.id || request.requestId)} compact />
|
||||
<Badge className={`border ${getStatusColor(request.currentStage)}`}>
|
||||
<Badge className={getStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
@ -571,7 +570,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-600 transition-all duration-300"
|
||||
className={`h-full transition-all duration-300 ${getStatusProgressBarClass(request.status, request.currentStage)}`}
|
||||
style={{ width: `${request.progressPercentage || 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
@ -616,7 +615,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="h-32 text-center">
|
||||
<Loader2 className="w-6 h-6 text-amber-600 animate-spin mx-auto" />
|
||||
<Loader2 className="w-6 h-6 text-re-red animate-spin mx-auto" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@ -639,7 +638,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SlaBadge status={getSla('relocation', request.id || request.requestId)} compact />
|
||||
<Badge className={`border ${getStatusColor(request.currentStage)}`}>
|
||||
<Badge className={getStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
@ -678,7 +677,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="h-32 text-center">
|
||||
<Loader2 className="w-6 h-6 text-amber-600 animate-spin mx-auto" />
|
||||
<Loader2 className="w-6 h-6 text-re-red animate-spin mx-auto" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@ -703,7 +702,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-red-500 transition-all duration-300"
|
||||
className={`h-full transition-all duration-300 ${getStatusProgressBarClass(request.status, request.currentStage)}`}
|
||||
style={{ width: `${request.progressPercentage || 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
@ -712,7 +711,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SlaBadge status={getSla('relocation', request.id || request.requestId)} compact />
|
||||
<Badge className={`border ${getStatusColor(request.currentStage)}`}>
|
||||
<Badge className={getStageBadgeClass(request.currentStage, request.status)}>
|
||||
{request.currentStage}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
@ -750,7 +749,7 @@ export function RelocationRequestPage({ currentUser, onViewDetails }: Relocation
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="h-32 text-center">
|
||||
<Loader2 className="w-6 h-6 text-amber-600 animate-spin mx-auto" />
|
||||
<Loader2 className="w-6 h-6 text-re-red animate-spin mx-auto" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
|
||||
@ -503,7 +503,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
if (isLoading && !resignationData) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-amber-600" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-re-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -692,7 +692,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<div className="flex flex-col items-center">
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${
|
||||
status === 'completed' ? 'bg-green-100 text-green-600' :
|
||||
status === 'active' ? 'bg-blue-100 text-amber-600' :
|
||||
status === 'active' ? 'bg-blue-100 text-re-red' :
|
||||
'bg-slate-100 text-slate-400'
|
||||
}`}>
|
||||
{status === 'completed' ? (
|
||||
@ -712,13 +712,13 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className={
|
||||
status === 'completed' ? 'text-green-600' :
|
||||
status === 'active' ? 'text-amber-600' :
|
||||
status === 'active' ? 'text-re-red' :
|
||||
'text-slate-400'
|
||||
}>{stage.name}</h3>
|
||||
{stageDocumentCount > 0 && (
|
||||
<button
|
||||
onClick={() => handleViewStageDocuments(stage.name, stage.key)}
|
||||
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"
|
||||
className="flex items-center gap-1 px-2 py-1 rounded-full bg-red-50 hover:bg-red-100 text-re-red-hover text-xs transition-colors cursor-pointer"
|
||||
>
|
||||
<FileText className="w-3 h-3" />
|
||||
<span>{stageDocumentCount} {stageDocumentCount === 1 ? 'doc' : 'docs'}</span>
|
||||
@ -774,7 +774,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<CardTitle>Documents</CardTitle>
|
||||
<CardDescription>View and manage resignation documents</CardDescription>
|
||||
</div>
|
||||
<Button size="sm" onClick={() => setShowUploadDialog(true)} className="bg-amber-600 hover:bg-amber-700">
|
||||
<Button size="sm" onClick={() => setShowUploadDialog(true)} className="bg-re-red hover:bg-re-red-hover">
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
Upload Document
|
||||
</Button>
|
||||
@ -899,7 +899,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
${(log.description || log.action || log.details?.action || '').toLowerCase().includes('reject') || (log.description || log.action || log.details?.action || '').toLowerCase().includes('revok')
|
||||
? 'bg-red-100 text-red-700 border-red-200'
|
||||
: (log.description || log.action || log.details?.action || '').toLowerCase().includes('sent back') || (log.description || log.action || log.details?.action || '').toLowerCase().includes('send back')
|
||||
? 'bg-amber-100 text-amber-700 border-amber-200'
|
||||
? 'bg-red-50 text-re-red-hover border-red-200'
|
||||
: (log.description || log.action || log.details?.action || '').toLowerCase().includes('approv') || (log.description || log.action || log.details?.action || '').toLowerCase().includes('initi')
|
||||
? 'bg-emerald-100 text-emerald-700 border-emerald-200'
|
||||
: 'bg-slate-100 text-slate-700 border-slate-200'}
|
||||
@ -1006,7 +1006,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full text-amber-700 border-amber-300 hover:bg-amber-50"
|
||||
className="w-full text-re-red-hover border-red-300 hover:bg-red-50"
|
||||
onClick={() => {
|
||||
setUploadDocType('PPT Presentation');
|
||||
setUploadStage('DD Lead');
|
||||
@ -1072,7 +1072,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={isSubmitting}
|
||||
className="w-full text-amber-700 border-amber-300 hover:bg-amber-50"
|
||||
className="w-full text-re-red-hover border-red-300 hover:bg-red-50"
|
||||
onClick={() => handleAction('pushfnf')}
|
||||
>
|
||||
{isSubmitting && actionDialog.type === 'pushfnf' ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Send className="w-4 h-4 mr-2" />}
|
||||
@ -1108,7 +1108,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
View Work Notes
|
||||
{resignationData?.worknotes?.length > 0 && (
|
||||
<Badge className="ml-auto bg-amber-600 hover:bg-amber-700 text-white h-5 px-2">
|
||||
<Badge className="ml-auto bg-re-red hover:bg-re-red-hover text-white h-5 px-2">
|
||||
{resignationData.worknotes.length}
|
||||
</Badge>
|
||||
)}
|
||||
@ -1209,9 +1209,9 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
</div>
|
||||
) : actionDialog.type === 'pushfnf' ? (
|
||||
<div className="space-y-4">
|
||||
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div className="text-sm text-amber-800">
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-lg flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-re-red mt-0.5" />
|
||||
<div className="text-sm text-red-800">
|
||||
<p className="font-bold">Manual Trigger Notice</p>
|
||||
<p>
|
||||
Normally F&F is triggered after the {LAST_WORKING_DAY_LABEL.toLowerCase()}. Use manual
|
||||
@ -1264,7 +1264,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
className={
|
||||
actionDialog.type === 'approve' ? 'bg-green-600 hover:bg-green-700' :
|
||||
actionDialog.type === 'withdrawal' ? 'bg-red-600 hover:bg-red-700' :
|
||||
'bg-amber-600 hover:bg-amber-700'
|
||||
'bg-re-red hover:bg-re-red-hover'
|
||||
}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
@ -1292,7 +1292,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<DialogContent className={WIDE_DIALOG_CLASS}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-amber-600" />
|
||||
<FileText className="w-5 h-5 text-re-red" />
|
||||
Documents - {stageDocumentsDialog.stageName}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@ -1325,7 +1325,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-amber-600 hover:text-blue-700"
|
||||
className="text-re-red hover:text-blue-700"
|
||||
onClick={() => {
|
||||
if (!doc.filePath) return;
|
||||
const fullPath = doc.filePath.startsWith('/uploads/') && !doc.filePath.startsWith('/uploads/documents/')
|
||||
|
||||
@ -146,8 +146,8 @@ export function ResignationPage({ onViewDetails }: ResignationPageProps) {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-4 flex-1">
|
||||
<div className="p-3 bg-amber-100 rounded-lg">
|
||||
<FileText className="w-6 h-6 text-amber-600" />
|
||||
<div className="p-3 bg-red-50 rounded-lg">
|
||||
<FileText className="w-6 h-6 text-re-red" />
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
|
||||
@ -95,7 +95,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
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" />
|
||||
<Loader2 className="w-8 h-8 text-re-red animate-spin" />
|
||||
<p className="text-slate-600">Loading termination details...</p>
|
||||
</div>
|
||||
);
|
||||
@ -751,9 +751,9 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-amber-200 bg-amber-50/30">
|
||||
<Card className="border-red-200 bg-red-50/30">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-amber-900 flex items-center gap-2">
|
||||
<CardTitle className="text-red-900 flex items-center gap-2">
|
||||
<AlertTriangle className="w-5 h-5" />
|
||||
Termination Details
|
||||
</CardTitle>
|
||||
@ -762,7 +762,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-slate-600">Termination Category</Label>
|
||||
<p className="text-amber-900">{request.category}</p>
|
||||
<p className="text-red-900">{request.category}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-slate-600">Sub Category</Label>
|
||||
@ -811,7 +811,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<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' :
|
||||
stage.status === 'active' ? 'bg-red-50 text-re-red' :
|
||||
'bg-slate-100 text-slate-400'
|
||||
}`}>
|
||||
{stage.status === 'completed' ? (
|
||||
@ -833,13 +833,13 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className={
|
||||
stage.status === 'completed' ? 'text-green-600' :
|
||||
stage.status === 'active' ? 'text-amber-600' :
|
||||
stage.status === 'active' ? 'text-re-red' :
|
||||
'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"
|
||||
className="flex items-center gap-1 px-2 py-1 rounded-full bg-red-50 hover:bg-red-100 text-re-red-hover text-xs transition-colors cursor-pointer"
|
||||
>
|
||||
<FileText className="w-3 h-3" />
|
||||
<span>{documentCount} {documentCount === 1 ? 'doc' : 'docs'}</span>
|
||||
@ -884,12 +884,12 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<div className={`
|
||||
p-2.5 rounded-lg border text-sm
|
||||
${isAttachment
|
||||
? 'bg-amber-50/50 border-amber-100 text-amber-900'
|
||||
? 'bg-red-50/50 border-red-100 text-red-900'
|
||||
: 'bg-slate-50 border-slate-100 text-slate-700'}
|
||||
`}>
|
||||
{isAttachment ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="w-3.5 h-3.5 text-amber-600" />
|
||||
<FileText className="w-3.5 h-3.5 text-re-red" />
|
||||
<span className="font-medium truncate">{remarksContent}</span>
|
||||
</div>
|
||||
) : (
|
||||
@ -918,7 +918,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<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">
|
||||
<Button size="sm" onClick={() => setShowUploadDialog(true)} className="bg-re-red hover:bg-re-red-hover">
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
Upload Document
|
||||
</Button>
|
||||
@ -1012,7 +1012,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
${(log.description || log.action || log.details?.action || '').toLowerCase().includes('reject') || (log.description || log.action || log.details?.action || '').toLowerCase().includes('revok')
|
||||
? 'bg-red-100 text-red-700 border-red-200'
|
||||
: (log.description || log.action || log.details?.action || '').toLowerCase().includes('sent back') || (log.description || log.action || log.details?.action || '').toLowerCase().includes('send back')
|
||||
? 'bg-amber-100 text-amber-700 border-amber-200'
|
||||
? 'bg-red-50 text-re-red-hover border-red-200'
|
||||
: (log.description || log.action || log.details?.action || '').toLowerCase().includes('approv') || (log.description || log.action || log.details?.action || '').toLowerCase().includes('initi')
|
||||
? 'bg-emerald-100 text-emerald-700 border-emerald-200'
|
||||
: 'bg-slate-100 text-slate-700 border-slate-200'}
|
||||
@ -1085,7 +1085,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
)}
|
||||
{permissions.canUploadSCNResponse && (
|
||||
<Button
|
||||
className="w-full bg-amber-600 hover:bg-amber-700"
|
||||
className="w-full bg-re-red hover:bg-re-red-hover"
|
||||
onClick={() => {
|
||||
setScnFile(null);
|
||||
setShowSCNDialog(true);
|
||||
@ -1148,12 +1148,12 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
request.status === OFFBOARDING_STATUS.AWAITING_FNF ||
|
||||
request.status === OFFBOARDING_STATUS.AWAITING_FNF_LWD_PENDING) &&
|
||||
!permissions.isLwdReached && (
|
||||
<Alert className="border-amber-200 bg-amber-50">
|
||||
<AlertTriangle className="h-4 w-4 text-amber-700" />
|
||||
<AlertTitle className="text-amber-900">
|
||||
<Alert className="border-red-200 bg-red-50">
|
||||
<AlertTriangle className="h-4 w-4 text-re-red-hover" />
|
||||
<AlertTitle className="text-red-900">
|
||||
Push to F&F locked until {LAST_WORKING_DAY_LABEL}
|
||||
</AlertTitle>
|
||||
<AlertDescription className="text-amber-800 text-sm">
|
||||
<AlertDescription className="text-red-800 text-sm">
|
||||
{LAST_WORKING_DAY_LABEL} is{' '}
|
||||
{request.proposedLwd
|
||||
? new Date(request.proposedLwd).toLocaleDateString('en-IN', { dateStyle: 'medium' })
|
||||
@ -1190,7 +1190,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
View Work Notes
|
||||
{workNotesCount > 0 && (
|
||||
<Badge className="ml-auto bg-amber-600 hover:bg-amber-700 text-white h-5 px-2">
|
||||
<Badge className="ml-auto bg-re-red hover:bg-re-red-hover text-white h-5 px-2">
|
||||
{workNotesCount}
|
||||
</Badge>
|
||||
)}
|
||||
@ -1308,7 +1308,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<DialogContent className={WIDE_DIALOG_CLASS}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-amber-600" />
|
||||
<FileText className="w-5 h-5 text-re-red" />
|
||||
Documents - {stageDocumentsDialog.stageName}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@ -1341,7 +1341,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-amber-600 hover:text-amber-700"
|
||||
className="text-re-red hover:text-re-red-hover"
|
||||
onClick={() => {
|
||||
const path = doc.path;
|
||||
if (!path) return;
|
||||
@ -1426,7 +1426,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className={isScnStage ? 'bg-amber-600 hover:bg-amber-700' : 'bg-purple-600 hover:bg-purple-700'}
|
||||
className={isScnStage ? 'bg-re-red hover:bg-re-red-hover' : 'bg-purple-600 hover:bg-purple-700'}
|
||||
onClick={isScnStage ? handleUploadSCNResponse : handleIssueSCN}
|
||||
disabled={isProcessing || (isScnStage && !scnFile)}
|
||||
>
|
||||
@ -1454,9 +1454,9 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
|
||||
<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="Approve" className="text-re-red-hover focus:bg-red-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>
|
||||
<SelectItem value="Reconsider" className="text-re-red focus:bg-red-50">Reconsider / Give More Time</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,6 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { API } from '@/api/API';
|
||||
import { slaService, SlaStatusSnapshot } from '@/services/sla.service';
|
||||
@ -291,13 +290,7 @@ export function TerminationPage({ currentUser, onViewDetails }: TerminationPageP
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Warning Alert */}
|
||||
{/* <Alert className="border-red-200 bg-red-50">
|
||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||
<AlertTitle className="text-red-900">Restricted Access</AlertTitle>
|
||||
<AlertDescription className="text-red-700">
|
||||
This section contains sensitive information. All termination actions are logged and require proper authorization.
|
||||
</AlertDescription>
|
||||
</Alert> */}
|
||||
|
||||
|
||||
{/* Header Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
@ -8,6 +8,31 @@ export const OFFBOARDING_STATUS = {
|
||||
AWAITING_FNF_LWD_PENDING: 'Awaiting F&F (LWD Pending)'
|
||||
} as const;
|
||||
|
||||
export {
|
||||
isStatusTerminalNegative as isOffboardingTerminalNegative,
|
||||
isStatusTerminalSuccess as isOffboardingTerminalSuccess,
|
||||
resolveStatusProgressVariant,
|
||||
STATUS_PROGRESS_THEME,
|
||||
getStatusProgressBarClass,
|
||||
getStatusProgressBarClass as getOffboardingProgressBarFillClass,
|
||||
getStatusProgressBarClass as getWorkflowProgressBarFillClass,
|
||||
getStatusProgressBadgeSolidClass,
|
||||
getRequestStatusBadgeClass,
|
||||
getRequestStatusBadgeSolidClass,
|
||||
getRequestStatusBadgeSolidClass as getOffboardingRequestStatusBadgeClass,
|
||||
getCurrentStageBadgeClass,
|
||||
getStatusProgressBadgeClass,
|
||||
getWorkflowActiveStageTheme,
|
||||
getStatusLabelBadgeClass,
|
||||
getPercentProgressBarClass,
|
||||
getPercentProgressBadgeSolidClass,
|
||||
} from './statusProgressTheme';
|
||||
|
||||
import { STATUS_PROGRESS_THEME } from './statusProgressTheme';
|
||||
|
||||
/** Active workflow step styling (amber) — Workflow Progress tab */
|
||||
export const WORKFLOW_IN_PROGRESS_ACCENT = STATUS_PROGRESS_THEME.inProgress;
|
||||
|
||||
/** Maps backend status / stage strings to user-facing labels */
|
||||
export function formatOffboardingStatusLabel(value: string | null | undefined): string {
|
||||
if (!value) return 'Pending';
|
||||
@ -17,3 +42,18 @@ export function formatOffboardingStatusLabel(value: string | null | undefined):
|
||||
label = label.replace(/\bLWD\b/g, LAST_WORKING_DAY_LABEL);
|
||||
return label;
|
||||
}
|
||||
|
||||
/** @deprecated Use STATUS_PROGRESS_THEME from statusProgressTheme */
|
||||
export const OFFBOARDING_BRAND_ACCENT = {
|
||||
solid: 'bg-re-red hover:bg-re-red-hover text-white border-transparent',
|
||||
progress: 'bg-re-red',
|
||||
soft: 'bg-red-50 text-re-red border-red-200',
|
||||
softHover: 'hover:bg-red-50 hover:text-re-red-hover',
|
||||
text: 'text-re-red',
|
||||
textMuted: 'text-re-red-hover',
|
||||
icon: 'text-re-red',
|
||||
ring: 'focus-visible:ring-re-red',
|
||||
currentPanel: 'bg-red-50 border-red-200',
|
||||
currentIcon: 'bg-red-100 text-re-red',
|
||||
inProgressBadge: 'bg-red-100 text-re-red-hover border-red-300',
|
||||
} as const;
|
||||
|
||||
231
src/lib/statusProgressTheme.ts
Normal file
231
src/lib/statusProgressTheme.ts
Normal file
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Unified status & progress colors across the app.
|
||||
* Uses CSS classes from globals.css (amber) — NOT Tailwind bg-amber-* (remapped to re-red globally).
|
||||
*
|
||||
* - In progress / active: amber via .bg-status-progress / .bg-status-workflow-*
|
||||
* - Completed / success: green
|
||||
* - Rejected / revoked: re-red
|
||||
*
|
||||
* Primary buttons / brand UI: use bg-re-red (never amber).
|
||||
*/
|
||||
|
||||
const TERMINAL_NEGATIVE = ['Rejected', 'Revoked', 'Withdrawn'] as const;
|
||||
|
||||
export type StatusProgressVariant = 'inProgress' | 'success' | 'negative' | 'neutral';
|
||||
|
||||
export function isStatusTerminalNegative(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): boolean {
|
||||
const s = String(status || '');
|
||||
const stage = String(currentStage || '');
|
||||
return TERMINAL_NEGATIVE.some((label) => s.includes(label) || stage === label);
|
||||
}
|
||||
|
||||
export function isStatusTerminalSuccess(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): boolean {
|
||||
const s = String(status || '');
|
||||
const stage = String(currentStage || '');
|
||||
return (
|
||||
s === 'Completed' ||
|
||||
stage === 'Completed' ||
|
||||
s === 'Settled' ||
|
||||
s === 'Onboarded' ||
|
||||
s === 'Verified' ||
|
||||
s === 'Closed'
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveStatusProgressVariant(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): StatusProgressVariant {
|
||||
if (isStatusTerminalSuccess(status, currentStage)) return 'success';
|
||||
if (isStatusTerminalNegative(status, currentStage)) return 'negative';
|
||||
return 'inProgress';
|
||||
}
|
||||
|
||||
/** Semantic class sets — status/progress uses globals.css amber tokens */
|
||||
export const STATUS_PROGRESS_THEME = {
|
||||
inProgress: {
|
||||
bar: 'bg-status-progress',
|
||||
barBadge: 'bg-status-progress hover:bg-status-progress-hover text-white border-transparent',
|
||||
badge: 'bg-status-progress-soft text-status-progress-strong border-status-progress',
|
||||
badgeSolid: 'bg-status-progress hover:bg-status-progress-hover text-white border-transparent',
|
||||
text: 'text-status-progress-muted',
|
||||
textStrong: 'text-status-progress-strong',
|
||||
icon: 'text-status-progress',
|
||||
workflowIcon: 'bg-status-workflow-icon',
|
||||
workflowPanel: 'bg-status-workflow-panel -ml-4 pl-4 pr-4 py-3 rounded-lg border border-status-workflow-panel',
|
||||
workflowTitle: 'text-status-progress-strong',
|
||||
workflowSubtitle: 'text-status-progress-muted',
|
||||
workflowStageBadge: 'bg-status-workflow-badge border',
|
||||
panel: 'bg-status-workflow-panel -ml-4 pl-4 pr-4 py-3 rounded-lg border border-status-workflow-panel',
|
||||
title: 'text-status-progress-strong',
|
||||
subtitle: 'text-status-progress-muted',
|
||||
stageBadge: 'bg-status-workflow-badge border',
|
||||
progress: 'bg-status-progress',
|
||||
workflowActive: 'bg-status-workflow-active',
|
||||
},
|
||||
success: {
|
||||
bar: 'bg-green-600',
|
||||
barBadge: 'bg-green-600 hover:bg-green-700 text-white border-transparent',
|
||||
badge: 'bg-green-100 text-green-800 border-green-200',
|
||||
badgeSolid: 'bg-green-600 hover:bg-green-700 text-white border-transparent',
|
||||
text: 'text-green-700',
|
||||
textStrong: 'text-green-900',
|
||||
icon: 'text-green-600',
|
||||
workflowIcon: 'bg-green-100 text-green-600',
|
||||
workflowPanel: 'bg-green-50 border border-green-200',
|
||||
workflowTitle: 'text-green-900',
|
||||
workflowSubtitle: 'text-green-700',
|
||||
workflowStageBadge: 'bg-green-100 text-green-700 border-green-300',
|
||||
panel: 'bg-green-50 border border-green-200',
|
||||
title: 'text-green-900',
|
||||
subtitle: 'text-green-700',
|
||||
stageBadge: 'bg-green-100 text-green-700 border-green-300',
|
||||
progress: 'bg-green-600',
|
||||
workflowActive: 'bg-green-600 border-green-600',
|
||||
},
|
||||
negative: {
|
||||
bar: 'bg-re-red',
|
||||
barBadge: 'bg-re-red hover:bg-re-red-hover text-white border-transparent',
|
||||
badge: 'bg-red-50 text-re-red border-red-200',
|
||||
badgeSolid: 'bg-re-red hover:bg-re-red-hover text-white border-transparent',
|
||||
text: 'text-re-red-hover',
|
||||
textStrong: 'text-red-900',
|
||||
icon: 'text-re-red',
|
||||
workflowIcon: 'bg-red-100 text-re-red',
|
||||
workflowPanel: 'bg-red-50 border border-red-200',
|
||||
workflowTitle: 'text-red-900',
|
||||
workflowSubtitle: 'text-re-red-hover',
|
||||
workflowStageBadge: 'bg-red-50 text-re-red border-red-200',
|
||||
panel: 'bg-red-50 border border-red-200',
|
||||
title: 'text-red-900',
|
||||
subtitle: 'text-re-red-hover',
|
||||
stageBadge: 'bg-red-50 text-re-red border-red-200',
|
||||
progress: 'bg-re-red',
|
||||
workflowActive: 'bg-re-red border-re-red',
|
||||
},
|
||||
neutral: {
|
||||
bar: 'bg-slate-300',
|
||||
barBadge: 'bg-slate-500 text-white border-transparent',
|
||||
badge: 'bg-slate-100 text-slate-700 border-slate-200',
|
||||
badgeSolid: 'bg-slate-600 hover:bg-slate-700 text-white border-transparent',
|
||||
text: 'text-slate-600',
|
||||
textStrong: 'text-slate-900',
|
||||
icon: 'text-slate-500',
|
||||
workflowIcon: 'bg-slate-100 text-slate-400',
|
||||
workflowPanel: '',
|
||||
workflowTitle: 'text-slate-900',
|
||||
workflowSubtitle: 'text-slate-600',
|
||||
workflowStageBadge: 'bg-slate-100 text-slate-500 border-slate-300',
|
||||
panel: '',
|
||||
title: 'text-slate-900',
|
||||
subtitle: 'text-slate-600',
|
||||
stageBadge: 'bg-slate-100 text-slate-500 border-slate-300',
|
||||
progress: 'bg-slate-300',
|
||||
workflowActive: 'bg-slate-400 border-slate-400',
|
||||
},
|
||||
} as const;
|
||||
|
||||
function themeFor(status: string | null | undefined, currentStage?: string | null | undefined) {
|
||||
return STATUS_PROGRESS_THEME[resolveStatusProgressVariant(status, currentStage)];
|
||||
}
|
||||
|
||||
export function getStatusProgressBarClass(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): string {
|
||||
return themeFor(status, currentStage).bar;
|
||||
}
|
||||
|
||||
export function getStatusProgressBadgeSolidClass(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): string {
|
||||
return themeFor(status, currentStage).barBadge;
|
||||
}
|
||||
|
||||
export function getStatusProgressBadgeClass(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): string {
|
||||
return themeFor(status, currentStage).badge;
|
||||
}
|
||||
|
||||
export function getWorkflowActiveStageTheme() {
|
||||
return STATUS_PROGRESS_THEME.inProgress;
|
||||
}
|
||||
|
||||
/** Progress bar / badge from completion % (EOR checklist, sub-trackers, etc.) */
|
||||
export function getPercentProgressBarClass(percent: number): string {
|
||||
if (percent >= 100) return STATUS_PROGRESS_THEME.success.bar;
|
||||
return STATUS_PROGRESS_THEME.inProgress.bar;
|
||||
}
|
||||
|
||||
export function getPercentProgressBadgeSolidClass(percent: number): string {
|
||||
if (percent >= 100) return STATUS_PROGRESS_THEME.success.barBadge;
|
||||
return STATUS_PROGRESS_THEME.inProgress.barBadge;
|
||||
}
|
||||
|
||||
export function getStatusLabelBadgeClass(status: string | null | undefined): string {
|
||||
const s = String(status || '');
|
||||
const upper = s.toUpperCase();
|
||||
if (s.includes('Rejected') || upper.includes('DOCUMENT_REJECTED')) {
|
||||
return STATUS_PROGRESS_THEME.negative.badge;
|
||||
}
|
||||
if (s === 'Verified' || s === 'Completed' || upper.includes('DOCUMENT_VERIFIED')) {
|
||||
return STATUS_PROGRESS_THEME.success.badge;
|
||||
}
|
||||
if (s.includes('Pending') || s.includes('Review') || s === 'In Progress') {
|
||||
return STATUS_PROGRESS_THEME.inProgress.badge;
|
||||
}
|
||||
return STATUS_PROGRESS_THEME.neutral.badge;
|
||||
}
|
||||
|
||||
/** Request status on listing/detail header — re-red while active (not amber). */
|
||||
export function getRequestStatusBadgeClass(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): string {
|
||||
if (isStatusTerminalSuccess(status, currentStage)) {
|
||||
return STATUS_PROGRESS_THEME.success.badge;
|
||||
}
|
||||
if (isStatusTerminalNegative(status, currentStage)) {
|
||||
return STATUS_PROGRESS_THEME.negative.badge;
|
||||
}
|
||||
return STATUS_PROGRESS_THEME.negative.badge;
|
||||
}
|
||||
|
||||
/** Solid request status pill (detail header). */
|
||||
export function getRequestStatusBadgeSolidClass(
|
||||
status: string | null | undefined,
|
||||
currentStage?: string | null | undefined
|
||||
): string {
|
||||
if (isStatusTerminalSuccess(status, currentStage)) {
|
||||
return STATUS_PROGRESS_THEME.success.badgeSolid;
|
||||
}
|
||||
if (isStatusTerminalNegative(status, currentStage)) {
|
||||
return STATUS_PROGRESS_THEME.negative.badgeSolid;
|
||||
}
|
||||
return STATUS_PROGRESS_THEME.negative.badgeSolid;
|
||||
}
|
||||
|
||||
/** Current stage chip (e.g. ASM Review) — brand re-red, not amber/yellow. */
|
||||
export function getCurrentStageBadgeClass(
|
||||
stage: string | null | undefined,
|
||||
requestStatus?: string | null | undefined
|
||||
): string {
|
||||
const s = String(stage || '');
|
||||
const status = String(requestStatus || '');
|
||||
if (isStatusTerminalNegative(status, s) || /rejected|revoked/i.test(s)) {
|
||||
return STATUS_PROGRESS_THEME.negative.badge;
|
||||
}
|
||||
if (s === 'Completed' || status === 'Completed' || s === 'Closed') {
|
||||
return STATUS_PROGRESS_THEME.success.badge;
|
||||
}
|
||||
return STATUS_PROGRESS_THEME.negative.badge;
|
||||
}
|
||||
@ -114,7 +114,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
|
||||
if (loading) return (
|
||||
<div className="flex items-center justify-center h-screen bg-slate-50">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-amber-600"></div>
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-re-red"></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -210,8 +210,8 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
<div className="flex items-center justify-center mb-7">
|
||||
<img src="/assets/images/Re_Logo.png" alt="Royal Enfield" className="h-12 w-auto" />
|
||||
</div>
|
||||
<div className="h-1 w-24 bg-amber-600 mx-auto mb-4"></div>
|
||||
<h2 className="text-amber-400 text-xl mb-4 font-light">Dealership Partner Application</h2>
|
||||
<div className="h-1 w-24 bg-re-red mx-auto mb-4"></div>
|
||||
<h2 className="text-re-red text-xl mb-4 font-light">Dealership Partner Application</h2>
|
||||
<p className="text-slate-300 max-w-2xl mx-auto leading-relaxed text-sm">
|
||||
Thank you for your interest in becoming a Royal Enfield business partner.
|
||||
Please complete this questionnaire to help us understand your profile and aspirations.
|
||||
@ -219,12 +219,12 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
|
||||
<div className="flex items-center justify-center gap-8 mt-8 border-t border-slate-700/50 pt-6 inline-flex mx-auto">
|
||||
<div className="text-center px-4">
|
||||
<div className="text-amber-400 text-2xl font-bold">{questions.length}</div>
|
||||
<div className="text-re-red text-2xl font-bold">{questions.length}</div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider">Questions</div>
|
||||
</div>
|
||||
<div className="h-10 w-px bg-slate-700"></div>
|
||||
<div className="text-center px-4">
|
||||
<div className="text-amber-400 text-2xl font-bold">{sections.length}</div>
|
||||
<div className="text-re-red text-2xl font-bold">{sections.length}</div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider">Sections</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -240,7 +240,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
onClick={() => setActiveSection(section)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg whitespace-nowrap transition-all text-sm font-medium
|
||||
${activeSection === section
|
||||
? 'bg-amber-600 text-white shadow-lg'
|
||||
? 'bg-re-red text-white shadow-lg'
|
||||
: 'bg-slate-700/50 text-slate-300 hover:bg-slate-700 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
@ -260,8 +260,8 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
{/* Question Content */}
|
||||
<div className="bg-white rounded-b-lg shadow-xl border border-slate-200 border-t-0 min-h-[400px]">
|
||||
<div className="p-8">
|
||||
<div className="flex items-start gap-4 pb-6 border-b-2 border-amber-100 mb-8">
|
||||
<div className="w-12 h-12 bg-amber-50 rounded-lg flex items-center justify-center flex-shrink-0 text-amber-600">
|
||||
<div className="flex items-start gap-4 pb-6 border-b-2 border-red-100 mb-8">
|
||||
<div className="w-12 h-12 bg-red-50 rounded-lg flex items-center justify-center flex-shrink-0 text-re-red">
|
||||
<Users className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
@ -276,7 +276,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
{activeQuestions.map((q, idx) => (
|
||||
<div key={q.id} className="group animate-in fade-in duration-500" style={{ animationDelay: `${idx * 100}ms` }}>
|
||||
<div className="flex items-start gap-5">
|
||||
<div className="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center flex-shrink-0 group-hover:bg-amber-100 transition-colors text-slate-600 group-hover:text-amber-700 font-semibold text-sm">
|
||||
<div className="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center flex-shrink-0 group-hover:bg-red-50 transition-colors text-slate-600 group-hover:text-re-red-hover font-semibold text-sm">
|
||||
{idx + 1}
|
||||
</div>
|
||||
<div className="flex-1 space-y-3">
|
||||
@ -291,7 +291,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
{(q.inputType === 'text' || q.inputType === 'email' || q.inputType === 'number') && (
|
||||
<input
|
||||
type={q.inputType}
|
||||
className="w-full h-10 px-3 rounded-lg border border-slate-300 focus:border-amber-500 focus:ring-2 focus:ring-amber-200 outline-none transition-all placeholder:text-slate-400"
|
||||
className="w-full h-10 px-3 rounded-lg border border-slate-300 focus:border-re-red focus:ring-2 focus:ring-red-200 outline-none transition-all placeholder:text-slate-400"
|
||||
placeholder="Type your answer here..."
|
||||
value={responses[q.id] || ''}
|
||||
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||
@ -300,7 +300,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
|
||||
{q.inputType === 'textarea' && (
|
||||
<textarea
|
||||
className="w-full h-32 p-3 rounded-lg border border-slate-300 focus:border-amber-500 focus:ring-2 focus:ring-amber-200 outline-none transition-all placeholder:text-slate-400"
|
||||
className="w-full h-32 p-3 rounded-lg border border-slate-300 focus:border-re-red focus:ring-2 focus:ring-red-200 outline-none transition-all placeholder:text-slate-400"
|
||||
placeholder="Type your answer here..."
|
||||
value={responses[q.id] || ''}
|
||||
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||
@ -316,7 +316,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
<input
|
||||
type="radio"
|
||||
name={`q-${q.id}`}
|
||||
className="w-4 h-4 text-amber-600 focus:ring-amber-500 border-slate-300"
|
||||
className="w-4 h-4 text-re-red focus:ring-re-red border-slate-300"
|
||||
checked={responses[q.id] === val}
|
||||
onChange={() => handleInputChange(q.id, val)}
|
||||
/>
|
||||
@ -351,7 +351,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
||||
{currentSectionIndex < sections.length - 1 ? (
|
||||
<button
|
||||
onClick={handleNextSection}
|
||||
className="px-6 py-2.5 rounded-lg text-sm font-medium bg-amber-600 text-white hover:bg-amber-700 flex items-center gap-2 transition-colors shadow-md hover:shadow-lg"
|
||||
className="px-6 py-2.5 rounded-lg text-sm font-medium bg-re-red text-white hover:bg-re-red-hover flex items-center gap-2 transition-colors shadow-md hover:shadow-lg"
|
||||
>
|
||||
Next Section
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
|
||||
@ -51,6 +51,14 @@
|
||||
--re-black: #000000;
|
||||
--re-white: #FFFFFF;
|
||||
--re-gray: #717171;
|
||||
|
||||
/* Status / progress semantics (see statusProgressTheme.ts) */
|
||||
--status-in-progress: #d97706;
|
||||
--status-in-progress-hover: #b45309;
|
||||
--status-success: #16a34a;
|
||||
--status-success-hover: #15803d;
|
||||
--status-negative: var(--re-red);
|
||||
--status-negative-hover: var(--re-red-hover);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@ -142,6 +150,9 @@
|
||||
--color-re-red-hover: var(--re-red-hover);
|
||||
--color-re-black: var(--re-black);
|
||||
--color-re-gray: var(--re-gray);
|
||||
--color-status-in-progress: var(--status-in-progress);
|
||||
--color-status-success: var(--status-success);
|
||||
--color-status-negative: var(--status-negative);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@ -155,12 +166,76 @@
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
button.bg-amber-600 {
|
||||
/* ─── Status / progress only (amber) — use these instead of bg-amber-* for trackers ─── */
|
||||
.bg-status-progress {
|
||||
background-color: var(--status-in-progress);
|
||||
}
|
||||
.hover\:bg-status-progress-hover:hover {
|
||||
background-color: var(--status-in-progress-hover);
|
||||
}
|
||||
.text-status-progress {
|
||||
color: var(--status-in-progress);
|
||||
}
|
||||
.text-status-progress-muted {
|
||||
color: var(--status-in-progress-hover);
|
||||
}
|
||||
.text-status-progress-strong {
|
||||
color: #92400e;
|
||||
}
|
||||
.bg-status-progress-soft {
|
||||
background-color: #fffbeb;
|
||||
}
|
||||
.border-status-progress {
|
||||
border-color: #fde68a;
|
||||
}
|
||||
.bg-status-workflow-icon {
|
||||
background-color: #fef3c7;
|
||||
color: var(--status-in-progress);
|
||||
}
|
||||
.bg-status-workflow-panel {
|
||||
background-color: #fffbeb;
|
||||
border-color: #fde68a;
|
||||
}
|
||||
.border-status-workflow-panel {
|
||||
border-color: #fde68a;
|
||||
}
|
||||
.bg-status-workflow-badge {
|
||||
background-color: #fef3c7;
|
||||
color: #b45309;
|
||||
border-color: #fcd34d;
|
||||
}
|
||||
.bg-status-workflow-active {
|
||||
background-color: var(--status-in-progress);
|
||||
border-color: var(--status-in-progress);
|
||||
}
|
||||
.ring-status-workflow-active {
|
||||
--tw-ring-color: var(--status-in-progress);
|
||||
}
|
||||
|
||||
/* ─── Brand primary: legacy amber CTAs → re-red ─── */
|
||||
.btn-re-primary,
|
||||
button.bg-amber-600,
|
||||
a.bg-amber-600,
|
||||
[data-slot="button"].bg-amber-600 {
|
||||
background-color: var(--color-re-red) !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-re-primary:hover,
|
||||
button.bg-amber-600:hover,
|
||||
button.hover\:bg-amber-700:hover,
|
||||
a.bg-amber-600:hover,
|
||||
a.hover\:bg-amber-700:hover {
|
||||
background-color: var(--color-re-red-hover) !important;
|
||||
}
|
||||
|
||||
/* Non-button amber-600 (badges, divs) → re-red unless using status-progress-* classes */
|
||||
.bg-amber-600:not(.bg-status-progress):not(.bg-status-workflow-active):not(.bg-status-workflow-badge):not(.bg-status-workflow-icon) {
|
||||
background-color: var(--color-re-red) !important;
|
||||
}
|
||||
|
||||
button.hover\:bg-amber-700:hover {
|
||||
background-color: var(--color-re-red) !important;
|
||||
.hover\:bg-amber-700:hover:not(.hover\:bg-status-progress-hover) {
|
||||
background-color: var(--color-re-red-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user