added joint approval in resignation for RBM DD-ZM approval stage

This commit is contained in:
laxman h 2026-04-29 16:37:06 +05:30
parent 352c656a9e
commit 95032cf2a7
5 changed files with 187 additions and 74 deletions

View File

@ -65,6 +65,20 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
}
};
const isEmailValid = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const isMobileValid = (mobile: string) => /^[0-9]{10}$/.test(mobile);
const isPincodeValid = (pincode: string) => /^[0-9]{6}$/.test(pincode);
const isFormValid = Boolean(
formData.country && formData.stateId && formData.districtId && formData.name &&
formData.interestedCity && formData.email && formData.pincode && formData.mobile &&
formData.ownRoyalEnfield && formData.age && formData.education &&
formData.companyName && formData.source && formData.existingDealer &&
formData.description && formData.address && formData.acceptTerms &&
otpVerified && isEmailValid(formData.email) && isMobileValid(formData.mobile) && isPincodeValid(formData.pincode) &&
(formData.ownRoyalEnfield === 'no' || (formData.ownRoyalEnfield === 'yes' && formData.royalEnfieldModel))
);
const handleVerifyMobile = () => {
if (!formData.mobile || formData.mobile.length < 10) {
toast.error('Please enter a valid mobile number');
@ -93,6 +107,11 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
return;
}
if (formData.ownRoyalEnfield === 'yes' && !formData.royalEnfieldModel) {
toast.error('Please select your motorcycle model');
return;
}
if (!formData.acceptTerms) {
toast.error('Please accept the terms and conditions');
return;
@ -145,10 +164,10 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
};
const reModels = [
"Classic 650", "Scram 440", "Goan Classic 350", "Bear 650", "Guerrilla 450",
"Shotgun 650", "Himalayan 450", "Bullet 350", "Super Meteor 650", "Hunter 350",
"Scram 411", "Meteor 350", "Interceptor INT 650", "Continental GT 650",
"Classic 350", "Other Royal Enfield motorcycle"
"Continental GT", "Interceptor 650", "Himalayan", "Classic 350",
"Classic 500", "Thunderbird 350", "Thunderbird 500", "Thunderbird X 350",
"Thunderbird X 500", "Bullet 350", "Bullet 500", "Bullet ES",
"Bullet Trials 350", "Bullet Trials 500", "Other Royal Enfield motorcycle"
];
const sourceOptions = [
@ -257,24 +276,35 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
onChange={(e) => setFormData({...formData, interestedCity: e.target.value})}
/>
<Input
type="email"
placeholder="Email Id*"
className="h-[44px] border-[#cccccc] rounded-none px-4 text-[14px] focus-visible:ring-1 focus-visible:ring-black placeholder:text-[#999999]"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
/>
<Input
type="text"
maxLength={6}
placeholder="Pincode*"
className="h-[44px] border-[#cccccc] rounded-none px-4 text-[14px] focus-visible:ring-1 focus-visible:ring-black placeholder:text-[#999999]"
value={formData.pincode}
onChange={(e) => setFormData({...formData, pincode: e.target.value})}
onChange={(e) => {
const val = e.target.value.replace(/\D/g, '');
setFormData({...formData, pincode: val});
}}
/>
<div className="relative">
<Input
type="text"
maxLength={10}
placeholder="Mobile No.*"
className="h-[44px] border-[#cccccc] rounded-none px-4 text-[14px] focus-visible:ring-1 focus-visible:ring-black placeholder:text-[#999999]"
value={formData.mobile}
onChange={(e) => setFormData({...formData, mobile: e.target.value})}
onChange={(e) => {
const val = e.target.value.replace(/\D/g, '');
setFormData({...formData, mobile: val});
}}
/>
{!otpVerified ? (
<button
@ -301,7 +331,13 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
type="radio"
className="hidden"
checked={formData.ownRoyalEnfield === val}
onChange={() => setFormData({...formData, ownRoyalEnfield: val})}
onChange={() => {
setFormData({
...formData,
ownRoyalEnfield: val,
royalEnfieldModel: val === 'no' ? '' : formData.royalEnfieldModel
});
}}
/>
<span className="text-[14px] capitalize">{val}</span>
</label>
@ -316,18 +352,19 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
onChange={(e) => setFormData({...formData, age: e.target.value})}
/>
<div className="relative">
<select
className="w-full h-[44px] px-4 border border-[#cccccc] appearance-none bg-white text-[14px] outline-none disabled:bg-slate-50"
value={formData.royalEnfieldModel}
disabled={formData.ownRoyalEnfield !== 'yes'}
onChange={(e) => setFormData({...formData, royalEnfieldModel: e.target.value})}
>
<option value="">Motorcycle Owned</option>
{reModels.map(m => <option key={m} value={m}>{m}</option>)}
</select>
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500 pointer-events-none" />
</div>
{formData.ownRoyalEnfield === 'yes' && (
<div className="relative">
<select
className="w-full h-[44px] px-4 border border-[#cccccc] appearance-none bg-white text-[14px] outline-none"
value={formData.royalEnfieldModel}
onChange={(e) => setFormData({...formData, royalEnfieldModel: e.target.value})}
>
<option value="">Select Motorcycle*</option>
{reModels.map(m => <option key={m} value={m}>{m}</option>)}
</select>
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500 pointer-events-none" />
</div>
)}
<Input
placeholder="Education Qualification*"
@ -406,14 +443,15 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
onCheckedChange={(checked) => setFormData({...formData, acceptTerms: checked as boolean})}
/>
<label htmlFor="terms" className="text-[14px] font-medium cursor-pointer">
I accept the <b>terms and conditions</b> as well as <b>privacy policy</b>.
I accept the <b>terms and conditions</b> as well as <b>privacy policy</b>.<span className="text-red-500">*</span>
</label>
</div>
</div>
<button
type="submit"
className="h-12 px-10 bg-black text-white flex items-center gap-3 hover:bg-slate-900 transition-colors"
disabled={!isFormValid}
className="h-12 px-10 bg-black text-white flex items-center gap-3 hover:bg-slate-900 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<span className="font-bold uppercase tracking-wider text-[14px]">Submit</span>
<ChevronRight className="w-4 h-4" />

View File

@ -489,7 +489,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
</button>
</div>
<p className="text-slate-400 text-[10px] mt-1" data-testid={`onboarding-progress-branch-stage-status-${branchKey}-${bsIdx}`}>
{isDone && branchStage.date ? `Done: ${formatDateTime(branchStage.date)}` : isDone && stageDocs.length > 0 ? `Uploaded: ${formatDateTime(stageDocs[0].updatedAt || stageDocs[0].createdAt)}` : branchStage.status === 'active' ? 'Evaluating' : 'Pending'}
{isDone && branchStage.date ? `Done: ${formatDateTime(branchStage.date)}` : isDone && stageDocs.length > 0 ? `Uploaded: ${formatDateTime(stageDocs[0].updatedAt || stageDocs[0].createdAt)}` : 'Pending'}
</p>
</div>
</>
@ -558,14 +558,14 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
<TableCell>
<div className="flex justify-end gap-2">
<Button size="sm" variant="outline" data-testid={`onboarding-document-preview-${idx}`} onClick={() => {
setPreviewDoc(doc);
setShowPreviewModal(true);
setPreviewDoc(doc);
setShowPreviewModal(true);
}}>
<Eye className="w-3 h-3 text-slate-500" />
</Button>
<Button size="sm" variant="outline" data-testid={`onboarding-document-download-${idx}`} onClick={() => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';
window.open(`${baseUrl}/${doc.filePath}`, '_blank');
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';
window.open(`${baseUrl}/${doc.filePath}`, '_blank');
}}>
<Download className="w-3 h-3 text-slate-500" />
</Button>

View File

@ -62,9 +62,9 @@ export function useApplicationDetailsStageData({
date: application.level1InterviewDate, description: 'DD-ZM + RBM evaluation',
evaluators: Array.from(new Set(
(application.participants || [])
.filter((p: any) =>
p.metadata?.interviewLevel === 1 ||
p.metadata?.interviewLevel === '1' ||
.filter((p: any) =>
p.metadata?.interviewLevel === 1 ||
p.metadata?.interviewLevel === '1' ||
p.metadata?.allAssignments?.includes(1) ||
p.metadata?.allAssignments?.includes('1') ||
hasAnyRole(p, ['DD-ZM', 'RBM'])
@ -78,9 +78,9 @@ export function useApplicationDetailsStageData({
date: application.level2InterviewDate, description: 'DD Lead + ZBH evaluation',
evaluators: Array.from(new Set(
(application.participants || [])
.filter((p: any) =>
p.metadata?.interviewLevel === 2 ||
p.metadata?.interviewLevel === '2' ||
.filter((p: any) =>
p.metadata?.interviewLevel === 2 ||
p.metadata?.interviewLevel === '2' ||
p.metadata?.allAssignments?.includes(2) ||
p.metadata?.allAssignments?.includes('2') ||
hasAnyRole(p, ['DD Lead', 'ZBH'])
@ -94,9 +94,9 @@ export function useApplicationDetailsStageData({
date: application.level3InterviewDate, description: 'NBH + DD Head evaluation',
evaluators: Array.from(new Set(
(application.participants || [])
.filter((p: any) =>
p.metadata?.interviewLevel === 3 ||
p.metadata?.interviewLevel === '3' ||
.filter((p: any) =>
p.metadata?.interviewLevel === 3 ||
p.metadata?.interviewLevel === '3' ||
p.metadata?.allAssignments?.includes(3) ||
p.metadata?.allAssignments?.includes('3') ||
hasAnyRole(p, ['NBH', 'DD Head'])
@ -120,15 +120,16 @@ export function useApplicationDetailsStageData({
id: 10, name: 'LOI Issue', status: getStageStatus('LOI Issue'),
date: application.loiIssueDate, description: 'Letter of Intent issued', isParallel: true,
branches: [
{ name: 'LOI Documents', color: 'blue', stages:
documentConfigs.some((c: any) => c.stageCode === 'LOI Issue')
? documentConfigs.filter((c: any) => c.stageCode === 'LOI Issue').map((c: any, i: number) => ({
{
name: 'LOI Documents', color: 'green', stages:
documentConfigs.some((c: any) => c.stageCode === 'LOI Issue')
? documentConfigs.filter((c: any) => c.stageCode === 'LOI Issue').map((c: any, i: number) => ({
id: `10a-${i}`,
name: c.documentType,
status: isDocumentUploaded(c.documentType) ? 'completed' : 'active',
description: c.isMandatory ? `Upload ${c.documentType} (Mandatory)` : `Upload ${c.documentType}`
}))
: [
: [
{ id: '10a-1', name: 'Letter of Intent', status: isDocumentUploaded('Letter of Intent') || isDocumentUploaded('LOI') ? 'completed' : 'active', description: 'Letter of Intent document' },
{ id: '10a-2', name: 'Signed LOI', status: isDocumentUploaded('Signed LOI') || isDocumentUploaded('LOI Signed Copy') ? 'completed' : 'active', description: 'Signed Letter of Intent' },
]
@ -139,24 +140,28 @@ export function useApplicationDetailsStageData({
id: 11, name: 'Dealer Code Generation', status: getStageStatus('Dealer Code Generation'),
date: application.dealerCodeDate, description: 'Dealer code generated and assigned', isParallel: true,
branches: [
{ name: 'Architectural Work', color: 'green', stages: [
{ id: '11a-1', name: 'Architecture Assignment', status: application.architectureAssignedTo ? 'completed' : application.status === 'Architecture Team Assigned' ? 'active' : 'pending', description: 'Assigned to architecture team' },
{ id: '11a-2', name: 'Site Plan Blueprint', status: isDocumentUploaded('Architecture Blueprint') ? 'completed' : application.architectureAssignedTo ? 'active' : 'pending', description: 'Blueprints and site plans' },
{ id: '11a-3', name: 'Architecture Work', status: application.architectureStatus === 'COMPLETED' ? 'completed' : (application.architectureStatus === 'IN_PROGRESS' || isDocumentUploaded('Architecture Blueprint')) ? 'active' : 'pending', description: 'Final architecture approval' },
]},
{ name: 'Statutory Documents', color: 'green', stages: [
{ id: '11b-1', name: 'GST', status: isDocumentUploaded('GST Certificate') || isDocumentUploaded('GST') ? 'completed' : 'active', description: 'GST certificate' },
{ id: '11b-2', name: 'PAN', status: isDocumentUploaded('PAN Card') || isDocumentUploaded('PAN') ? 'completed' : 'active', description: 'PAN card' },
{ id: '11b-3', name: 'Nodal Agreement', status: isDocumentUploaded('Nodal Agreement') ? 'completed' : 'active', description: 'Nodal agreement document' },
{ id: '11b-4', name: 'Cancelled Check', status: isDocumentUploaded('Cancelled Check') ? 'completed' : 'active', description: 'Cancelled check copy' },
{ id: '11b-5', name: 'Partnership Deed/LLP/MOA/AOA/COI', status: isDocumentUploaded('Partnership Deed/LLP/MOA/AOA/COI') || isDocumentUploaded('Partnership Deed') ? 'completed' : 'active', description: 'Business entity documents' },
{ id: '11b-6', name: 'Firm Registration Certificate', status: isDocumentUploaded('Firm Registration Certificate') || isDocumentUploaded('Firm Registration') ? 'completed' : 'active', description: 'Firm registration certificate' },
{ id: '11b-7', name: 'Rental agreement/ Lease agreement / Own/ Land agreement', status: isDocumentUploaded('Rental agreement/ Lease agreement / Own/ Land agreement') || isDocumentUploaded('Property Document') ? 'completed' : 'active', description: 'Property agreement document' },
{ id: '11b-8', name: 'Virtual Code', status: isDocumentUploaded('Virtual Code') || isDocumentUploaded('Virtual Code Confirmation') ? 'completed' : 'active', description: 'Virtual code availability' },
{ id: '11b-9', name: 'Domain ID', status: isDocumentUploaded('Domain ID') || isDocumentUploaded('Domain ID Setup') ? 'completed' : 'active', description: 'Domain ID setup' },
{ id: '11b-10', name: 'MSD Configuration', status: isDocumentUploaded('MSD Configuration') ? 'completed' : 'active', description: 'Microsoft Dynamics configuration' },
{ id: '11b-11', name: 'LOI Acknowledgement Copy', status: isDocumentUploaded('LOI Acknowledgement Copy') || isDocumentUploaded('LOI Acknowledgement') ? 'completed' : 'active', description: 'LOI acknowledgement copy' },
]},
{
name: 'Architectural Work', color: 'green', stages: [
{ id: '11a-1', name: 'Architecture Assignment', status: application.architectureAssignedTo ? 'completed' : application.status === 'Architecture Team Assigned' ? 'active' : 'pending', description: 'Assigned to architecture team' },
{ id: '11a-2', name: 'Site Plan Blueprint', status: isDocumentUploaded('Architecture Blueprint') ? 'completed' : application.architectureAssignedTo ? 'active' : 'pending', description: 'Blueprints and site plans' },
{ id: '11a-3', name: 'Architecture Work', status: application.architectureStatus === 'COMPLETED' ? 'completed' : (application.architectureStatus === 'IN_PROGRESS' || isDocumentUploaded('Architecture Blueprint')) ? 'active' : 'pending', description: 'Final architecture approval' },
]
},
{
name: 'Statutory Documents', color: 'green', stages: [
{ id: '11b-1', name: 'GST', status: isDocumentUploaded('GST Certificate') || isDocumentUploaded('GST') ? 'completed' : 'active', description: 'GST certificate' },
{ id: '11b-2', name: 'PAN', status: isDocumentUploaded('PAN Card') || isDocumentUploaded('PAN') ? 'completed' : 'active', description: 'PAN card' },
{ id: '11b-3', name: 'Nodal Agreement', status: isDocumentUploaded('Nodal Agreement') ? 'completed' : 'active', description: 'Nodal agreement document' },
{ id: '11b-4', name: 'Cancelled Check', status: isDocumentUploaded('Cancelled Check') ? 'completed' : 'active', description: 'Cancelled check copy' },
{ id: '11b-5', name: 'Partnership Deed/LLP/MOA/AOA/COI', status: isDocumentUploaded('Partnership Deed/LLP/MOA/AOA/COI') || isDocumentUploaded('Partnership Deed') ? 'completed' : 'active', description: 'Business entity documents' },
{ id: '11b-6', name: 'Firm Registration Certificate', status: isDocumentUploaded('Firm Registration Certificate') || isDocumentUploaded('Firm Registration') ? 'completed' : 'active', description: 'Firm registration certificate' },
{ id: '11b-7', name: 'Rental agreement/ Lease agreement / Own/ Land agreement', status: isDocumentUploaded('Rental agreement/ Lease agreement / Own/ Land agreement') || isDocumentUploaded('Property Document') ? 'completed' : 'active', description: 'Property agreement document' },
{ id: '11b-8', name: 'Virtual Code', status: isDocumentUploaded('Virtual Code') || isDocumentUploaded('Virtual Code Confirmation') ? 'completed' : 'active', description: 'Virtual code availability' },
{ id: '11b-9', name: 'Domain ID', status: isDocumentUploaded('Domain ID') || isDocumentUploaded('Domain ID Setup') ? 'completed' : 'active', description: 'Domain ID setup' },
{ id: '11b-10', name: 'MSD Configuration', status: isDocumentUploaded('MSD Configuration') ? 'completed' : 'active', description: 'Microsoft Dynamics configuration' },
{ id: '11b-11', name: 'LOI Acknowledgement Copy', status: isDocumentUploaded('LOI Acknowledgement Copy') || isDocumentUploaded('LOI Acknowledgement') ? 'completed' : 'active', description: 'LOI acknowledgement copy' },
]
},
]
},
{

View File

@ -45,7 +45,7 @@ const TERMINAL_STAGE_LABELS = ['REJECTED', 'Rejected', 'REVOKED', 'Revoked', 'WI
const RESIGNATION_STAGE_ALIASES: Record<string, string[]> = {
'ASM': ['ASM', 'ASM Review', 'Submission', 'Submitted'],
'RBM': ['RBM', 'RBM Review', 'Regional Review'],
'RBM': ['RBM', 'RBM Review', 'Regional Review', 'RBM + DD-ZM Review'],
'ZBH': ['ZBH', 'ZBH Review', 'ZM Review'],
'DD Lead': ['DD Lead', 'DD Lead Review', 'DDL Review'],
'NBH': ['NBH', 'NBH Approval', 'NBH Review'],
@ -128,7 +128,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
// Progress stages logic based on live data
const progressStages = [
{ id: 1, name: 'ASM Review', key: 'ASM', description: 'Area Sales Manager review' },
{ id: 2, name: 'RBM Review', key: 'RBM', description: 'Regional Business Manager evaluation' },
{ id: 2, name: 'RBM + DD-ZM Review', key: 'RBM', description: 'Joint approval by Regional Business Manager and DD-ZM' },
{ id: 3, name: 'ZBH Review', key: 'ZBH', description: 'Zonal Business Head approval' },
{ id: 4, name: 'DD Lead Review', key: 'DD Lead', description: 'DD Lead final review' },
{ id: 5, name: 'NBH Approval', key: 'NBH', description: 'National Business Head approval' },
@ -203,10 +203,11 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
};
const permissions = getResignationPermissions();
const isNationalLevel = ['Super Admin', 'DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Legal Admin'].includes(currentUser?.role || '');
const stageAliases: Record<string, string[]> = {
'ASM': ['ASM', 'ASM Review', 'Request Initiated'],
'RBM': ['RBM', 'RBM Review'],
'RBM': ['RBM', 'RBM Review', 'RBM + DD-ZM Review'],
'ZBH': ['ZBH', 'ZBH Review'],
'DD Lead': ['DD Lead', 'DD Lead Review', 'Lead Review'],
'NBH': ['NBH', 'NBH Approval', 'NBH Review'],
@ -562,6 +563,9 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
<TabsTrigger value="progress" className="data-[state=active]:bg-white">Progress</TabsTrigger>
<TabsTrigger value="documents" className="data-[state=active]:bg-white">Documents</TabsTrigger>
<TabsTrigger value="audit" className="data-[state=active]:bg-white">Audit Trail</TabsTrigger>
{isNationalLevel && (
<TabsTrigger value="approvals" className="data-[state=active]:bg-white">Approval Summary</TabsTrigger>
)}
</TabsList>
{/* Details Tab */}
@ -750,19 +754,27 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
</div>
<p className="text-slate-600 text-sm mb-1">{stage.description}</p>
{timelineEntry && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<Badge variant="secondary" className="bg-slate-100 text-[10px] font-bold uppercase">
{timelineEntry.user || 'System'}
</Badge>
<span className="text-[10px] text-slate-500 italic">
{timelineEntry.action}
</span>
</div>
<div className="bg-slate-50 p-3 rounded-lg border border-slate-100 text-sm text-slate-700 shadow-sm">
{timelineEntry.comments || timelineEntry.remarks || 'No remarks provided.'}
</div>
{stageTimelineEntries.length > 0 && (
<div className="space-y-4 mt-3">
{stageTimelineEntries.map((entry: any, i: number) => (
<div key={i} className="space-y-2">
<div className="flex items-center gap-2">
<Badge variant="secondary" className="bg-slate-100 text-[10px] font-bold uppercase">
{entry.user || 'System'}
</Badge>
<span className="text-[10px] text-slate-500 italic">
{entry.action}
</span>
<span className="text-[10px] text-slate-400 ml-auto">
{formatDateTime(entry.timestamp || entry.createdAt)}
</span>
</div>
<div className="bg-slate-50 p-3 rounded-lg border border-slate-100 text-sm text-slate-700 shadow-sm">
{entry.comments || entry.remarks || 'No remarks provided.'}
</div>
</div>
))}
</div>
)}
</div>
@ -938,6 +950,64 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
</CardContent>
</Card>
</TabsContent>
{/* Approval Summary Tab */}
{isNationalLevel && (
<TabsContent value="approvals">
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Approval Summary</CardTitle>
<CardDescription>Comprehensive view of all approvals and remarks</CardDescription>
</div>
{permissions.canApprove && (
<Button onClick={() => handleAction('approve')} className="bg-green-600 hover:bg-green-700">
<Check className="w-4 h-4 mr-2" />
Approve Request
</Button>
)}
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Stage</TableHead>
<TableHead>Approver</TableHead>
<TableHead>Action</TableHead>
<TableHead>Remarks</TableHead>
<TableHead>Date</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(resignationData?.timeline || []).length > 0 ? (
resignationData.timeline.map((entry: any, index: number) => (
<TableRow key={index}>
<TableCell className="font-medium whitespace-nowrap">{entry.stage}</TableCell>
<TableCell>
<Badge variant="outline">{entry.user || 'System'}</Badge>
</TableCell>
<TableCell className="whitespace-nowrap">{entry.action}</TableCell>
<TableCell className="max-w-[400px]">
{entry.remarks || entry.comments || '-'}
</TableCell>
<TableCell className="text-slate-500 whitespace-nowrap">
{formatDateTime(entry.timestamp || entry.createdAt)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={5} className="text-center py-6 text-slate-500">
No approval records found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
)}
</Tabs>
{/* Action Dialogs */}

View File

@ -10,7 +10,7 @@
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: #daaa00;
--primary: #da291c;
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.95 0.0058 264.53);
--secondary-foreground: #030213;