667 lines
24 KiB
TypeScript
667 lines
24 KiB
TypeScript
import { Dispatch, SetStateAction, useCallback } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { onboardingService } from '@/services/onboarding.service';
|
|
|
|
interface UseApplicationDetailsAdminActionsParams {
|
|
application: any;
|
|
applicationId: string;
|
|
currentUser: any;
|
|
interviews: any[];
|
|
approvalFile: File | null;
|
|
approvalRemark: string;
|
|
rejectionReason: string;
|
|
architectureLeadId: string;
|
|
architectureStatus: string;
|
|
architectureRemarks: string;
|
|
selectedUser: string;
|
|
participantType: string;
|
|
users: any[];
|
|
interviewDate: string;
|
|
setInterviewDate: Dispatch<SetStateAction<string>>;
|
|
interviewType: string;
|
|
setInterviewType: Dispatch<SetStateAction<string>>;
|
|
interviewMode: string;
|
|
setInterviewMode: Dispatch<SetStateAction<string>>;
|
|
meetingLink: string;
|
|
setMeetingLink: Dispatch<SetStateAction<string>>;
|
|
location: string;
|
|
setLocation: Dispatch<SetStateAction<string>>;
|
|
scheduledInterviewParticipants: any[];
|
|
uploadFile: File | null;
|
|
uploadDocType: string;
|
|
selectedStage: string | null;
|
|
setIsApproving: Dispatch<SetStateAction<boolean>>;
|
|
setShowApproveModal: Dispatch<SetStateAction<boolean>>;
|
|
setApprovalRemark: Dispatch<SetStateAction<string>>;
|
|
setApprovalFile: Dispatch<SetStateAction<File | null>>;
|
|
setIsRejecting: Dispatch<SetStateAction<boolean>>;
|
|
setShowRejectModal: Dispatch<SetStateAction<boolean>>;
|
|
setRejectionReason: Dispatch<SetStateAction<string>>;
|
|
setIsAssigningArchitecture: Dispatch<SetStateAction<boolean>>;
|
|
setShowAssignArchitectureModal: Dispatch<SetStateAction<boolean>>;
|
|
setIsUpdatingArchitecture: Dispatch<SetStateAction<boolean>>;
|
|
setShowArchitectureStatusModal: Dispatch<SetStateAction<boolean>>;
|
|
setIsAssigningParticipant: Dispatch<SetStateAction<boolean>>;
|
|
setSelectedUser: Dispatch<SetStateAction<string>>;
|
|
setShowAssignModal: Dispatch<SetStateAction<boolean>>;
|
|
setLoading: Dispatch<SetStateAction<boolean>>;
|
|
setIsScheduling: Dispatch<SetStateAction<boolean>>;
|
|
setShowScheduleModal: Dispatch<SetStateAction<boolean>>;
|
|
setShowCancelInterviewModal: Dispatch<SetStateAction<boolean>>;
|
|
interviewIdToCancel: string;
|
|
setInterviewIdToCancel: Dispatch<SetStateAction<string>>;
|
|
interviewToReschedule: any;
|
|
setInterviewToReschedule: Dispatch<SetStateAction<any>>;
|
|
setIsCancellingInterview: Dispatch<SetStateAction<boolean>>;
|
|
setIsUploading: Dispatch<SetStateAction<boolean>>;
|
|
setShowUploadForm: Dispatch<SetStateAction<boolean>>;
|
|
setUploadFile: Dispatch<SetStateAction<File | null>>;
|
|
setUploadDocType: Dispatch<SetStateAction<string>>;
|
|
setDocuments: Dispatch<SetStateAction<any[]>>;
|
|
selectedInterviewerId: string;
|
|
setSelectedInterviewerId: Dispatch<SetStateAction<string>>;
|
|
setScheduledInterviewParticipants: Dispatch<SetStateAction<any[]>>;
|
|
setUsers: Dispatch<SetStateAction<any[]>>;
|
|
showScheduleModal: boolean;
|
|
showAssignArchitectureModal: boolean;
|
|
showAssignModal: boolean;
|
|
fetchApplication: (silent?: boolean) => Promise<void>;
|
|
fetchInterviews: () => Promise<void>;
|
|
fetchEorData: () => Promise<void>;
|
|
}
|
|
|
|
export function useApplicationDetailsAdminActions(params: UseApplicationDetailsAdminActionsParams) {
|
|
const {
|
|
application,
|
|
applicationId,
|
|
currentUser,
|
|
interviews,
|
|
approvalFile,
|
|
approvalRemark,
|
|
rejectionReason,
|
|
architectureLeadId,
|
|
architectureStatus,
|
|
architectureRemarks,
|
|
selectedUser,
|
|
participantType,
|
|
users,
|
|
interviewDate,
|
|
setInterviewDate,
|
|
interviewType,
|
|
setInterviewType,
|
|
interviewMode,
|
|
setInterviewMode,
|
|
meetingLink,
|
|
setMeetingLink,
|
|
location,
|
|
setLocation,
|
|
scheduledInterviewParticipants,
|
|
uploadFile,
|
|
uploadDocType,
|
|
selectedStage,
|
|
setIsApproving,
|
|
setShowApproveModal,
|
|
setApprovalRemark,
|
|
setApprovalFile,
|
|
setIsRejecting,
|
|
setShowRejectModal,
|
|
setRejectionReason,
|
|
setIsAssigningArchitecture,
|
|
setShowAssignArchitectureModal,
|
|
setIsUpdatingArchitecture,
|
|
setShowArchitectureStatusModal,
|
|
setIsAssigningParticipant,
|
|
setSelectedUser,
|
|
setShowAssignModal,
|
|
setLoading,
|
|
setIsScheduling,
|
|
setShowScheduleModal,
|
|
setShowCancelInterviewModal,
|
|
interviewIdToCancel,
|
|
setInterviewIdToCancel,
|
|
interviewToReschedule,
|
|
setInterviewToReschedule,
|
|
setIsCancellingInterview,
|
|
setIsUploading,
|
|
setShowUploadForm,
|
|
setUploadFile,
|
|
setUploadDocType,
|
|
setDocuments,
|
|
selectedInterviewerId,
|
|
setSelectedInterviewerId,
|
|
setScheduledInterviewParticipants,
|
|
setUsers,
|
|
showScheduleModal,
|
|
showAssignArchitectureModal,
|
|
showAssignModal,
|
|
fetchApplication,
|
|
fetchInterviews,
|
|
fetchEorData,
|
|
} = params;
|
|
|
|
const handleAddInterviewer = () => {
|
|
if (!selectedInterviewerId) return;
|
|
const usersList = Array.isArray(users) ? users : [];
|
|
const userToAdd = usersList.find((u) => u.id === selectedInterviewerId);
|
|
if (userToAdd && !scheduledInterviewParticipants.find((p) => p.id === userToAdd.id)) {
|
|
setScheduledInterviewParticipants([...scheduledInterviewParticipants, userToAdd]);
|
|
setSelectedInterviewerId('');
|
|
}
|
|
};
|
|
|
|
const handleRemoveInterviewer = (userId: string) => {
|
|
setScheduledInterviewParticipants(scheduledInterviewParticipants.filter((p) => p.id !== userId));
|
|
};
|
|
|
|
const fetchUsers = useCallback(async (type?: string) => {
|
|
if (!currentUser || !['DD Admin', 'Super Admin', 'DD Lead', 'DD Head', 'NBH'].includes(currentUser.role)) return;
|
|
try {
|
|
const reqParams: any = {};
|
|
if (type) {
|
|
const roleMapping: any = {
|
|
level1: ['DD-ZM', 'RBM'],
|
|
level2: ['DD Lead', 'ZBH'],
|
|
level3: ['NBH', 'DD Head'],
|
|
};
|
|
// Keep stage roles as preferred default, but allow broader user pool
|
|
// so admins can add extra panelists for the same interview.
|
|
if (roleMapping[type]) {
|
|
reqParams.preferredRoleCode = roleMapping[type];
|
|
}
|
|
if (application) {
|
|
reqParams.locationId = application.districtId || application.areaId || application.regionId || application.zoneId;
|
|
}
|
|
}
|
|
reqParams.isExternal = false;
|
|
const response = await onboardingService.getUsers(reqParams);
|
|
const rawUsers = Array.isArray(response)
|
|
? response
|
|
: response && Array.isArray(response.data)
|
|
? response.data
|
|
: response && Array.isArray(response.users)
|
|
? response.users
|
|
: [];
|
|
// Exclude inactive users and keep deterministic sorting.
|
|
const activeUsers = rawUsers.filter((u: any) => (u.status || '').toLowerCase() !== 'inactive');
|
|
setUsers(activeUsers.sort((a: any, b: any) => String(a.fullName || a.name || '').localeCompare(String(b.fullName || b.name || ''))));
|
|
} catch {
|
|
setUsers([]);
|
|
}
|
|
}, [currentUser, application, setUsers]);
|
|
|
|
const prefillInterviewParticipants = useCallback(() => {
|
|
if (!showScheduleModal || !application || interviewToReschedule) return;
|
|
const levelNum = parseInt(interviewType.replace('level', '')) || 1;
|
|
const requiredRolesByLevel: Record<number, string[]> = {
|
|
1: ['DD-ZM', 'RBM'],
|
|
2: ['DD Lead', 'ZBH'],
|
|
3: ['NBH', 'DD Head'],
|
|
};
|
|
const normalizeRole = (value: unknown) =>
|
|
String(value || '')
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[_\s-]+/g, ' ');
|
|
const expectedRoles = (requiredRolesByLevel[levelNum] || []).map(normalizeRole);
|
|
|
|
const deriveDisplayRole = (participant: any, user: any): string => {
|
|
const candidateRoles = [
|
|
participant?.metadata?.role,
|
|
user?.role?.roleName,
|
|
user?.role?.roleCode,
|
|
user?.roleCode,
|
|
user?.role,
|
|
].filter(Boolean);
|
|
const matched = candidateRoles.find((r: any) => expectedRoles.includes(normalizeRole(r)));
|
|
return String(matched || candidateRoles[0] || 'Panelist');
|
|
};
|
|
|
|
const preAssigned = (application?.participants || [])
|
|
.filter((p: any) =>
|
|
p.metadata?.interviewLevel === levelNum ||
|
|
p.metadata?.interviewLevel === String(levelNum) ||
|
|
p.metadata?.allAssignments?.includes(levelNum) ||
|
|
p.metadata?.allAssignments?.includes(String(levelNum)) ||
|
|
expectedRoles.includes(normalizeRole(p.user?.role)) ||
|
|
expectedRoles.includes(normalizeRole(p.user?.roleCode)) ||
|
|
expectedRoles.includes(normalizeRole(p.metadata?.role))
|
|
)
|
|
.map((p: any) => {
|
|
const user = p.user || {};
|
|
return {
|
|
...user,
|
|
__stageRole: deriveDisplayRole(p, user),
|
|
};
|
|
})
|
|
.filter((u: any) => !!u?.id);
|
|
if (preAssigned.length === 0) {
|
|
setScheduledInterviewParticipants([]);
|
|
return;
|
|
}
|
|
const unique: any[] = [];
|
|
const seen = new Set();
|
|
preAssigned.forEach((u: any) => {
|
|
if (u.id && !seen.has(u.id)) {
|
|
seen.add(u.id);
|
|
unique.push(u);
|
|
}
|
|
});
|
|
setScheduledInterviewParticipants(unique);
|
|
}, [showScheduleModal, application, interviewType, interviewToReschedule, setScheduledInterviewParticipants]);
|
|
|
|
const handleScheduleInterview = async () => {
|
|
if (!interviewDate) {
|
|
toast.warning('Please select date and time');
|
|
return;
|
|
}
|
|
try {
|
|
setIsScheduling(true);
|
|
const payload = {
|
|
applicationId: application?.id,
|
|
level: interviewType,
|
|
scheduledAt: interviewDate,
|
|
type: interviewMode === 'virtual' ? 'Virtual Interview' : 'Physical Interview',
|
|
location: interviewMode === 'virtual' ? meetingLink : location,
|
|
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');
|
|
}
|
|
|
|
setShowScheduleModal(false);
|
|
setInterviewToReschedule(null);
|
|
await fetchInterviews();
|
|
await fetchApplication();
|
|
} catch {
|
|
toast.error(interviewToReschedule ? 'Failed to reschedule interview' : 'Failed to schedule interview');
|
|
} finally {
|
|
setIsScheduling(false);
|
|
}
|
|
};
|
|
|
|
const handleCancelInterview = async (interviewId: string) => {
|
|
setInterviewIdToCancel(interviewId);
|
|
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 () => {
|
|
if (!interviewIdToCancel) return;
|
|
try {
|
|
setIsCancellingInterview(true);
|
|
await onboardingService.updateInterview(interviewIdToCancel, { status: 'Cancelled' });
|
|
toast.success('Interview cancelled successfully');
|
|
setShowCancelInterviewModal(false);
|
|
setInterviewIdToCancel('');
|
|
await fetchInterviews();
|
|
} catch {
|
|
toast.error('Failed to cancel interview');
|
|
} finally {
|
|
setIsCancellingInterview(false);
|
|
}
|
|
};
|
|
|
|
const handleUpload = async () => {
|
|
if (!uploadFile || !uploadDocType) {
|
|
toast.warning('Please select a file and document type');
|
|
return;
|
|
}
|
|
try {
|
|
setIsUploading(true);
|
|
const formData = new FormData();
|
|
formData.append('file', uploadFile);
|
|
formData.append('documentType', uploadDocType);
|
|
if (selectedStage) formData.append('stage', selectedStage);
|
|
await onboardingService.uploadDocument(applicationId, formData);
|
|
toast.success('Document uploaded successfully');
|
|
setShowUploadForm(false);
|
|
setUploadFile(null);
|
|
setUploadDocType('');
|
|
const docs = await onboardingService.getDocuments(applicationId);
|
|
setDocuments(docs || []);
|
|
await fetchEorData();
|
|
} catch {
|
|
toast.error('Failed to upload document');
|
|
} finally {
|
|
setIsUploading(false);
|
|
}
|
|
};
|
|
|
|
const handleApprove = async () => {
|
|
try {
|
|
setIsApproving(true);
|
|
const activeInterview = interviews.find((i) =>
|
|
i.status !== 'Completed' &&
|
|
i.status !== 'Cancelled' &&
|
|
i.participants?.some((p: any) => p.userId === currentUser?.id)
|
|
);
|
|
|
|
if (approvalFile && applicationId) {
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', approvalFile);
|
|
formData.append('documentType', 'Approval Attachment');
|
|
let stageName: string | null = null;
|
|
if (activeInterview) {
|
|
if (activeInterview.level === 1 || activeInterview.level === '1') stageName = '1st Level Interview';
|
|
else if (activeInterview.level === 2 || activeInterview.level === '2') stageName = '2nd Level Interview';
|
|
else if (activeInterview.level === 3 || activeInterview.level === '3') stageName = '3rd Level Interview';
|
|
}
|
|
if (!stageName) {
|
|
if (application.status === 'Shortlisted' || application.status === 'Level 1 Interview Pending') stageName = '1st Level Interview';
|
|
else if (application.status === 'Level 1 Approved' || application.status === 'Level 2 Interview Pending') stageName = '2nd Level Interview';
|
|
else if (application.status === 'Level 2 Approved' || application.status === 'Level 3 Interview Pending') stageName = '3rd Level Interview';
|
|
}
|
|
if (stageName) formData.append('stage', stageName);
|
|
await onboardingService.uploadDocument(applicationId, formData);
|
|
} catch {
|
|
toast.error('Failed to upload document');
|
|
}
|
|
}
|
|
|
|
if (activeInterview) {
|
|
try {
|
|
await onboardingService.updateInterviewDecision({ interviewId: activeInterview.id, decision: 'Approved', remarks: approvalRemark });
|
|
toast.success('Interview approved successfully');
|
|
setShowApproveModal(false);
|
|
setApprovalRemark('');
|
|
setApprovalFile(null);
|
|
await fetchInterviews();
|
|
await fetchApplication();
|
|
return;
|
|
} catch {
|
|
toast.error('Failed to approve interview');
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!approvalRemark.trim()) {
|
|
toast.warning('Please enter a remark');
|
|
return;
|
|
}
|
|
|
|
let newStatus = application.status;
|
|
switch (application.status) {
|
|
case 'Shortlisted':
|
|
case 'Level 1 Interview Pending': newStatus = 'Level 1 Approved'; break;
|
|
case 'Level 1 Approved':
|
|
case 'Level 2 Interview Pending': newStatus = 'Level 2 Approved'; break;
|
|
case 'Level 2 Approved':
|
|
case 'Level 3 Interview Pending': newStatus = 'Level 3 Approved'; break;
|
|
case 'Level 3 Approved': newStatus = 'FDD Verification'; break;
|
|
case 'FDD Verification': newStatus = 'LOI In Progress'; break;
|
|
case 'LOI In Progress': newStatus = 'Security Deposit'; break;
|
|
case 'Security Deposit':
|
|
case 'Security Details':
|
|
case 'Payment Pending': newStatus = 'LOI Issued'; break;
|
|
case 'LOI Issued': newStatus = 'Dealer Code Generation'; break;
|
|
case 'Dealer Code Generation':
|
|
case 'Architecture Team Assigned':
|
|
case 'Architecture Document Upload':
|
|
case 'Architecture Team Completion':
|
|
case 'Statutory GST':
|
|
case 'Statutory PAN':
|
|
case 'Statutory Nodal':
|
|
case 'Statutory Check':
|
|
case 'Statutory Partnership':
|
|
case 'Statutory Firm Reg':
|
|
case 'Statutory Rental':
|
|
case 'Statutory Virtual Code':
|
|
case 'Statutory Domain':
|
|
case 'Statutory MSD':
|
|
case 'Statutory LOI Ack': newStatus = 'LOA Pending'; break;
|
|
case 'LOA Pending': newStatus = 'EOR In Progress'; break;
|
|
case 'EOR In Progress': newStatus = 'EOR Complete'; break;
|
|
case 'EOR Complete': newStatus = 'Inauguration'; break;
|
|
case 'Inauguration':
|
|
case 'Approved': newStatus = 'Onboarded'; break;
|
|
default: newStatus = 'Onboarded';
|
|
}
|
|
|
|
const policyManagedStages: Record<string, string> = {
|
|
'Level 1 Interview Pending': 'INTERVIEW_LEVEL_1',
|
|
'Level 2 Interview Pending': 'INTERVIEW_LEVEL_2',
|
|
'Level 2 Recommended': 'INTERVIEW_LEVEL_2',
|
|
'Level 3 Interview Pending': 'INTERVIEW_LEVEL_3',
|
|
'LOI In Progress': 'LOI_APPROVAL',
|
|
'LOA Pending': 'LOA_APPROVAL',
|
|
};
|
|
|
|
const stageCodeForPolicy = policyManagedStages[application.status];
|
|
if (stageCodeForPolicy) {
|
|
const response = await onboardingService.submitStageDecision({
|
|
applicationId: application.id,
|
|
stageCode: stageCodeForPolicy,
|
|
decision: 'Approved',
|
|
remarks: approvalRemark,
|
|
nextStatus: newStatus,
|
|
});
|
|
if (response.data?.statusUpdated) toast.success(response.message || 'Stage completed and moved to next step');
|
|
else toast.info(response.message || 'Approval recorded. Waiting for other mandatory approvers.');
|
|
} else {
|
|
await onboardingService.updateApplicationStatus(applicationId, { status: newStatus, remarks: approvalRemark });
|
|
}
|
|
|
|
if (newStatus === 'Onboarded') {
|
|
await onboardingService.createDealer({ applicationId });
|
|
toast.success('Application finalized and Dealer profile created!');
|
|
} else {
|
|
toast.success(`Application moved to ${newStatus}`);
|
|
}
|
|
|
|
setShowApproveModal(false);
|
|
setApprovalRemark('');
|
|
setApprovalFile(null);
|
|
await fetchApplication();
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to process approval');
|
|
} finally {
|
|
setIsApproving(false);
|
|
}
|
|
};
|
|
|
|
const handleReject = async () => {
|
|
try {
|
|
setIsRejecting(true);
|
|
const activeInterview = interviews.find((i) =>
|
|
i.status !== 'Completed' &&
|
|
i.status !== 'Cancelled' &&
|
|
i.participants?.some((p: any) => p.userId === currentUser?.id)
|
|
);
|
|
if (activeInterview) {
|
|
try {
|
|
await onboardingService.updateInterviewDecision({ interviewId: activeInterview.id, decision: 'Rejected', remarks: rejectionReason });
|
|
toast.success('Interview rejected');
|
|
setShowRejectModal(false);
|
|
setRejectionReason('');
|
|
await fetchInterviews();
|
|
await fetchApplication();
|
|
return;
|
|
} catch {
|
|
toast.error('Failed to reject interview');
|
|
return;
|
|
}
|
|
}
|
|
if (!rejectionReason.trim()) {
|
|
toast.warning('Please enter a reason for rejection');
|
|
return;
|
|
}
|
|
const policyManagedStages: Record<string, string> = {
|
|
'Level 1 Interview Pending': 'INTERVIEW_LEVEL_1',
|
|
'Level 2 Interview Pending': 'INTERVIEW_LEVEL_2',
|
|
'Level 2 Recommended': 'INTERVIEW_LEVEL_2',
|
|
'Level 3 Interview Pending': 'INTERVIEW_LEVEL_3',
|
|
'LOI In Progress': 'LOI_APPROVAL',
|
|
'LOA Pending': 'LOA_APPROVAL',
|
|
};
|
|
const stageCodeForPolicy = policyManagedStages[application.status];
|
|
if (stageCodeForPolicy) {
|
|
await onboardingService.submitStageDecision({
|
|
applicationId: application.id,
|
|
stageCode: stageCodeForPolicy,
|
|
decision: 'Rejected',
|
|
remarks: rejectionReason,
|
|
interviewId: activeInterview?.id,
|
|
});
|
|
} else {
|
|
await onboardingService.updateApplicationStatus(applicationId, { status: 'Rejected', remarks: rejectionReason });
|
|
}
|
|
toast.success('Application rejected');
|
|
setShowRejectModal(false);
|
|
setRejectionReason('');
|
|
await fetchApplication();
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to process rejection');
|
|
} finally {
|
|
setIsRejecting(false);
|
|
}
|
|
};
|
|
|
|
const handleGenerateDealerCodes = async () => {
|
|
try {
|
|
await onboardingService.generateDealerCodes(applicationId);
|
|
toast.success('Dealer codes generated successfully');
|
|
await fetchApplication();
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to generate dealer codes');
|
|
}
|
|
};
|
|
|
|
const handleAssignArchitecture = async () => {
|
|
if (!architectureLeadId) {
|
|
toast.warning('Please select an architecture lead');
|
|
return;
|
|
}
|
|
try {
|
|
setIsAssigningArchitecture(true);
|
|
await onboardingService.assignArchitectureTeam(applicationId, architectureLeadId);
|
|
toast.success('Architecture team assigned successfully');
|
|
setShowAssignArchitectureModal(false);
|
|
await fetchApplication();
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to assign architecture team');
|
|
} finally {
|
|
setIsAssigningArchitecture(false);
|
|
}
|
|
};
|
|
|
|
const handleUpdateArchitectureStatus = async () => {
|
|
try {
|
|
setIsUpdatingArchitecture(true);
|
|
await onboardingService.updateArchitectureStatus(applicationId, architectureStatus, architectureRemarks);
|
|
toast.success('Architecture status updated successfully');
|
|
setShowArchitectureStatusModal(false);
|
|
await fetchApplication();
|
|
} catch {
|
|
toast.error('Failed to update architecture status');
|
|
} finally {
|
|
setIsUpdatingArchitecture(false);
|
|
}
|
|
};
|
|
|
|
const handleAddParticipant = async () => {
|
|
if (!selectedUser) {
|
|
toast.warning('Please select a user');
|
|
return;
|
|
}
|
|
try {
|
|
setIsAssigningParticipant(true);
|
|
const u = Array.isArray(users) ? users.find((user) => user.id === selectedUser) : null;
|
|
if (u && (u.role === 'FDD' || u.roleCode === 'FDD')) {
|
|
await onboardingService.assignFddAgency({ applicationId, assignedToAgency: selectedUser });
|
|
toast.info(`${u.fullName || u.name} assigned as FDD Agency based on role.`);
|
|
}
|
|
await onboardingService.addParticipant({
|
|
requestId: applicationId,
|
|
requestType: 'application',
|
|
userId: selectedUser,
|
|
participantType: participantType || 'contributor',
|
|
});
|
|
toast.success('User assigned successfully!');
|
|
await fetchApplication();
|
|
setSelectedUser('');
|
|
setShowAssignModal(false);
|
|
} catch {
|
|
toast.error('Failed to assign user');
|
|
} finally {
|
|
setIsAssigningParticipant(false);
|
|
}
|
|
};
|
|
|
|
const handleRetriggerEvaluators = async () => {
|
|
try {
|
|
setLoading(true);
|
|
await onboardingService.retriggerEvaluators(applicationId);
|
|
toast.success('Evaluators re-assigned successfully');
|
|
await fetchApplication();
|
|
} catch {
|
|
toast.error('Failed to re-assign evaluators');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const maybeFetchUsersForModal = useCallback(async () => {
|
|
if (showScheduleModal && application) {
|
|
await fetchUsers(interviewType);
|
|
prefillInterviewParticipants();
|
|
return;
|
|
}
|
|
if ((showAssignArchitectureModal || showAssignModal) && application) {
|
|
await fetchUsers();
|
|
}
|
|
}, [
|
|
showScheduleModal,
|
|
showAssignArchitectureModal,
|
|
showAssignModal,
|
|
application,
|
|
interviewType,
|
|
fetchUsers,
|
|
prefillInterviewParticipants,
|
|
]);
|
|
|
|
return {
|
|
handleAddInterviewer,
|
|
handleRemoveInterviewer,
|
|
fetchUsers,
|
|
maybeFetchUsersForModal,
|
|
handleScheduleInterview,
|
|
handleRescheduleInterview,
|
|
handleCancelInterview,
|
|
handleConfirmCancelInterview,
|
|
handleUpload,
|
|
handleApprove,
|
|
handleReject,
|
|
handleGenerateDealerCodes,
|
|
handleAssignArchitecture,
|
|
handleUpdateArchitectureStatus,
|
|
handleAddParticipant,
|
|
handleRetriggerEvaluators,
|
|
};
|
|
}
|