Dealer_Onboard_Frontend/src/features/onboarding/components/application-details/ApplicationDetailsSidebar.tsx

480 lines
23 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
AlertCircle,
Calendar,
CheckCircle,
ChevronDown,
ClipboardCheck,
Clock,
GitBranch,
Info,
Lock,
Mail,
MessageSquare,
Star,
User,
XCircle,
Zap,
} from 'lucide-react';
import { cn, formatDateTime } from '@/components/ui/utils';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Label } from '@/components/ui/label';
import { Progress } from '@/components/ui/progress';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
interface ApplicationDetailsSidebarProps {
application: any;
permissions: any;
getDeposit: (type: string) => any;
isNonResponsive: boolean;
isAdmin: boolean;
currentUserStageAction: any;
currentUserEvaluation: any;
onOpenApproveModal: () => void;
onOpenRejectModal: () => void;
onOpenWorknote: () => void;
onOpenScheduleModal: () => void;
currentUser: any;
handleGenerateDealerCodes: () => void;
onOpenAssignArchitectureModal: () => void;
onOpenAssignFdd: () => void;
showAssignFddModal: boolean;
setShowAssignFddModal: (value: boolean) => void;
fddAgencies: any[];
selectedAgencyId: string;
setSelectedAgencyId: (value: string) => void;
isAssigningAgency: boolean;
handleAssignAgency: () => void;
activeInterviewForUser: any;
hasSubmittedFeedback: boolean;
setSelectedInterviewForFeedback: (value: any) => void;
setShowKTMatrixModal: (value: boolean) => void;
setShowLevel2FeedbackModal: (value: boolean) => void;
setShowLevel3FeedbackModal: (value: boolean) => void;
onGoToDashboard: () => void;
showAssignModal: boolean;
setShowAssignModal: (value: boolean) => void;
selectedUser: string;
setSelectedUser: (value: string) => void;
users: any[];
participantType: string;
setParticipantType: (value: string) => void;
handleAddParticipant: () => void;
isAssigningParticipant: boolean;
}
export function ApplicationDetailsSidebar(props: ApplicationDetailsSidebarProps) {
const {
application,
permissions,
getDeposit,
isNonResponsive,
isAdmin,
currentUserStageAction,
currentUserEvaluation,
onOpenApproveModal,
onOpenRejectModal,
onOpenWorknote,
onOpenScheduleModal,
currentUser,
handleGenerateDealerCodes,
onOpenAssignArchitectureModal,
onOpenAssignFdd,
showAssignFddModal,
setShowAssignFddModal,
fddAgencies,
selectedAgencyId,
setSelectedAgencyId,
isAssigningAgency,
handleAssignAgency,
activeInterviewForUser,
hasSubmittedFeedback,
setSelectedInterviewForFeedback,
setShowKTMatrixModal,
setShowLevel2FeedbackModal,
setShowLevel3FeedbackModal,
onGoToDashboard,
showAssignModal,
setShowAssignModal,
selectedUser,
setSelectedUser,
users,
participantType,
setParticipantType,
handleAddParticipant,
isAssigningParticipant,
} = props;
return (
<div className="space-y-6">
<Card data-testid="onboarding-details-summary-card">
<CardHeader>
<CardTitle>Summary</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<p className="text-slate-600">Registration ID</p>
<p className="text-slate-900" data-testid="onboarding-details-summary-reg-id">{application.registrationNumber}</p>
</div>
<div>
<p className="text-slate-600">Current Status</p>
<Badge
className={cn(
"mt-1",
application.status === 'Onboarded' ? "bg-green-600 hover:bg-green-700 text-white" :
application.status === 'Rejected' ? "bg-red-600" :
"bg-amber-600"
)}
data-testid="onboarding-details-summary-status"
>
{application.status}
</Badge>
</div>
{application.rank && (
<div>
<p className="text-slate-600">Rank</p>
<p className="text-slate-900" data-testid="onboarding-details-summary-rank">
{application.rank} of {application.totalApplicantsAtLocation}
<span className="text-slate-500"> in {application.preferredLocation}</span>
</p>
</div>
)}
<div>
<p className="text-slate-600">Progress</p>
<div className="flex items-center gap-2 mt-2">
<Progress value={application.progress} className="flex-1" data-testid="onboarding-details-summary-progress-bar" />
<span className="text-slate-900" data-testid="onboarding-details-summary-progress-text">{application.progress}%</span>
</div>
</div>
{application.deadline && (
<div>
<p className="text-slate-600">Questionnaire Deadline</p>
<p className="text-slate-900" data-testid="onboarding-details-summary-deadline">{formatDateTime(application.deadline)}</p>
</div>
)}
</CardContent>
</Card>
{(application.isShortlisted !== false || application.status === 'Submitted') && (
<Card data-testid="onboarding-details-actions-card">
<CardHeader>
<CardTitle>Actions</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{permissions.isLoaLocked && (
<Alert variant="destructive" className="mb-4 bg-amber-50 border-amber-200 text-amber-800" data-testid="onboarding-details-loa-locked-alert">
<Lock className="w-4 h-4 text-amber-600" />
<AlertTitle className="text-amber-900 font-semibold">LOA approval locked</AlertTitle>
<AlertDescription className="text-amber-800">
<span className="font-medium">First Fill</span> (later-stage payment) must be verified by Finance
before LOA approval can proceed. This is separate from the initial security deposit before LOI Issued.
</AlertDescription>
</Alert>
)}
{getDeposit('FIRST_FILL')?.status === 'Verified' &&
application.status !== 'LOA Pending' &&
!['LOA Issued', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded', 'Rejected'].includes(
application.status,
) && (
<Alert className="mb-4 border-violet-200 bg-violet-50/90 text-violet-950" data-testid="onboarding-details-first-fill-verified-alert">
<Info className="h-4 w-4 text-violet-700" />
<AlertTitle className="font-semibold">First Fill verified on file</AlertTitle>
<AlertDescription className="text-sm text-violet-900/90 leading-relaxed">
Finance has verified the <span className="font-medium">First Fill</span> payment. The application
status was <span className="font-medium">not</span> changed until you reach{' '}
<span className="font-medium">LOA Pending</span>. When you get there, LOA approval will not be
blocked by payment (same pattern as recording the initial security deposit before the LOI
security step).
</AlertDescription>
</Alert>
)}
{permissions.isSecurityDetailsLocked && (
<Alert variant="destructive" className="mb-4 bg-amber-50 border-amber-200 text-amber-800" data-testid="onboarding-details-security-locked-alert">
<Lock className="w-4 h-4 text-amber-600" />
<AlertTitle className="text-amber-900 font-semibold">Security Details approval locked</AlertTitle>
<AlertDescription className="text-amber-800">
Finance must verify the <span className="font-medium">Security Deposit</span> before this stage can be approved.
You can still use <span className="font-medium">Reject</span> if needed.
</AlertDescription>
</Alert>
)}
{['Security Details', 'Payment Pending'].includes(application.status) && (
<Alert className="mb-4 border-sky-200 bg-sky-50/90 text-sky-900" data-testid="onboarding-details-security-review-alert">
<Info className="h-4 w-4 text-sky-700" />
<AlertTitle className="text-sky-950 font-semibold">Security Details review</AlertTitle>
<AlertDescription className="text-sm text-sky-900/90 leading-relaxed">
Check the initial security deposit on the <span className="font-medium">Payments</span> tab (Finance
may have already marked it verified). When satisfied, use <span className="font-medium">Approve</span>{' '}
to move to <span className="font-medium">LOI Issued</span>.
</AlertDescription>
</Alert>
)}
{isNonResponsive && isAdmin && (
<Alert variant="destructive" className="mb-4 bg-red-50 border-red-200 text-red-800" data-testid="onboarding-details-non-responsive-alert">
<AlertCircle className="w-4 h-4 text-red-600" />
<AlertTitle className="text-red-900 font-black uppercase tracking-tighter"> Non-Responsive Flag</AlertTitle>
<AlertDescription className="text-red-800 text-xs font-bold leading-tight">
FDD Audit has flagged this applicant. Review audit logs before approval.
</AlertDescription>
</Alert>
)}
{isAdmin && (application.status === 'Level 3 Approved' || application.status === 'FDD Verification') && (!application.fddAssignments || application.fddAssignments.length === 0) && (
<Alert className="mb-4 bg-amber-50 border-amber-200 text-amber-800" data-testid="onboarding-details-fdd-assignment-alert">
<AlertCircle className="w-4 h-4 text-amber-600" />
<AlertTitle className="text-amber-900 font-bold">FDD Assignment Required</AlertTitle>
<AlertDescription className="text-amber-800 font-medium">
This application is pending financial due diligence. Please assign an FDD Agency to proceed with the audit.
</AlertDescription>
</Alert>
)}
{permissions.canApprove && (
<Button className="w-full bg-green-600 hover:bg-green-700 font-bold" onClick={onOpenApproveModal} data-testid="onboarding-details-approve-button">
<CheckCircle className="w-4 h-4 mr-2" />
{['Inauguration', 'Approved'].includes(application.status) ? 'Onboard Dealer' : 'Approve'}
</Button>
)}
{permissions.canReject && (
<Button variant="destructive" className="w-full font-bold" onClick={onOpenRejectModal} data-testid="onboarding-details-reject-button">
<XCircle className="w-4 h-4 mr-2" />
Reject
</Button>
)}
{permissions.showDecisionMessage && (
<div
className={`w-full p-2 text-center rounded border ${(currentUserStageAction?.decision === 'Approved' || currentUserEvaluation?.decision === 'Approved' || currentUserEvaluation?.recommendation === 'Approved' || currentUserEvaluation?.decision === 'Selected') ? 'bg-green-50 border-green-200 text-green-700' : 'bg-red-50 border-red-200 text-red-700'}`}
data-testid="onboarding-details-decision-message"
>
You have {(currentUserStageAction?.decision === 'Approved' || currentUserEvaluation?.decision === 'Approved' || currentUserEvaluation?.recommendation === 'Approved' || currentUserEvaluation?.decision === 'Selected') ? 'Approved' : 'Rejected'}
</div>
)}
<Separator />
<Button variant="outline" className="w-full" onClick={onOpenWorknote} data-testid="onboarding-details-worknote-button">
<MessageSquare className="w-4 h-4 mr-2" />
Work Note
</Button>
{permissions.canSchedule && (
<Button variant="outline" className="w-full" onClick={onOpenScheduleModal} data-testid="onboarding-details-schedule-button">
<Calendar className="w-4 h-4 mr-2" />
Schedule Interview
</Button>
)}
{currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) &&
['Dealer Code Generation', 'LOA Pending', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion'].includes(application.status) && (
<>
{!application.dealerCode && (
<Button className="w-full bg-blue-600 hover:bg-blue-700" onClick={handleGenerateDealerCodes} data-testid="onboarding-details-generate-dealer-codes">
<Zap className="w-4 h-4 mr-2" />
Generate Dealer Codes
</Button>
)}
{application.dealerCode && !application.architectureAssignedTo && (
<Button
variant="outline"
className="w-full border-blue-200 hover:bg-blue-50 text-blue-700"
onClick={onOpenAssignArchitectureModal}
data-testid="onboarding-details-assign-architecture"
>
<GitBranch className="w-4 h-4 mr-2" />
Assign Architecture Team
</Button>
)}
</>
)}
{isAdmin &&
['Level 3 Approved', 'Level 3 Recommended', 'FDD Verification', 'FDD In Progress'].includes(application.status) &&
(!application.fddAssignments || application.fddAssignments.length === 0) && (
<Dialog open={showAssignFddModal} onOpenChange={setShowAssignFddModal}>
<Button
variant="outline"
className="w-full border-purple-200 hover:bg-purple-50 text-purple-700"
onClick={onOpenAssignFdd}
data-testid="onboarding-details-assign-fdd"
>
<ClipboardCheck className="w-4 h-4 mr-2" />
Assign FDD
</Button>
<DialogContent data-testid="onboarding-details-assign-fdd-modal">
<DialogHeader>
<DialogTitle>Assign FDD Agency</DialogTitle>
<DialogDescription>
Select an FDD partner agency to perform the financial due diligence audit for this application.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>FDD Agency</Label>
<Select value={selectedAgencyId} onValueChange={setSelectedAgencyId}>
<SelectTrigger className="mt-2" data-testid="onboarding-details-assign-fdd-select">
<SelectValue placeholder={fddAgencies?.length ? 'Choose partner agency...' : 'No agencies available'} />
</SelectTrigger>
<SelectContent>
{(fddAgencies || []).map((agency: any) => (
<SelectItem key={agency.id} value={agency.id}>
{agency.fullName || agency.name} ({agency.email})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button
className="w-full bg-amber-600 hover:bg-amber-700 font-bold h-11"
onClick={handleAssignAgency}
disabled={isAssigningAgency || !selectedAgencyId}
data-testid="onboarding-details-assign-fdd-submit"
>
{isAssigningAgency ? 'Assigning...' : 'Assign Agency'}
</Button>
</div>
</DialogContent>
</Dialog>
)}
{activeInterviewForUser && !hasSubmittedFeedback && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="w-full" data-testid="onboarding-details-feedback-dropdown-trigger">
<Star className="w-4 h-4 mr-2" />
Interview Feedback
<ChevronDown className="w-4 h-4 ml-auto" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" data-testid="onboarding-details-feedback-dropdown-content">
<DropdownMenuItem
key={activeInterviewForUser.id}
data-testid={`onboarding-details-feedback-item-${activeInterviewForUser.id}`}
onClick={() => {
setSelectedInterviewForFeedback(activeInterviewForUser);
if (activeInterviewForUser.level === 1) setShowKTMatrixModal(true);
else if (activeInterviewForUser.level === 2) setShowLevel2FeedbackModal(true);
else setShowLevel3FeedbackModal(true);
}}
>
Level {activeInterviewForUser.level} - {activeInterviewForUser.interviewType}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
{application.status === 'Questionnaire Pending' && (
<>
<Button variant="outline" className="w-full" data-testid="onboarding-details-send-reminder">
<Mail className="w-4 h-4 mr-2" />
Send Reminder
</Button>
<Button variant="outline" className="w-full" data-testid="onboarding-details-extend-deadline">
<Clock className="w-4 h-4 mr-2" />
Extend Deadline
</Button>
</>
)}
{application.dealer && (
<div className="p-4 bg-green-50 border border-green-200 rounded-lg space-y-3" data-testid="onboarding-details-dealer-active-banner">
<div className="flex items-center gap-2 text-green-800 font-semibold">
<CheckCircle className="w-5 h-5 text-green-600" />
Dealer Profile Active
</div>
<div className="text-sm text-green-700">
This application has been successfully onboarded as a dealer. A user account has been created for the dealer.
</div>
{application.dealerCode && (
<div className="flex items-center justify-between text-xs font-mono bg-white p-2 rounded border border-green-100" data-testid="onboarding-details-active-dealer-code">
<span className="text-slate-500">Dealer Code:</span>
<span className="font-bold text-slate-900">{application.dealerCode.code}</span>
</div>
)}
<Button className="w-full bg-green-600 hover:bg-green-700 text-white" onClick={onGoToDashboard} data-testid="onboarding-details-goto-dashboard">
<Zap className="w-4 h-4 mr-2" />
Go to Dealer Dashboard
</Button>
</div>
)}
{currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && (
<Dialog open={showAssignModal} onOpenChange={setShowAssignModal}>
<DialogTrigger asChild>
<Button variant="outline" className="w-full" data-testid="onboarding-details-assign-user-trigger">
<User className="w-4 h-4 mr-2" />
Assign User
</Button>
</DialogTrigger>
<DialogContent data-testid="onboarding-details-assign-user-modal">
<DialogHeader>
<DialogTitle>Assign User to Application</DialogTitle>
<DialogDescription>
Select a user and their role for this application.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Select User</Label>
<Select value={selectedUser} onValueChange={setSelectedUser}>
<SelectTrigger className="mt-2" data-testid="onboarding-details-assign-user-select">
<SelectValue placeholder="Search users..." />
</SelectTrigger>
<SelectContent>
{users.map((u) => (
<SelectItem key={u.id} value={u.id}>
{u.fullName} ({u.email})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>Assignment Role</Label>
<Select value={participantType} onValueChange={setParticipantType}>
<SelectTrigger className="mt-2" data-testid="onboarding-details-assign-role-select">
<SelectValue placeholder="Select role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="owner">Owner</SelectItem>
<SelectItem value="contributor">Contributor</SelectItem>
<SelectItem value="reviewer">Reviewer</SelectItem>
</SelectContent>
</Select>
</div>
<Button
className="w-full bg-amber-600 hover:bg-amber-700 font-bold h-11"
onClick={handleAddParticipant}
disabled={isAssigningParticipant}
data-testid="onboarding-details-assign-user-submit"
>
{isAssigningParticipant ? 'Assigning...' : 'Assign User'}
</Button>
</div>
</DialogContent>
</Dialog>
)}
</CardContent>
</Card>
)}
</div>
);
}