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>; interviewType: string; setInterviewType: Dispatch>; interviewMode: string; setInterviewMode: Dispatch>; meetingLink: string; setMeetingLink: Dispatch>; location: string; setLocation: Dispatch>; scheduledInterviewParticipants: any[]; uploadFile: File | null; uploadDocType: string; selectedStage: string | null; setIsApproving: Dispatch>; setShowApproveModal: Dispatch>; setApprovalRemark: Dispatch>; setApprovalFile: Dispatch>; setIsRejecting: Dispatch>; setShowRejectModal: Dispatch>; setRejectionReason: Dispatch>; setIsAssigningArchitecture: Dispatch>; setShowAssignArchitectureModal: Dispatch>; setIsUpdatingArchitecture: Dispatch>; setShowArchitectureStatusModal: Dispatch>; setIsAssigningParticipant: Dispatch>; setSelectedUser: Dispatch>; setShowAssignModal: Dispatch>; setLoading: Dispatch>; setIsScheduling: Dispatch>; setShowScheduleModal: Dispatch>; setShowCancelInterviewModal: Dispatch>; interviewIdToCancel: string; setInterviewIdToCancel: Dispatch>; interviewToReschedule: any; setInterviewToReschedule: Dispatch>; setIsCancellingInterview: Dispatch>; setIsUploading: Dispatch>; setShowUploadForm: Dispatch>; setUploadFile: Dispatch>; setUploadDocType: Dispatch>; setDocuments: Dispatch>; selectedInterviewerId: string; setSelectedInterviewerId: Dispatch>; setScheduledInterviewParticipants: Dispatch>; setUsers: Dispatch>; showScheduleModal: boolean; showAssignArchitectureModal: boolean; showAssignModal: boolean; fetchApplication: (silent?: boolean) => Promise; fetchInterviews: () => Promise; fetchEorData: () => Promise; } 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 = { 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 = { '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 = { '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, }; }