ication service enhanced even more detailed way added more templates documentented i splitted based on modulewise joint approval added for resignation flow, upload ppt document with new docment type add for DD Lead user
This commit is contained in:
laxman h 2026-04-30 18:52:17 +05:30
parent 95032cf2a7
commit 2f82699572
12 changed files with 460 additions and 260 deletions

View File

@ -78,10 +78,15 @@ export default function App() {
const location = useLocation(); const location = useLocation();
const currentRole = currentUser?.role || currentUser?.roleCode || ''; const currentRole = currentUser?.role || currentUser?.roleCode || '';
const normalizedRole = String(currentRole).trim().toLowerCase(); const normalizedRole = String(currentRole).trim().toLowerCase();
const hasRole = (roles: string[]) => roles.map((r) => r.toLowerCase()).includes(normalizedRole); const hasRole = (roles: string[]) => {
const resignationRoles = ['DD Admin', 'ASM', 'RBM', 'DD Lead', 'ZBH', 'NBH', 'Legal', 'Legal Admin', 'Super Admin']; const normalizedTargetRoles = roles.map((r) => r.toLowerCase());
const terminationRoles = ['ASM', 'RBM', 'ZBH', 'DD Lead', 'DD Head', 'NBH', 'Legal Admin', 'Legal', 'DD Admin', 'CCO', 'CEO', 'Super Admin']; const userRole = String(currentUser?.role || '').toLowerCase();
const fnfRoles = ['DD Admin', 'DD Lead', 'NBH', 'Finance', 'Finance Admin', 'Super Admin']; const userRoleCode = String(currentUser?.roleCode || '').toLowerCase();
return normalizedTargetRoles.includes(userRole) || normalizedTargetRoles.includes(userRoleCode);
};
const resignationRoles = ['DD Admin', 'DD_ADMIN', 'ASM', 'RBM', 'DD-ZM', 'DD_ZM', 'DD Lead', 'DD_LEAD', 'DD Head', 'DD_HEAD', 'ZBH', 'NBH', 'Legal', 'Legal Admin', 'LEGAL_ADMIN', 'Super Admin', 'SUPER_ADMIN'];
const terminationRoles = ['ASM', 'RBM', 'DD-ZM', 'DD_ZM', 'ZBH', 'DD Lead', 'DD_LEAD', 'DD Head', 'DD_HEAD', 'NBH', 'Legal Admin', 'LEGAL_ADMIN', 'Legal', 'DD Admin', 'DD_ADMIN', 'CCO', 'CEO', 'Super Admin', 'SUPER_ADMIN'];
const fnfRoles = ['DD Admin', 'DD_ADMIN', 'DD-ZM', 'DD_ZM', 'DD Lead', 'DD_LEAD', 'DD Head', 'DD_HEAD', 'NBH', 'Finance', 'Finance Admin', 'FINANCE_ADMIN', 'Super Admin', 'SUPER_ADMIN'];
const financeRoles = ['Finance', 'Finance Admin']; const financeRoles = ['Finance', 'Finance Admin'];
useEffect(() => { useEffect(() => {

View File

@ -48,11 +48,16 @@ export function Sidebar({ onLogout }: SidebarProps) {
const currentRole = currentUser?.role || currentUser?.roleCode || ''; const currentRole = currentUser?.role || currentUser?.roleCode || '';
const normalizedRole = String(currentRole).trim().toLowerCase(); const normalizedRole = String(currentRole).trim().toLowerCase();
const hasRole = (roles: string[]) => roles.map((r) => r.toLowerCase()).includes(normalizedRole); const hasRole = (roles: string[]) => {
const normalizedTargetRoles = roles.map((r) => r.toLowerCase());
const userRole = String(currentUser?.role || '').toLowerCase();
const userRoleCode = String(currentUser?.roleCode || '').toLowerCase();
return normalizedTargetRoles.includes(userRole) || normalizedTargetRoles.includes(userRoleCode);
};
const resignationRoles = ['DD Admin', 'ASM', 'RBM', 'DD Lead', 'ZBH', 'NBH', 'Legal', 'Legal Admin', 'Super Admin']; const resignationRoles = ['DD Admin', 'DD_ADMIN', 'ASM', 'RBM', 'DD-ZM', 'DD_ZM', 'DD Lead', 'DD_LEAD', 'DD Head', 'DD_HEAD', 'ZBH', 'NBH', 'Legal', 'Legal Admin', 'LEGAL_ADMIN', 'Super Admin', 'SUPER_ADMIN'];
const terminationRoles = ['ASM', 'RBM', 'ZBH', 'DD Lead', 'DD Head', 'NBH', 'Legal Admin', 'Legal', 'DD Admin', 'CCO', 'CEO', 'Super Admin']; const terminationRoles = ['ASM', 'RBM', 'DD-ZM', 'DD_ZM', 'ZBH', 'DD Lead', 'DD_LEAD', 'DD Head', 'DD_HEAD', 'NBH', 'Legal Admin', 'LEGAL_ADMIN', 'Legal', 'DD Admin', 'DD_ADMIN', 'CCO', 'CEO', 'Super Admin', 'SUPER_ADMIN'];
const fnfRoles = ['DD Admin', 'DD Lead', 'NBH', 'Finance', 'Finance Admin', 'Super Admin']; const fnfRoles = ['DD Admin', 'DD_ADMIN', 'DD-ZM', 'DD_ZM', 'DD Lead', 'DD_LEAD', 'DD Head', 'DD_HEAD', 'NBH', 'Finance', 'Finance Admin', 'FINANCE_ADMIN', 'Super Admin', 'SUPER_ADMIN'];
const canSeeResignation = hasRole(resignationRoles); const canSeeResignation = hasRole(resignationRoles);
const canSeeTermination = hasRole(terminationRoles); const canSeeTermination = hasRole(terminationRoles);
const canSeeFnF = hasRole(fnfRoles); const canSeeFnF = hasRole(fnfRoles);

View File

@ -438,7 +438,6 @@ export const MasterPage: React.FC = () => {
<h1 className="text-slate-900 mb-2 font-bold text-2xl">Master Configuration</h1> <h1 className="text-slate-900 mb-2 font-bold text-2xl">Master Configuration</h1>
<p className="text-slate-600">Centralized governance for locations, roles, and operational policies</p> <p className="text-slate-600">Centralized governance for locations, roles, and operational policies</p>
</div> </div>
<Badge className="bg-gradient-to-r from-purple-600 to-indigo-600 px-4 py-1">Admin Control Panel</Badge>
</div> </div>
{loading ? ( {loading ? (

View File

@ -35,6 +35,8 @@ interface ApplicationDetailsActionModalsProps {
setInterviewIdToCancel: (value: string) => void; setInterviewIdToCancel: (value: string) => void;
isCancellingInterview: boolean; isCancellingInterview: boolean;
handleConfirmCancelInterview: () => void; handleConfirmCancelInterview: () => void;
interviewToReschedule: any;
setInterviewToReschedule: (value: any) => void;
interviewType: string; interviewType: string;
setInterviewType: (value: string) => void; setInterviewType: (value: string) => void;
interviewMode: string; interviewMode: string;
@ -99,6 +101,8 @@ export function ApplicationDetailsActionModals(props: ApplicationDetailsActionMo
setInterviewIdToCancel, setInterviewIdToCancel,
isCancellingInterview, isCancellingInterview,
handleConfirmCancelInterview, handleConfirmCancelInterview,
interviewToReschedule,
setInterviewToReschedule,
interviewType, interviewType,
setInterviewType, setInterviewType,
interviewMode, interviewMode,
@ -252,10 +256,13 @@ export function ApplicationDetailsActionModals(props: ApplicationDetailsActionMo
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Dialog open={showScheduleModal} onOpenChange={setShowScheduleModal}> <Dialog open={showScheduleModal} onOpenChange={(open) => {
setShowScheduleModal(open);
if (!open) setInterviewToReschedule(null);
}}>
<DialogContent data-testid="onboarding-schedule-modal"> <DialogContent data-testid="onboarding-schedule-modal">
<DialogHeader> <DialogHeader>
<DialogTitle>Schedule Interview</DialogTitle> <DialogTitle>{interviewToReschedule ? 'Reschedule Interview' : 'Schedule Interview'}</DialogTitle>
<DialogDescription>Set up an interview session with the applicant and relevant team members.</DialogDescription> <DialogDescription>Set up an interview session with the applicant and relevant team members.</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
@ -305,8 +312,13 @@ export function ApplicationDetailsActionModals(props: ApplicationDetailsActionMo
)} )}
</div> </div>
<div className="flex gap-3"> <div className="flex gap-3">
<Button variant="outline" className="flex-1" onClick={() => setShowScheduleModal(false)} disabled={isScheduling} data-testid="onboarding-schedule-cancel-button">Cancel</Button> <Button variant="outline" className="flex-1" onClick={() => {
<Button className="flex-1 bg-amber-600 hover:bg-amber-700" onClick={handleScheduleInterview} disabled={isScheduling} data-testid="onboarding-schedule-submit-button">{isScheduling ? 'Scheduling...' : 'Schedule'}</Button> setShowScheduleModal(false);
setInterviewToReschedule(null);
}} disabled={isScheduling} data-testid="onboarding-schedule-cancel-button">Cancel</Button>
<Button className="flex-1 bg-primary-600 hover:bg-primary-700" onClick={handleScheduleInterview} disabled={isScheduling} data-testid="onboarding-schedule-submit-button">
{isScheduling ? (interviewToReschedule ? 'Rescheduling...' : 'Scheduling...') : (interviewToReschedule ? 'Reschedule' : 'Schedule')}
</Button>
</div> </div>
</div> </div>
</DialogContent> </DialogContent>

View File

@ -51,6 +51,7 @@ interface ApplicationDetailsTabsProps {
setShowUploadForm: (value: boolean) => void; setShowUploadForm: (value: boolean) => void;
handleRetriggerEvaluators: () => void; handleRetriggerEvaluators: () => void;
handleCancelInterview: (interviewId: any) => void; handleCancelInterview: (interviewId: any) => void;
handleRescheduleInterview: (interview: any) => void;
setSelectedEvaluationForView: (value: any) => void; setSelectedEvaluationForView: (value: any) => void;
setShowFeedbackDetailsModal: (value: boolean) => void; setShowFeedbackDetailsModal: (value: boolean) => void;
renderFddAuditContent: () => React.ReactNode; renderFddAuditContent: () => React.ReactNode;
@ -86,6 +87,7 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
setShowUploadForm, setShowUploadForm,
handleRetriggerEvaluators, handleRetriggerEvaluators,
handleCancelInterview, handleCancelInterview,
handleRescheduleInterview,
setSelectedEvaluationForView, setSelectedEvaluationForView,
setShowFeedbackDetailsModal, setShowFeedbackDetailsModal,
renderFddAuditContent, renderFddAuditContent,
@ -627,11 +629,11 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="text-red-500 hover:text-red-700 hover:bg-red-50 h-8 px-2" className="text-primary-600 hover:text-primary-700 hover:bg-primary-50 h-8 px-2"
data-testid={`onboarding-interview-cancel-${idx}`} data-testid={`onboarding-interview-reschedule-${idx}`}
onClick={() => handleCancelInterview(interview.id)} onClick={() => handleRescheduleInterview(interview)}
> >
Cancel Reschedule
</Button> </Button>
)} )}
</TableCell> </TableCell>

View File

@ -17,10 +17,15 @@ interface UseApplicationDetailsAdminActionsParams {
participantType: string; participantType: string;
users: any[]; users: any[];
interviewDate: string; interviewDate: string;
setInterviewDate: Dispatch<SetStateAction<string>>;
interviewType: string; interviewType: string;
setInterviewType: Dispatch<SetStateAction<string>>;
interviewMode: string; interviewMode: string;
setInterviewMode: Dispatch<SetStateAction<string>>;
meetingLink: string; meetingLink: string;
setMeetingLink: Dispatch<SetStateAction<string>>;
location: string; location: string;
setLocation: Dispatch<SetStateAction<string>>;
scheduledInterviewParticipants: any[]; scheduledInterviewParticipants: any[];
uploadFile: File | null; uploadFile: File | null;
uploadDocType: string; uploadDocType: string;
@ -45,6 +50,8 @@ interface UseApplicationDetailsAdminActionsParams {
setShowCancelInterviewModal: Dispatch<SetStateAction<boolean>>; setShowCancelInterviewModal: Dispatch<SetStateAction<boolean>>;
interviewIdToCancel: string; interviewIdToCancel: string;
setInterviewIdToCancel: Dispatch<SetStateAction<string>>; setInterviewIdToCancel: Dispatch<SetStateAction<string>>;
interviewToReschedule: any;
setInterviewToReschedule: Dispatch<SetStateAction<any>>;
setIsCancellingInterview: Dispatch<SetStateAction<boolean>>; setIsCancellingInterview: Dispatch<SetStateAction<boolean>>;
setIsUploading: Dispatch<SetStateAction<boolean>>; setIsUploading: Dispatch<SetStateAction<boolean>>;
setShowUploadForm: Dispatch<SetStateAction<boolean>>; setShowUploadForm: Dispatch<SetStateAction<boolean>>;
@ -79,10 +86,15 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
participantType, participantType,
users, users,
interviewDate, interviewDate,
setInterviewDate,
interviewType, interviewType,
setInterviewType,
interviewMode, interviewMode,
setInterviewMode,
meetingLink, meetingLink,
setMeetingLink,
location, location,
setLocation,
scheduledInterviewParticipants, scheduledInterviewParticipants,
uploadFile, uploadFile,
uploadDocType, uploadDocType,
@ -107,6 +119,8 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
setShowCancelInterviewModal, setShowCancelInterviewModal,
interviewIdToCancel, interviewIdToCancel,
setInterviewIdToCancel, setInterviewIdToCancel,
interviewToReschedule,
setInterviewToReschedule,
setIsCancellingInterview, setIsCancellingInterview,
setIsUploading, setIsUploading,
setShowUploadForm, setShowUploadForm,
@ -176,7 +190,7 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
}, [currentUser, application, setUsers]); }, [currentUser, application, setUsers]);
const prefillInterviewParticipants = useCallback(() => { const prefillInterviewParticipants = useCallback(() => {
if (!showScheduleModal || !application) return; if (!showScheduleModal || !application || interviewToReschedule) return;
const levelNum = parseInt(interviewType.replace('level', '')) || 1; const levelNum = parseInt(interviewType.replace('level', '')) || 1;
const requiredRolesByLevel: Record<number, string[]> = { const requiredRolesByLevel: Record<number, string[]> = {
1: ['DD-ZM', 'RBM'], 1: ['DD-ZM', 'RBM'],
@ -233,7 +247,7 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
} }
}); });
setScheduledInterviewParticipants(unique); setScheduledInterviewParticipants(unique);
}, [showScheduleModal, application, interviewType, setScheduledInterviewParticipants]); }, [showScheduleModal, application, interviewType, interviewToReschedule, setScheduledInterviewParticipants]);
const handleScheduleInterview = async () => { const handleScheduleInterview = async () => {
if (!interviewDate) { if (!interviewDate) {
@ -242,20 +256,32 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
} }
try { try {
setIsScheduling(true); setIsScheduling(true);
await onboardingService.scheduleInterview({ const payload = {
applicationId: application?.id, applicationId: application?.id,
level: interviewType, level: interviewType,
scheduledAt: interviewDate, scheduledAt: interviewDate,
type: interviewMode === 'virtual' ? 'Virtual Interview' : 'Physical Interview', type: interviewMode === 'virtual' ? 'Virtual Interview' : 'Physical Interview',
location: interviewMode === 'virtual' ? meetingLink : location, location: interviewMode === 'virtual' ? meetingLink : location,
participants: scheduledInterviewParticipants.map((p) => p.id), participants: scheduledInterviewParticipants.map((p) => p.id),
};
if (interviewToReschedule) {
await onboardingService.updateInterview(interviewToReschedule.id, {
...payload,
status: 'Scheduled',
}); });
toast.success('Interview rescheduled successfully');
} else {
await onboardingService.scheduleInterview(payload);
toast.success('Interview scheduled successfully'); toast.success('Interview scheduled successfully');
}
setShowScheduleModal(false); setShowScheduleModal(false);
setInterviewToReschedule(null);
await fetchInterviews(); await fetchInterviews();
await fetchApplication(); await fetchApplication();
} catch { } catch {
toast.error('Failed to schedule interview'); toast.error(interviewToReschedule ? 'Failed to reschedule interview' : 'Failed to schedule interview');
} finally { } finally {
setIsScheduling(false); setIsScheduling(false);
} }
@ -266,6 +292,24 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
setShowCancelInterviewModal(true); setShowCancelInterviewModal(true);
}; };
const handleRescheduleInterview = async (interview: any) => {
setInterviewToReschedule(interview);
setInterviewType(`level${interview.level}`);
setInterviewMode(interview.interviewType?.toLowerCase().includes('virtual') ? 'virtual' : 'physical');
setInterviewDate(interview.scheduleDate ? (() => {
const d = new Date(interview.scheduleDate);
return new Date(d.getTime() - d.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
})() : '');
if (interview.interviewType?.toLowerCase().includes('virtual')) {
setMeetingLink(interview.linkOrLocation || '');
} else {
setLocation(interview.linkOrLocation || '');
}
const participants = (interview.participants || []).map((p: any) => p.user || p).filter(Boolean);
setScheduledInterviewParticipants(participants);
setShowScheduleModal(true);
};
const handleConfirmCancelInterview = async () => { const handleConfirmCancelInterview = async () => {
if (!interviewIdToCancel) return; if (!interviewIdToCancel) return;
try { try {
@ -606,6 +650,7 @@ export function useApplicationDetailsAdminActions(params: UseApplicationDetailsA
fetchUsers, fetchUsers,
maybeFetchUsersForModal, maybeFetchUsersForModal,
handleScheduleInterview, handleScheduleInterview,
handleRescheduleInterview,
handleCancelInterview, handleCancelInterview,
handleConfirmCancelInterview, handleConfirmCancelInterview,
handleUpload, handleUpload,

View File

@ -19,6 +19,7 @@ export function useApplicationDetailsUIState({ initialTab = 'questionnaire' }: U
const [showScheduleModal, setShowScheduleModal] = useState(false); const [showScheduleModal, setShowScheduleModal] = useState(false);
const [showCancelInterviewModal, setShowCancelInterviewModal] = useState(false); const [showCancelInterviewModal, setShowCancelInterviewModal] = useState(false);
const [interviewIdToCancel, setInterviewIdToCancel] = useState(''); const [interviewIdToCancel, setInterviewIdToCancel] = useState('');
const [interviewToReschedule, setInterviewToReschedule] = useState<any>(null);
const [showKTMatrixModal, setShowKTMatrixModal] = useState(false); const [showKTMatrixModal, setShowKTMatrixModal] = useState(false);
const [showLevel2FeedbackModal, setShowLevel2FeedbackModal] = useState(false); const [showLevel2FeedbackModal, setShowLevel2FeedbackModal] = useState(false);
const [showLevel3FeedbackModal, setShowLevel3FeedbackModal] = useState(false); const [showLevel3FeedbackModal, setShowLevel3FeedbackModal] = useState(false);
@ -98,6 +99,7 @@ export function useApplicationDetailsUIState({ initialTab = 'questionnaire' }: U
showScheduleModal, setShowScheduleModal, showScheduleModal, setShowScheduleModal,
showCancelInterviewModal, setShowCancelInterviewModal, showCancelInterviewModal, setShowCancelInterviewModal,
interviewIdToCancel, setInterviewIdToCancel, interviewIdToCancel, setInterviewIdToCancel,
interviewToReschedule, setInterviewToReschedule,
showKTMatrixModal, setShowKTMatrixModal, showKTMatrixModal, setShowKTMatrixModal,
showLevel2FeedbackModal, setShowLevel2FeedbackModal, showLevel2FeedbackModal, setShowLevel2FeedbackModal,
showLevel3FeedbackModal, setShowLevel3FeedbackModal, showLevel3FeedbackModal, setShowLevel3FeedbackModal,

View File

@ -62,6 +62,7 @@ export const ApplicationDetails = () => {
showScheduleModal, setShowScheduleModal, showScheduleModal, setShowScheduleModal,
showCancelInterviewModal, setShowCancelInterviewModal, showCancelInterviewModal, setShowCancelInterviewModal,
interviewIdToCancel, setInterviewIdToCancel, interviewIdToCancel, setInterviewIdToCancel,
interviewToReschedule, setInterviewToReschedule,
showKTMatrixModal, setShowKTMatrixModal, showKTMatrixModal, setShowKTMatrixModal,
showLevel2FeedbackModal, setShowLevel2FeedbackModal, showLevel2FeedbackModal, setShowLevel2FeedbackModal,
showLevel3FeedbackModal, setShowLevel3FeedbackModal, showLevel3FeedbackModal, setShowLevel3FeedbackModal,
@ -266,6 +267,7 @@ export const ApplicationDetails = () => {
handleRemoveInterviewer, handleRemoveInterviewer,
maybeFetchUsersForModal, maybeFetchUsersForModal,
handleScheduleInterview, handleScheduleInterview,
handleRescheduleInterview,
handleCancelInterview, handleCancelInterview,
handleConfirmCancelInterview, handleConfirmCancelInterview,
handleUpload, handleUpload,
@ -291,10 +293,15 @@ export const ApplicationDetails = () => {
participantType, participantType,
users, users,
interviewDate, interviewDate,
setInterviewDate,
interviewType, interviewType,
setInterviewType,
interviewMode, interviewMode,
setInterviewMode,
meetingLink, meetingLink,
setMeetingLink,
location, location,
setLocation,
scheduledInterviewParticipants, scheduledInterviewParticipants,
uploadFile, uploadFile,
uploadDocType, uploadDocType,
@ -319,6 +326,8 @@ export const ApplicationDetails = () => {
setShowCancelInterviewModal, setShowCancelInterviewModal,
interviewIdToCancel, interviewIdToCancel,
setInterviewIdToCancel, setInterviewIdToCancel,
interviewToReschedule,
setInterviewToReschedule,
setIsCancellingInterview, setIsCancellingInterview,
setIsUploading, setIsUploading,
setShowUploadForm, setShowUploadForm,
@ -445,6 +454,7 @@ export const ApplicationDetails = () => {
setShowUploadForm={setShowUploadForm} setShowUploadForm={setShowUploadForm}
handleRetriggerEvaluators={handleRetriggerEvaluators} handleRetriggerEvaluators={handleRetriggerEvaluators}
handleCancelInterview={handleCancelInterview} handleCancelInterview={handleCancelInterview}
handleRescheduleInterview={handleRescheduleInterview}
setSelectedEvaluationForView={setSelectedEvaluationForView} setSelectedEvaluationForView={setSelectedEvaluationForView}
setShowFeedbackDetailsModal={setShowFeedbackDetailsModal} setShowFeedbackDetailsModal={setShowFeedbackDetailsModal}
renderFddAuditContent={renderFddAuditContent} renderFddAuditContent={renderFddAuditContent}
@ -533,6 +543,8 @@ export const ApplicationDetails = () => {
setInterviewIdToCancel={setInterviewIdToCancel} setInterviewIdToCancel={setInterviewIdToCancel}
isCancellingInterview={isCancellingInterview} isCancellingInterview={isCancellingInterview}
handleConfirmCancelInterview={handleConfirmCancelInterview} handleConfirmCancelInterview={handleConfirmCancelInterview}
interviewToReschedule={interviewToReschedule}
setInterviewToReschedule={setInterviewToReschedule}
interviewType={interviewType} interviewType={interviewType}
setInterviewType={setInterviewType} setInterviewType={setInterviewType}
interviewMode={interviewMode} interviewMode={interviewMode}

View File

@ -9,7 +9,7 @@ import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { useState, useEffect } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { User as UserType } from '@/lib/mock-data'; import { User as UserType } from '@/lib/mock-data';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -97,6 +97,16 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
const [uploadFile, setUploadFile] = useState<File | null>(null); const [uploadFile, setUploadFile] = useState<File | null>(null);
const [uploadDocType, setUploadDocType] = useState<string>(RESIGNATION_DOCUMENT_TYPES[0]); const [uploadDocType, setUploadDocType] = useState<string>(RESIGNATION_DOCUMENT_TYPES[0]);
const [uploadStage, setUploadStage] = useState(''); const [uploadStage, setUploadStage] = useState('');
const hasUploadedPPT = useMemo(() => {
const allDocs = [
...(resignationData?.documents || []),
...(resignationData?.uploadedDocuments || [])
];
return allDocs.some(doc =>
(doc.documentType || doc.type) === 'PPT Presentation'
);
}, [resignationData]);
const fetchResignation = async () => { const fetchResignation = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
@ -132,13 +142,13 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
{ id: 3, name: 'ZBH Review', key: 'ZBH', description: 'Zonal Business Head approval' }, { 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: 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' }, { id: 5, name: 'NBH Approval', key: 'NBH', description: 'National Business Head approval' },
{ id: 6, name: 'DD Admin Review', key: 'DD Admin', description: 'DD Admin verification' }, { id: 6, name: 'Legal - Resignation Letter', key: 'Legal', description: 'Legal team issues resignation approval letter' },
{ id: 7, name: 'Legal - Resignation Letter', key: 'Legal', description: 'Legal team issues resignation approval letter' }, { id: 7, name: 'DD Admin Review', key: 'DD Admin', description: 'DD Admin verification and final closure' },
{ id: 8, name: 'F&F Settlement', key: 'F&F Initiated', description: 'Full & Final settlement process' }, { id: 8, name: 'F&F Settlement', key: 'F&F Initiated', description: 'Full & Final settlement process' },
{ id: 9, name: 'Completed', key: 'Completed', description: 'Resignation process finalized' } { id: 9, name: 'Completed', key: 'Completed', description: 'Resignation process finalized' }
]; ];
const stagesOrdered = ['ASM', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'DD Admin', 'Legal', 'F&F Initiated', 'Completed']; const stagesOrdered = ['ASM', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'Legal', 'DD Admin', 'F&F Initiated', 'Completed'];
const legalStageApproved = (() => { const legalStageApproved = (() => {
if (!resignationData) return false; if (!resignationData) return false;
@ -157,6 +167,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
const atLegal = stage === 'legal' || stage === 'legal - resignation letter'; const atLegal = stage === 'legal' || stage === 'legal - resignation letter';
const legalApprovedTransition = const legalApprovedTransition =
targetStage === 'legal' || targetStage === 'legal' ||
targetStage === 'dd admin' ||
targetStage === 'f&f initiated' || targetStage === 'f&f initiated' ||
targetStage === 'fnf_initiated' || targetStage === 'fnf_initiated' ||
action.includes('approved'); action.includes('approved');
@ -174,7 +185,16 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
const status = resignationData.status; const status = resignationData.status;
const userRole = currentUser.role; const userRole = currentUser.role;
// Final states where no more actions are possible const isZmRbmStage = currentStage === 'RBM' || currentStage === 'RBM Review' || currentStage === 'RBM + DD-ZM Review';
const userRoleCode = String(currentUser.roleCode || currentUser.role || '').trim().toUpperCase();
// Check if current user already partially approved this request at this stage
const hasAlreadyPartiallyApproved = isZmRbmStage && auditLogs.some(log =>
log.action === 'PARTIAL_APPROVE' &&
(log.actor?.id === currentUser.id || log.actorId === currentUser.id || log.actor?.email === currentUser.email || log.userEmail === currentUser.email) &&
(log.details?.roleCode === userRoleCode || (log.details?.roleCode === 'DD-ZM' && userRoleCode === 'DD ZM'))
);
const isFinalState = ['Completed', 'Rejected', 'Withdrawn', 'Revoked'].includes(status); const isFinalState = ['Completed', 'Rejected', 'Withdrawn', 'Revoked'].includes(status);
// Check if it's already in the settlement phase // Check if it's already in the settlement phase
@ -184,18 +204,25 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
const nbhIndex = stagesOrdered.indexOf('NBH'); const nbhIndex = stagesOrdered.indexOf('NBH');
const isPastNBH = stageIndex !== -1 && nbhIndex !== -1 && stageIndex >= nbhIndex; const isPastNBH = stageIndex !== -1 && nbhIndex !== -1 && stageIndex >= nbhIndex;
const isCurrentlyAssigned = userRole === 'Super Admin' || userRole === STAGE_TO_ROLE_MAP[currentStage]; const isCurrentlyAssigned = userRoleCode === 'SUPER_ADMIN' ||
(isZmRbmStage && (userRoleCode === 'RBM' || userRoleCode === 'DD-ZM' || userRoleCode === 'DD ZM')) ||
userRole === STAGE_TO_ROLE_MAP[currentStage];
const isDDLeadStage = currentStage === 'DD Lead' || currentStage === 'DD Lead Review';
const isDDLead = userRoleCode === 'DD_LEAD' || userRoleCode === 'DD LEAD';
const canApprove = isCurrentlyAssigned && const canApprove = isCurrentlyAssigned &&
!isFinalState && !isFinalState &&
!isSettlementPhase && !isSettlementPhase &&
!(currentStage === 'Legal' && legalStageApproved); !hasAlreadyPartiallyApproved &&
!(currentStage === 'Legal' && legalStageApproved) &&
!(isDDLead && isDDLeadStage && !hasUploadedPPT);
return { return {
canApprove, canApprove,
canSendBack: isCurrentlyAssigned && !isFinalState && !isSettlementPhase && stageIndex > 0, canSendBack: isCurrentlyAssigned && !isFinalState && !isSettlementPhase && stageIndex > 0,
canWithdraw: userRole === 'Dealer' && !isPastNBH && !isFinalState, canWithdraw: userRole === 'Dealer' && !isPastNBH && !isFinalState,
canRevoke: (userRole === 'Super Admin' || userRole === 'DD Admin') && !isFinalState && !isSettlementPhase, canRevoke: (userRoleCode === 'SUPER_ADMIN' || userRole === 'DD Admin') && !isFinalState && !isSettlementPhase,
canPushToFnF: ['DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Super Admin'].includes(userRole) && canPushToFnF: ['DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Super Admin'].includes(userRole) &&
!isSettlementPhase && !isFinalState, !isSettlementPhase && !isFinalState,
canAssign: userRole !== 'Dealer' && !isFinalState canAssign: userRole !== 'Dealer' && !isFinalState
@ -203,7 +230,7 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
}; };
const permissions = getResignationPermissions(); const permissions = getResignationPermissions();
const isNationalLevel = ['Super Admin', 'DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Legal Admin'].includes(currentUser?.role || ''); const isNationalLevel = ['Super Admin', 'DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Legal Admin', 'DD-ZM'].includes(currentUser?.role || '');
const stageAliases: Record<string, string[]> = { const stageAliases: Record<string, string[]> = {
'ASM': ['ASM', 'ASM Review', 'Request Initiated'], 'ASM': ['ASM', 'ASM Review', 'Request Initiated'],
@ -447,6 +474,31 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-slate-600 mr-2">Workflow Actions:</span> <span className="text-sm text-slate-600 mr-2">Workflow Actions:</span>
{/* Debug for PPT button visibility */}
{(() => {
const roleNormalized = String(currentUser?.roleCode || currentUser?.role || '').trim().toUpperCase();
const isDDLeadUser = roleNormalized === 'DD LEAD' || roleNormalized === 'DD_LEAD';
const isDDLeadStageCurrent = ['DD Lead', 'DD Lead Review', 'DDL Review'].includes(resignationData?.currentStage);
if (isDDLeadUser && isDDLeadStageCurrent) {
return (
<Button
size="sm"
variant="outline"
className="text-amber-700 border-amber-300 hover:bg-amber-50 shadow-sm"
onClick={() => {
setUploadDocType('PPT Presentation');
setUploadStage('DD Lead');
setShowUploadDialog(true);
}}
>
<Upload className="w-4 h-4 mr-2" />
Upload PPT
</Button>
);
}
return null;
})()}
{permissions.canApprove && ( {permissions.canApprove && (
<Button <Button
size="sm" size="sm"

View File

@ -153,7 +153,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
}; };
// Check if user can push to F&F (DD Lead and above) // Check if user can push to F&F (DD Lead and above)
const canPushToFnF = currentUser && ['DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Super Admin'].includes(currentUser.role || currentUser.roleCode); const canPushToFnF = currentUser && ['DD Lead', 'DD Head', 'DD_HEAD', 'NBH', 'DD Admin', 'Super Admin'].includes(currentUser.role || currentUser.roleCode);
// Centralized Permissions Utility for Termination logic (Robust Validation) // Centralized Permissions Utility for Termination logic (Robust Validation)
const getTerminationPermissions = () => { const getTerminationPermissions = () => {
@ -169,12 +169,38 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
const isFinalState = ['Completed', 'Rejected', 'Withdrawn', 'Terminated'].includes(status) || currentStage === 'Terminated'; const isFinalState = ['Completed', 'Rejected', 'Withdrawn', 'Terminated'].includes(status) || currentStage === 'Terminated';
const isSettlementPhase = status === 'F&F Initiated' || currentStage === 'F&F Initiated' || status === 'Settled' || status === 'FNF_INITIATED'; const isSettlementPhase = status === 'F&F Initiated' || currentStage === 'F&F Initiated' || status === 'Settled' || status === 'FNF_INITIATED';
const userHasApprovedJointly = auditLogs.some(log => {
const logUserId = log.userId || log.user?.id || log.actor?.id || log.actorId;
const isThisUser = String(logUserId) === String(currentUser.id);
const actionText = (log.action || log.description || '').toUpperCase();
const isPartialApprove = actionText.includes('PARTIAL_APPROVE') || actionText.includes('PARTIAL APPROVED');
const stageMatches =
log.details?.stage === 'RBM + DD-ZM Review' ||
log.stage === 'RBM + DD-ZM Review' ||
(log.remarks || '').includes('Partial approval by');
const result = isThisUser && isPartialApprove && stageMatches;
if (result) console.log('[TerminationDebug] Found matching partial approval log:', log);
return result;
});
if (currentStage === 'RBM + DD-ZM Review' && (userRole === 'RBM' || userRole === 'DD-ZM')) {
console.log('[TerminationDebug] Joint Stage Detection:', {
currentStage,
userRole,
userId: currentUser.id,
userHasApprovedJointly,
auditLogsCount: auditLogs.length
});
}
const isCurrentlyAssigned = userRole === 'Super Admin' || userRole === 'DD Admin' || ( const isCurrentlyAssigned = userRole === 'Super Admin' || userRole === 'DD Admin' || (
(currentStage === 'RBM Review' && userRole === 'RBM') || (currentStage === 'RBM + DD-ZM Review' && (userRole === 'RBM' || userRole === 'DD-ZM') && !userHasApprovedJointly) ||
(currentStage === 'ZBH Review' && userRole === 'ZBH') || (currentStage === 'ZBH Review' && userRole === 'ZBH') ||
(currentStage === 'DD Lead Review' && userRole === 'DD Lead') || (currentStage === 'DD Lead Review' && userRole === 'DD Lead') ||
(currentStage === 'Legal Verification' && userRole === 'Legal Admin') || (currentStage === 'Legal Verification' && userRole === 'Legal Admin') ||
(currentStage === 'DD Head Review' && userRole === 'DD Head') || (currentStage === 'DD Head Review' && (userRole === 'DD Head' || userRole === 'DD_HEAD')) ||
(currentStage === 'NBH Evaluation' && userRole === 'NBH') || (currentStage === 'NBH Evaluation' && userRole === 'NBH') ||
(currentStage === 'NBH Final Approval' && userRole === 'NBH') || (currentStage === 'NBH Final Approval' && userRole === 'NBH') ||
(currentStage === 'CCO Approval' && userRole === 'CCO') || (currentStage === 'CCO Approval' && userRole === 'CCO') ||
@ -206,8 +232,8 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
const isScnStage = ['Show Cause Notice', 'Show Cause Notice (SCN)', 'SCN'].includes(request.currentStage); const isScnStage = ['Show Cause Notice', 'Show Cause Notice (SCN)', 'SCN'].includes(request.currentStage);
const stageAliases: Record<string, string[]> = { const stageAliases: Record<string, string[]> = {
'Submitted': ['Submitted', 'Request Initiated'], 'Submitted': ['Submitted'],
'RBM Review': ['RBM Review'], 'RBM + DD-ZM Review': ['RBM + DD-ZM Review'],
'ZBH Review': ['ZBH Review'], 'ZBH Review': ['ZBH Review'],
'DD Lead Review': ['DD Lead Review'], 'DD Lead Review': ['DD Lead Review'],
'Legal Verification': ['Legal Verification'], 'Legal Verification': ['Legal Verification'],
@ -224,7 +250,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
const stageSequence = [ const stageSequence = [
'Submitted', 'Submitted',
'RBM Review', 'RBM + DD-ZM Review',
'ZBH Review', 'ZBH Review',
'DD Lead Review', 'DD Lead Review',
'Legal Verification', 'Legal Verification',
@ -301,21 +327,17 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
return acc; return acc;
}, {}); }, {});
const getLatestStageTimelineEntry = (stageName: string) => { const getStageTimelineEntries = (stageName: string) => {
const aliases = stageAliases[stageName] || [stageName]; const aliases = stageAliases[stageName] || [stageName];
const entries = (request.timeline || []).filter((entry: any) => aliases.includes(entry.stage)); const entries = (request.timeline || []).filter((entry: any) =>
aliases.includes(entry.stage) ||
if (entries.length === 0) return null; (stageName === 'Submitted' && (entry.stage === 'Submitted' || entry.stage === 'Request Initiated'))
// Keep submitted row anchored to initiation details, not later stage-transition remarks.
if (stageName === 'Submitted') {
const initiatedEntry = entries.find((entry: any) =>
String(entry?.action || '').toLowerCase().includes('initiated')
); );
return initiatedEntry || entries[0];
}
return entries[entries.length - 1]; // Sort by timestamp
return entries.sort((a: any, b: any) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
);
}; };
const progressStages = [ const progressStages = [
@ -332,9 +354,9 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
}, },
{ {
id: 2, id: 2,
name: 'RBM Review', name: 'RBM + DD-ZM Review',
status: getProgressStatus('RBM Review'), status: getProgressStatus('RBM + DD-ZM Review'),
description: 'Regional Business Manager review' description: 'Joint review and approval by RBM and DD-ZM'
}, },
{ {
id: 3, id: 3,
@ -448,27 +470,34 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
setIsProcessing(true); setIsProcessing(true);
try { try {
let response: any;
if (actionType === 'approve' || actionType === 'sendBack' || actionType === 'withdrawal' || actionType === 'revoke') { if (actionType === 'approve' || actionType === 'sendBack' || actionType === 'withdrawal' || actionType === 'revoke') {
await terminationService.updateTerminationStatus(terminationId, effectiveAction, remarks); response = await terminationService.updateTerminationStatus(terminationId, effectiveAction, remarks);
} else if (actionType === 'pushfnf') { } else if (actionType === 'pushfnf') {
// Logic for push to fnf (using existing service if available) response = await terminationService.updateTerminationStatus(terminationId, 'pushfnf', remarks);
await terminationService.updateTerminationStatus(terminationId, 'pushfnf', remarks);
} else { } else {
toast.error('Action logic not fully implemented for this type'); toast.error('Action logic not fully implemented for this type');
setIsProcessing(false); setIsProcessing(false);
return; return;
} }
if (response && (response.success === false || response.ok === false)) {
console.error('[TerminationDetails] Action failed:', response);
toast.error(response.message || response.data?.message || 'Failed to perform action');
setIsProcessing(false);
return;
}
const actionMessages: Record<string, string> = { const actionMessages: Record<string, string> = {
approve: 'Request approved and forwarded', approve: 'Request approved and forwarded',
withdrawal: 'Request withdrawn successfully', withdrawal: 'Request withdrawn successfully',
sendBack: 'Request sent back for clarification', sendBack: 'Request sent back for clarification',
assign: `Request assigned to ${assignToUser}`, assign: `Request assigned successfully`,
pushfnf: 'Request pushed to F&F successfully', pushfnf: 'Request pushed to F&F successfully',
revoke: 'Request revoked and withdrawn' revoke: 'Request revoked and withdrawn'
}; };
toast.success(actionMessages[actionType!] || 'Action completed'); toast.success(actionMessages[actionType!] || response?.message || 'Action completed');
setActionDialog({ open: false, type: null }); setActionDialog({ open: false, type: null });
setRemarks(''); setRemarks('');
setAssignToUser(''); setAssignToUser('');
@ -844,7 +873,7 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
<div className="space-y-4"> <div className="space-y-4">
{progressStages.map((stage, index) => { {progressStages.map((stage, index) => {
const documentCount = stageDocuments[stage.name]?.length || 0; const documentCount = stageDocuments[stage.name]?.length || 0;
const timelineEntry = getLatestStageTimelineEntry(stage.name); const stageEntries = getStageTimelineEntries(stage.name);
return ( return (
<div key={stage.id} className="flex gap-4"> <div key={stage.id} className="flex gap-4">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
@ -884,29 +913,59 @@ export function TerminationDetails({ terminationId, onBack, currentUser }: Termi
</button> </button>
)} )}
</div> </div>
{(timelineEntry?.timestamp || stage.date) && ( {(stageEntries[0]?.timestamp || stage.date) && (
<div className="flex items-center gap-1 text-sm text-slate-600"> <div className="flex items-center gap-1 text-xs text-slate-500 bg-slate-50 px-2 py-0.5 rounded-full border border-slate-100">
<Calendar className="w-4 h-4" /> <Calendar className="w-3 h-3" />
<span>{formatDateTime(timelineEntry?.timestamp || stage.date)}</span> <span>{formatDateTime(stageEntries[0]?.timestamp || stage.date)}</span>
</div> </div>
)} )}
</div> </div>
<p className="text-slate-600 text-sm">{stage.description}</p> <p className="text-slate-600 text-sm">{stage.description}</p>
{timelineEntry && ( {stageEntries.length > 0 && (
<div className="mt-3 space-y-2"> <div className="mt-3 space-y-3">
{stageEntries.map((entry: any, entryIdx: number) => {
const rawRemarks = entry.remarks || entry.comments || '';
const isAttachment = rawRemarks?.startsWith('Attachment:');
const remarksContent = isAttachment
? rawRemarks.replace('Attachment:', '').trim()
: rawRemarks;
return (
<div key={entryIdx} className="group">
<div className="flex items-center gap-2 mb-1">
<Badge className={`
text-[10px] h-4 px-1.5
${entry.action?.toLowerCase().includes('rejected') || entry.action?.toLowerCase().includes('revoked')
? 'bg-red-100 text-red-700'
: entry.action?.toLowerCase().includes('approved')
? 'bg-emerald-100 text-emerald-700'
: 'bg-blue-100 text-blue-700'}
`}>
{entry.action || 'Action'}
</Badge>
<span className="text-[10px] text-slate-500 font-medium">
by {entry.user || 'System'} {formatDateTime(entry.timestamp)}
</span>
</div>
<div className={`
p-2.5 rounded-lg border text-sm
${isAttachment
? 'bg-amber-50/50 border-amber-100 text-amber-900'
: 'bg-slate-50 border-slate-100 text-slate-700'}
`}>
{isAttachment ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge className="bg-blue-100 text-blue-700 border-blue-300">{timelineEntry.action || 'Updated'}</Badge> <FileText className="w-3.5 h-3.5 text-amber-600" />
<span className="text-xs text-slate-500">by {timelineEntry.user || 'System'}</span> <span className="font-medium truncate">{remarksContent}</span>
</div>
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3">
<div className="space-y-2">
<div>
<Label className="text-xs text-slate-600">Remarks:</Label>
<p className="text-sm text-slate-700 mt-1">{timelineEntry.remarks || 'No remarks provided.'}</p>
</div> </div>
) : (
<p className="leading-relaxed">{remarksContent || 'No remarks provided.'}</p>
)}
</div> </div>
</div> </div>
);
})}
</div> </div>
)} )}
</div> </div>

View File

@ -5,6 +5,7 @@ export const RESIGNATION_DOCUMENT_TYPES = [
"Legal Communication", "Legal Communication",
"Handover Document", "Handover Document",
"Settlement Supporting Document", "Settlement Supporting Document",
"PPT Presentation",
"Other", "Other",
] as const; ] as const;
@ -32,7 +33,7 @@ export const TERMINATION_DOCUMENT_TYPES = [
export const TERMINATION_STAGE_OPTIONS = [ export const TERMINATION_STAGE_OPTIONS = [
"Submitted", "Submitted",
"RBM Review", "RBM + DD-ZM Review",
"ZBH Review", "ZBH Review",
"DD Lead Review", "DD Lead Review",
"Legal Verification", "Legal Verification",

View File

@ -11,6 +11,9 @@
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0); --popover-foreground: oklch(0.145 0 0);
--primary: #da291c; --primary: #da291c;
--primary-600: #da291c;
--primary-700: #b82216;
--primary-50: #fef2f2;
--primary-foreground: oklch(1 0 0); --primary-foreground: oklch(1 0 0);
--secondary: oklch(0.95 0.0058 264.53); --secondary: oklch(0.95 0.0058 264.53);
--secondary-foreground: #030213; --secondary-foreground: #030213;
@ -99,6 +102,9 @@
--color-popover: var(--popover); --color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground); --color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-600: var(--primary-600);
--color-primary-700: var(--primary-700);
--color-primary-50: var(--primary-50);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground); --color-secondary-foreground: var(--secondary-foreground);