import * as React from "react"; import { CheckCircle2, Circle, Upload } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; // ─── Types ──────────────────────────────────────────────────────────────────── export type DocumentChecklistItemStatus = | "pending" | "uploaded" | "not-required"; export type DocumentChecklistItem = { id: string; /** Document category label — e.g. "Bank Statements", "Income Verification" */ category: string; /** Filename of the uploaded file, if any */ fileName?: string; /** Upload status */ status: DocumentChecklistItemStatus; }; export type ApplicantDocumentChecklistProps = { /** Applicant label shown above the list */ applicantName?: string; /** List of document requirements */ items: DocumentChecklistItem[]; /** Called when the applicant selects a file for an item */ onUpload?: (itemId: string, file: File) => void; /** Called when the applicant removes an uploaded file */ onRemove?: (itemId: string) => void; className?: string; }; // ─── Internal helpers ───────────────────────────────────────────────────────── type StatusIconProps = { status: DocumentChecklistItemStatus }; function StatusIcon({ status }: StatusIconProps) { if (status === "uploaded") { return ; } return ; } // ─── Component ──────────────────────────────────────────────────────────────── /** * ApplicantDocumentChecklist — applicant-facing document upload list. * * Shows a checklist of required document categories. Each row has a status * icon, category label, and an upload button. Once a file is uploaded the row * shows the filename and a Remove action. * * Figma: WealthX-Backoffice---Mobile-App (loan wizard — Supporting Documents tab) */ export function ApplicantDocumentChecklist({ applicantName, items, onUpload, onRemove, className, }: ApplicantDocumentChecklistProps) { const inputRefs = React.useRef>({}); function handleFileChange( itemId: string, e: React.ChangeEvent, ) { const file = e.target.files?.[0]; if (file) onUpload?.(itemId, file); // Reset so re-selecting the same file fires onChange again e.target.value = ""; } const { uploadedCount, totalRequired } = React.useMemo(() => { let uploaded = 0; let required = 0; for (const i of items) { if (i.status !== "not-required") required++; if (i.status === "uploaded") uploaded++; } return { uploadedCount: uploaded, totalRequired: required }; }, [items]); return ( {applicantName && ( {applicantName} )} {uploadedCount} of {totalRequired} documents uploaded 0 ? Math.round((uploadedCount / totalRequired) * 100) : 0 } className="h-1.5" /> {items.map((item) => ( {item.category} {item.fileName && ( {item.fileName} )} {item.status === "uploaded" ? ( onRemove?.(item.id)} > Remove ) : ( <> inputRefs.current[item.id]?.click()} > Upload { inputRefs.current[item.id] = el; }} type="file" accept=".pdf,.jpg,.jpeg,.png" className="sr-only" onChange={(e) => handleFileChange(item.id, e)} /> > )} ))} Accepted formats: PDF, JPG, PNG. Max 10 MB per file. ); }
{applicantName}
{uploadedCount} of {totalRequired} documents uploaded
Accepted formats: PDF, JPG, PNG. Max 10 MB per file.