333 lines
18 KiB
TypeScript
333 lines
18 KiB
TypeScript
import { Check, CheckCircle, Clock, Loader2 } from 'lucide-react';
|
||
import { toast } from 'sonner';
|
||
import { onboardingService } from '../../../services/onboarding.service';
|
||
import { Button } from '../../ui/button';
|
||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog';
|
||
import { Input } from '../../ui/input';
|
||
import { Label } from '../../ui/label';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
|
||
import { Textarea } from '../../ui/textarea';
|
||
|
||
interface ApplicationDetailsActionModalsProps {
|
||
application: any;
|
||
fetchApplication: () => void;
|
||
showApproveModal: boolean;
|
||
setShowApproveModal: (value: boolean) => void;
|
||
approvalRemark: string;
|
||
setApprovalRemark: (value: string) => void;
|
||
setApprovalFile: (file: File | null) => void;
|
||
isApproving: boolean;
|
||
handleApprove: () => void;
|
||
showOnboardModal: boolean;
|
||
setShowOnboardModal: (value: boolean) => void;
|
||
isOnboarding: boolean;
|
||
setIsOnboarding: (value: boolean) => void;
|
||
showRejectModal: boolean;
|
||
setShowRejectModal: (value: boolean) => void;
|
||
rejectionReason: string;
|
||
setRejectionReason: (value: string) => void;
|
||
isRejecting: boolean;
|
||
handleReject: () => void;
|
||
showScheduleModal: boolean;
|
||
setShowScheduleModal: (value: boolean) => void;
|
||
interviewType: string;
|
||
setInterviewType: (value: string) => void;
|
||
interviewMode: string;
|
||
setInterviewMode: (value: string) => void;
|
||
interviewDate: string;
|
||
setInterviewDate: (value: string) => void;
|
||
meetingLink: string;
|
||
setMeetingLink: (value: string) => void;
|
||
location: string;
|
||
setLocation: (value: string) => void;
|
||
isInterviewCompleted: (level: number) => boolean;
|
||
isInterviewActive: (level: number) => boolean;
|
||
users: any[];
|
||
selectedInterviewerId: string;
|
||
setSelectedInterviewerId: (value: string) => void;
|
||
handleAddInterviewer: () => void;
|
||
scheduledInterviewParticipants: any[];
|
||
handleRemoveInterviewer: (id: string) => void;
|
||
isScheduling: boolean;
|
||
handleScheduleInterview: () => void;
|
||
showAssignArchitectureModal: boolean;
|
||
setShowAssignArchitectureModal: (value: boolean) => void;
|
||
architectureLeadId: string;
|
||
setArchitectureLeadId: (value: string) => void;
|
||
isAssigningArchitecture: boolean;
|
||
handleAssignArchitecture: () => void;
|
||
showArchitectureStatusModal: boolean;
|
||
setShowArchitectureStatusModal: (value: boolean) => void;
|
||
architectureStatus: string;
|
||
setArchitectureStatus: (value: string) => void;
|
||
architectureRemarks: string;
|
||
setArchitectureRemarks: (value: string) => void;
|
||
isUpdatingArchitecture: boolean;
|
||
handleUpdateArchitectureStatus: () => void;
|
||
}
|
||
|
||
export function ApplicationDetailsActionModals(props: ApplicationDetailsActionModalsProps) {
|
||
const {
|
||
application,
|
||
fetchApplication,
|
||
showApproveModal,
|
||
setShowApproveModal,
|
||
approvalRemark,
|
||
setApprovalRemark,
|
||
setApprovalFile,
|
||
isApproving,
|
||
handleApprove,
|
||
showOnboardModal,
|
||
setShowOnboardModal,
|
||
isOnboarding,
|
||
setIsOnboarding,
|
||
showRejectModal,
|
||
setShowRejectModal,
|
||
rejectionReason,
|
||
setRejectionReason,
|
||
isRejecting,
|
||
handleReject,
|
||
showScheduleModal,
|
||
setShowScheduleModal,
|
||
interviewType,
|
||
setInterviewType,
|
||
interviewMode,
|
||
setInterviewMode,
|
||
interviewDate,
|
||
setInterviewDate,
|
||
meetingLink,
|
||
setMeetingLink,
|
||
location,
|
||
setLocation,
|
||
isInterviewCompleted,
|
||
isInterviewActive,
|
||
users,
|
||
selectedInterviewerId,
|
||
setSelectedInterviewerId,
|
||
handleAddInterviewer,
|
||
scheduledInterviewParticipants,
|
||
handleRemoveInterviewer,
|
||
isScheduling,
|
||
handleScheduleInterview,
|
||
showAssignArchitectureModal,
|
||
setShowAssignArchitectureModal,
|
||
architectureLeadId,
|
||
setArchitectureLeadId,
|
||
isAssigningArchitecture,
|
||
handleAssignArchitecture,
|
||
showArchitectureStatusModal,
|
||
setShowArchitectureStatusModal,
|
||
architectureStatus,
|
||
setArchitectureStatus,
|
||
architectureRemarks,
|
||
setArchitectureRemarks,
|
||
isUpdatingArchitecture,
|
||
handleUpdateArchitectureStatus,
|
||
} = props;
|
||
|
||
return (
|
||
<>
|
||
<Dialog open={showApproveModal} onOpenChange={setShowApproveModal}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Approve Application</DialogTitle>
|
||
<DialogDescription>Provide approval remarks and optionally attach supporting documents.</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Remark (Required)</Label>
|
||
<Textarea placeholder="Enter approval remarks..." value={approvalRemark} onChange={(e) => setApprovalRemark(e.target.value)} className="mt-2" rows={4} />
|
||
</div>
|
||
<div>
|
||
<Label>Attach File (Optional)</Label>
|
||
<Input type="file" className="mt-2" onChange={(e) => setApprovalFile(e.target.files ? e.target.files[0] : null)} />
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<Button variant="outline" className="flex-1" onClick={() => setShowApproveModal(false)} disabled={isApproving}>Cancel</Button>
|
||
<Button className="flex-1 bg-green-600 hover:bg-green-700" onClick={handleApprove} disabled={isApproving}>
|
||
{isApproving ? <><Loader2 className="w-4 h-4 mr-2 animate-spin" />Approving...</> : 'Submit Approval'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showOnboardModal} onOpenChange={setShowOnboardModal}>
|
||
<DialogContent className="max-w-md">
|
||
<DialogHeader>
|
||
<div className="mx-auto w-12 h-12 bg-green-100 rounded-full flex items-center justify-center mb-4">
|
||
<CheckCircle className="w-8 h-8 text-green-600" />
|
||
</div>
|
||
<DialogTitle className="text-center text-xl font-bold">Finalize Onboarding</DialogTitle>
|
||
<DialogDescription className="text-center pt-2">
|
||
You are about to officially onboard <span className="font-semibold text-slate-900">{application.name}</span> as a Royal Enfield dealer.
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200 mt-4 space-y-3">
|
||
<div className="flex items-start gap-3"><div className="mt-1 bg-green-500 rounded-full p-0.5"><Check className="w-3 h-3 text-white" /></div><p className="text-sm text-slate-600">Official dealer profile will be created.</p></div>
|
||
<div className="flex items-start gap-3"><div className="mt-1 bg-green-500 rounded-full p-0.5"><Check className="w-3 h-3 text-white" /></div><p className="text-sm text-slate-600">User account will be activated with role <span className="font-medium text-slate-900">Dealer</span>.</p></div>
|
||
<div className="flex items-start gap-3"><div className="mt-1 bg-green-500 rounded-full p-0.5"><Check className="w-3 h-3 text-white" /></div><p className="text-sm text-slate-600">Primary outlet will be registered in the system.</p></div>
|
||
</div>
|
||
<div className="mt-6 flex flex-col gap-3">
|
||
<Button
|
||
className="w-full bg-green-600 hover:bg-green-700 h-11 text-lg font-semibold shadow-lg shadow-green-100"
|
||
onClick={async () => {
|
||
setIsOnboarding(true);
|
||
try {
|
||
await onboardingService.createDealer({ applicationId: application.id });
|
||
toast.success('Dealer profile and login account created successfully!');
|
||
setShowOnboardModal(false);
|
||
fetchApplication();
|
||
} catch {
|
||
toast.error('Failed to create dealer profile');
|
||
} finally {
|
||
setIsOnboarding(false);
|
||
}
|
||
}}
|
||
disabled={isOnboarding}
|
||
>
|
||
{isOnboarding ? <><Loader2 className="w-5 h-5 mr-2 animate-spin" />Processing Onboarding...</> : 'Confirm & Onboard Dealer'}
|
||
</Button>
|
||
<Button variant="ghost" className="w-full text-slate-500 hover:text-slate-700" onClick={() => setShowOnboardModal(false)} disabled={isOnboarding}>Cancel</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showRejectModal} onOpenChange={setShowRejectModal}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Reject Application</DialogTitle>
|
||
<DialogDescription>Please provide a clear reason for rejecting this application.</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Reason for Rejection (Required)</Label>
|
||
<Textarea placeholder="Enter rejection reason..." value={rejectionReason} onChange={(e) => setRejectionReason(e.target.value)} className="mt-2" rows={4} />
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<Button variant="outline" className="flex-1" onClick={() => setShowRejectModal(false)} disabled={isRejecting}>Cancel</Button>
|
||
<Button variant="destructive" className="flex-1" onClick={handleReject} disabled={isRejecting}>
|
||
{isRejecting ? <><Loader2 className="w-4 h-4 mr-2 animate-spin" />Rejecting...</> : 'Confirm Rejection'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showScheduleModal} onOpenChange={setShowScheduleModal}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Schedule Interview</DialogTitle>
|
||
<DialogDescription>Set up an interview session with the applicant and relevant team members.</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Interview Type</Label>
|
||
<Select value={interviewType} onValueChange={setInterviewType}>
|
||
<SelectTrigger className="mt-2"><SelectValue placeholder="Select interview type" /></SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="level1" disabled={isInterviewCompleted(1) || isInterviewActive(1)}><div className="flex items-center justify-between w-full"><span>Level 1</span>{isInterviewCompleted(1) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(1) && <Clock className="w-4 h-4 text-amber-500 ml-2 inline" />}</div></SelectItem>
|
||
<SelectItem value="level2" disabled={!isInterviewCompleted(1) || isInterviewCompleted(2) || isInterviewActive(2)}><div className="flex items-center justify-between w-full"><span>Level 2</span>{!isInterviewCompleted(1) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L1)</span>}{isInterviewCompleted(2) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(2) && <Clock className="w-4 h-4 text-amber-500 ml-2 inline" />}</div></SelectItem>
|
||
<SelectItem value="level3" disabled={!isInterviewCompleted(2) || isInterviewCompleted(3) || isInterviewActive(3)}><div className="flex items-center justify-between w-full"><span>Level 3</span>{!isInterviewCompleted(2) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L2)</span>}{isInterviewCompleted(3) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}{isInterviewActive(3) && <Clock className="w-4 h-4 text-amber-500 ml-2 inline" />}</div></SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div>
|
||
<Label>Interview Mode</Label>
|
||
<Select value={interviewMode} onValueChange={setInterviewMode}>
|
||
<SelectTrigger className="mt-2"><SelectValue placeholder="Select interview mode" /></SelectTrigger>
|
||
<SelectContent><SelectItem value="virtual">Virtual</SelectItem><SelectItem value="physical">Physical</SelectItem></SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div><Label>Date & Time</Label><Input type="datetime-local" className="mt-2" value={interviewDate} onChange={(e) => setInterviewDate(e.target.value)} /></div>
|
||
{interviewMode === 'virtual' && <div><Label>Meeting Link</Label><Input placeholder="https://meet.google.com/..." className="mt-2" value={meetingLink} onChange={(e) => setMeetingLink(e.target.value)} /></div>}
|
||
{interviewMode === 'physical' && <div><Label>Location</Label><Input placeholder="Enter interview location address" className="mt-2" value={location} onChange={(e) => setLocation(e.target.value)} /></div>}
|
||
<div>
|
||
<Label>Interviewers</Label>
|
||
<div className="flex gap-2 mt-2">
|
||
<Select value={selectedInterviewerId} onValueChange={setSelectedInterviewerId}>
|
||
<SelectTrigger className="flex-1"><SelectValue placeholder="Select interviewer" /></SelectTrigger>
|
||
<SelectContent>{users.map((user) => <SelectItem key={user.id} value={user.id}>{user.fullName || user.name} ({user.role?.roleName || user.roleCode})</SelectItem>)}</SelectContent>
|
||
</Select>
|
||
<Button onClick={handleAddInterviewer} type="button" variant="secondary">Add</Button>
|
||
</div>
|
||
{scheduledInterviewParticipants.length > 0 && (
|
||
<div className="mt-3 space-y-2">
|
||
<Label className="text-xs text-muted-foreground">Selected Interviewers:</Label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{scheduledInterviewParticipants.map((p) => (
|
||
<div key={p.id} className="flex items-center gap-1 bg-secondary px-2 py-1 rounded text-sm">
|
||
<span>{p.fullName || p.name || 'Unknown'}</span>
|
||
<button onClick={() => handleRemoveInterviewer(p.id)} className="text-muted-foreground hover:text-destructive">×</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<Button variant="outline" className="flex-1" onClick={() => setShowScheduleModal(false)} disabled={isScheduling}>Cancel</Button>
|
||
<Button className="flex-1 bg-amber-600 hover:bg-amber-700" onClick={handleScheduleInterview} disabled={isScheduling}>{isScheduling ? 'Scheduling...' : 'Schedule'}</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showAssignArchitectureModal} onOpenChange={setShowAssignArchitectureModal}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Assign Architecture Team</DialogTitle>
|
||
<DialogDescription>Select an architecture team lead for site planning and blueprints.</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Select Architecture Lead</Label>
|
||
<Select value={architectureLeadId} onValueChange={setArchitectureLeadId}>
|
||
<SelectTrigger className="mt-2"><SelectValue placeholder="Search users..." /></SelectTrigger>
|
||
<SelectContent>
|
||
{users.filter(u => u.roleCode === 'ARCHITECTURE' || u.role?.roleCode === 'ARCHITECTURE' || u.role === 'Architecture' || u.role === 'Architecture Team').map((u) => (
|
||
<SelectItem key={u.id} value={u.id}>{u.fullName} ({u.email})</SelectItem>
|
||
))}
|
||
{users.filter(u => u.roleCode === 'ARCHITECTURE' || u.role?.roleCode === 'ARCHITECTURE' || u.role === 'Architecture' || u.role === 'Architecture Team').length === 0 && users.map((u) => (
|
||
<SelectItem key={u.id} value={u.id}>{u.fullName} ({u.roleCode || u.role})</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<Button variant="outline" className="flex-1" onClick={() => setShowAssignArchitectureModal(false)} disabled={isAssigningArchitecture}>Cancel</Button>
|
||
<Button className="flex-1 bg-blue-600 hover:bg-blue-700" onClick={handleAssignArchitecture} disabled={isAssigningArchitecture}>{isAssigningArchitecture ? 'Assigning...' : 'Assign Team'}</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={showArchitectureStatusModal} onOpenChange={setShowArchitectureStatusModal}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Update Architecture Status</DialogTitle>
|
||
<DialogDescription>Mark the architectural work as completed and optionally add remarks.</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label>Status</Label>
|
||
<Select value={architectureStatus} onValueChange={setArchitectureStatus}>
|
||
<SelectTrigger className="mt-2"><SelectValue placeholder="Select status" /></SelectTrigger>
|
||
<SelectContent><SelectItem value="COMPLETED">Completed</SelectItem><SelectItem value="REJECTED">Rejected / Needs Revision</SelectItem></SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div>
|
||
<Label>Remarks (Optional)</Label>
|
||
<Textarea placeholder="Enter any planning or site-visit remarks..." value={architectureRemarks} onChange={(e) => setArchitectureRemarks(e.target.value)} className="mt-2" rows={4} />
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<Button variant="outline" className="flex-1" onClick={() => setShowArchitectureStatusModal(false)} disabled={isUpdatingArchitecture}>Cancel</Button>
|
||
<Button className="flex-1 bg-blue-600 hover:bg-blue-700" onClick={handleUpdateArchitectureStatus} disabled={isUpdatingArchitecture}>{isUpdatingArchitecture ? 'Updating...' : 'Update Status'}</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</>
|
||
);
|
||
}
|