"use client"; import { useState, useEffect, useCallback, useMemo } from "react"; import type { ReactNode } from "react"; import type { Prisma } from "@prisma/client"; import { Download, Trash2, Eye, MoreVertical, Loader2, FileText, FileSpreadsheet, MessageSquare, AlertTriangle, X, Search, Info, Network, Shield, Sparkles, FileIcon, ChevronRight, Calendar, HardDrive, Tag, FileType, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogClose, } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { deleteContract, getContracts, deleteAllContractsAction, } from "@/features/contracts/api/contract.action"; import { toast } from "sonner"; import { ContractChatModal } from "@/features/contracts/components/modals/contract-chat-modal"; import { ContractProofModal } from "@/features/contracts/components/modals/contract-proof-modal"; import { stripMarkdown, exportToCSV, exportToPDF, } from "@/features/contracts/utils/export.utils"; import { motion, AnimatePresence } from "motion/react"; interface Contract { id: string; fileName: string; fileSize: number; mimeType: string; status: string; createdAt: string; // ISO string fileUrl: string; title?: string | null; type?: string | null; provider?: string | null; policyNumber?: string | null; startDate?: string | null; endDate?: string | null; premium?: number | null; summary?: string | null; keyPoints?: Prisma.JsonValue | null; extractedText?: string | null; ragChunkCount?: number; isRagged?: boolean; } interface ExplainabilityEntry { field: string; why: string; sourceSnippet: string; sourceHints?: { page?: string | null; section?: string | null; confidence?: number | null; }; } interface ContractKeyPoints { guarantees?: string[]; exclusions?: string[]; franchise?: string | null; importantDates?: string[]; explainability?: ExplainabilityEntry[]; aiMeta?: { language?: string | null; premiumCurrency?: string | null; }; } const isContractKeyPoints = (value: unknown): value is ContractKeyPoints => { return typeof value === "object" && value !== null && !Array.isArray(value); }; export function ContractsList({ refreshTrigger }: { refreshTrigger?: number }) { const emitNotificationRefresh = () => { window.dispatchEvent(new Event("notifications:refresh")); const channel = new BroadcastChannel("notifications-channel"); channel.postMessage({ type: "notifications:refresh" }); channel.close(); }; const [contracts, setContracts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [deletingId, setDeletingId] = useState(null); const [isDeletingAll, setIsDeletingAll] = useState(false); const [detailsOpen, setDetailsOpen] = useState(false); const [selectedContract, setSelectedContract] = useState( null, ); const [askOpen, setAskOpen] = useState(false); const [chatContract, setChatContract] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [contractToDelete, setContractToDelete] = useState( null, ); const [deleteAllDialogOpen, setDeleteAllDialogOpen] = useState(false); const [invalidContractDialogOpen, setInvalidContractDialogOpen] = useState(false); const [invalidContractReason, setInvalidContractReason] = useState(""); const [invalidContractFileName, setInvalidContractFileName] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(""); const [proofModalOpen, setProofModalOpen] = useState(false); const [proofData, setProofData] = useState<{ fieldKey: string; field: string; sourceSnippet: string; confidence: number | null; page: string | null; section: string | null; lineNumber: number | null; contextStartLine: number | null; context: string[]; resolutionMode: "exact" | "fuzzy" | "fallback"; } | null>(null); const ENTITY_REGEX = /(\*\*[^*]+\*\*)|([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})|(\+?\d[\d\s().-]{7,}\d)|(\b\d{4}-\d{2}-\d{2}\b)|(\b\d{1,2}[\/.-]\d{1,2}[\/.-]\d{2,4}\b)|((?:€|\$|£)\s?\d[\d\s.,]*\d|\b\d[\d\s.,]*\d\s?(?:EUR|USD|TND|MAD|DZD|GBP)\b)|(\b\d+(?:[.,]\d+)?\s?%\b)|(\b\d{2,}(?:[.,]\d+)?\b)/g; const renderHighlightedText = ( text: string, keyPrefix: string, ): ReactNode[] => { const nodes: ReactNode[] = []; let lastIndex = 0; let match: RegExpExecArray | null; let index = 0; while ((match = ENTITY_REGEX.exec(text)) !== null) { const [token] = match; const start = match.index; if (start > lastIndex) { nodes.push(text.slice(lastIndex, start)); } const tokenKey = `${keyPrefix}-token-${index}`; index += 1; if (token.startsWith("**") && token.endsWith("**")) { nodes.push( {token.slice(2, -2)} , ); } else if (match[2]) { nodes.push( {token} , ); } else if (match[3]) { nodes.push( {token} , ); } else if (match[4] || match[5]) { nodes.push( {token} , ); } else if (match[6] || match[7]) { nodes.push( {token} , ); } else { nodes.push( {token} , ); } lastIndex = start + token.length; } if (lastIndex < text.length) { nodes.push(text.slice(lastIndex)); } return nodes; }; const renderRichParagraphs = ( text: string, keyPrefix: string, ): ReactNode[] => { const cleaned = text.replace(/\u2022/g, "•").trim(); if (!cleaned) return []; const lines = cleaned .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line.length > 0); return lines.map((line, idx) => { const isList = /^[-*•]\s+/.test(line); const content = line.replace(/^[-*•]\s+/, ""); return (
{isList && ( )}

{renderHighlightedText(content, `${keyPrefix}-${idx}`)}

); }); }; // Helper: Find source snippet in extracted text and get line number + context const findSourceSnippetInText = ( sourceSnippet: string, extractedText: string | null | undefined, fieldKey?: string, fallbackNeedles?: string[], ): { lineNumber: number | null; contextStartLine: number | null; context: string[]; resolutionMode: "exact" | "fuzzy" | "fallback"; } => { if (!extractedText || !sourceSnippet) { return { lineNumber: null, contextStartLine: null, context: [], resolutionMode: "fallback", }; } const normalizeForMatch = (value: string) => value .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim() .replace(/\s+/g, " "); const buildDateNeedles = (isoDate: string): string[] => { if (!/^\d{4}-\d{2}-\d{2}$/.test(isoDate)) return [isoDate]; const [year, month, day] = isoDate.split("-"); const monthIndex = Number(month) - 1; const frMonths = [ "janvier", "fevrier", "mars", "avril", "mai", "juin", "juillet", "aout", "septembre", "octobre", "novembre", "decembre", ]; const enMonths = [ "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december", ]; const numericDay = String(Number(day)); const numericMonth = String(Number(month)); return [ isoDate, `${day}/${month}/${year}`, `${day}-${month}-${year}`, `${numericDay}/${numericMonth}/${year}`, `${numericDay}-${numericMonth}-${year}`, `${numericDay} ${frMonths[monthIndex]} ${year}`, `${numericDay} ${enMonths[monthIndex]} ${year}`, ]; }; const tokenize = (value: string) => normalizeForMatch(value) .split(" ") .filter((token) => token.length >= 3 || /^\d+$/.test(token)); const lines = extractedText.split(/\r?\n/); const normalizedLines = lines.map((line) => normalizeForMatch(line)); const snippetNormalized = normalizeForMatch(sourceSnippet); const snippetTokens = tokenize(sourceSnippet); const resolvedFallbackNeedles = [...(fallbackNeedles ?? [])]; if ( (fieldKey === "startDate" || fieldKey === "endDate") && fallbackNeedles?.[0] ) { resolvedFallbackNeedles.push(...buildDateNeedles(fallbackNeedles[0])); } const needleTokens = resolvedFallbackNeedles.flatMap((needle) => tokenize(needle), ); const snippetNeedle = snippetNormalized.slice(0, 90); const isDateField = fieldKey === "startDate" || fieldKey === "endDate"; // Stage 1: strict exact matching to avoid false positives. const exactIndex = normalizedLines.findIndex((line) => snippetNeedle.length >= 25 ? line.includes(snippetNeedle) : false, ); if (exactIndex >= 0) { const contextStart = Math.max(0, exactIndex - 3); const contextEnd = Math.min(lines.length, exactIndex + 4); return { lineNumber: exactIndex + 1, contextStartLine: contextStart + 1, context: lines.slice(contextStart, contextEnd), resolutionMode: "exact", }; } // Stage 2: deterministic date/value lookup for date fields. if (isDateField && resolvedFallbackNeedles.length > 0) { const normalizedNeedles = resolvedFallbackNeedles .map((value) => normalizeForMatch(value)) .filter((value) => value.length >= 6); const dateCandidates = normalizedLines .map((line, idx) => { const needleHits = normalizedNeedles.filter((needle) => line.includes(needle), ).length; if (needleHits === 0) return null; const genericTemporalBoost = /(effet|debut|start|commence|expiration|expire|echeance|fin|periode|garantie|validite|prend effet)/.test( line, ) ? 15 : 0; const fieldSpecificBoost = fieldKey === "endDate" ? /(expiration|expire|echeance|fin|expire le|date d expiration)/.test( line, ) ? 28 : 0 : /(prend effet|debut|commence|start date|date d effet)/.test( line, ) ? 24 : 0; const score = needleHits * 40 + genericTemporalBoost + fieldSpecificBoost; return { idx, score }; }) .filter((item): item is { idx: number; score: number } => item !== null) .sort((a, b) => b.score - a.score); const bestDateCandidate = dateCandidates[0]; if (bestDateCandidate) { const contextStart = Math.max(0, bestDateCandidate.idx - 3); const contextEnd = Math.min(lines.length, bestDateCandidate.idx + 4); return { lineNumber: bestDateCandidate.idx + 1, contextStartLine: contextStart + 1, context: lines.slice(contextStart, contextEnd), resolutionMode: "exact", }; } } const windows: { start: number; end: number; text: string }[] = []; for (let start = 0; start < normalizedLines.length; start++) { for (let length = 1; length <= 4; length++) { const end = start + length; if (end > normalizedLines.length) break; windows.push({ start, end, text: normalizedLines.slice(start, end).join(" "), }); } } let bestMatch: { start: number; score: number } | null = null; const hasDateHint = isDateField; const hasPremiumHint = fieldKey === "premium"; for (const window of windows) { const windowTokens = window.text.split(" ").filter(Boolean); const tokenSet = new Set(windowTokens); let score = 0; if ( snippetNormalized && window.text.includes(snippetNormalized.slice(0, 60)) ) { score += 45; } if (snippetTokens.length > 0) { const overlap = snippetTokens.filter((token) => tokenSet.has(token), ).length; score += (overlap / snippetTokens.length) * 40; } if (needleTokens.length > 0) { const overlap = needleTokens.filter((token) => tokenSet.has(token), ).length; score += (overlap / needleTokens.length) * 35; } if (hasDateHint) { const containsYear = /\b(19|20)\d{2}\b/.test(window.text); const temporalKeywords = /(effet|debut|start|commence|expiration|expire|echeance|fin|periode|garantie|validite|prend effet)/.test( window.text, ); if (containsYear) score += 12; if (temporalKeywords) score += 20; if ( fieldKey === "endDate" && /(expiration|expire|echeance|fin|date d effet|expire le)/.test( window.text, ) ) { score += 22; } } if (hasPremiumHint) { if ( /(prime|cotisation|premium|montant|cout|cost|total)/.test(window.text) ) { score += 18; } if (/(eur|usd|tnd|mad|dzd|gbp|€|\$|£)/.test(window.text)) { score += 12; } } if (!bestMatch || score > bestMatch.score) { bestMatch = { start: window.start, score }; } } if (bestMatch && bestMatch.score >= 32) { const contextStart = Math.max(0, bestMatch.start - 3); const contextEnd = Math.min(lines.length, bestMatch.start + 4); const context = lines.slice(contextStart, contextEnd); return { lineNumber: bestMatch.start + 1, contextStartLine: contextStart + 1, context, resolutionMode: "fuzzy", }; } return { lineNumber: null, contextStartLine: null, context: [], resolutionMode: "fallback", }; }; // Handler: Open proof modal when clicking info icon const handleProofClick = ( fieldKey: string, entry: ExplainabilityEntry, fieldValueFallback?: string, sectionHint?: string | null, ) => { const proofHints = [fieldValueFallback, sectionHint] .filter((hint): hint is string => Boolean(hint && hint.trim())) .map((hint) => hint.trim()); const { lineNumber, contextStartLine, context, resolutionMode } = findSourceSnippetInText( entry.sourceSnippet, selectedContract?.extractedText, fieldKey, proofHints, ); setProofData({ fieldKey, field: entry.field, sourceSnippet: entry.sourceSnippet, confidence: entry.sourceHints?.confidence ?? null, page: entry.sourceHints?.page ?? null, section: entry.sourceHints?.section ?? null, lineNumber, contextStartLine, context, resolutionMode, }); setProofModalOpen(true); }; const getExplainabilityItems = ( contract: Contract | null, ): ExplainabilityEntry[] => { const raw = isContractKeyPoints(contract?.keyPoints) ? contract.keyPoints.explainability : undefined; if (!Array.isArray(raw)) return []; return raw .map((item) => ({ field: String(item?.field ?? "").trim(), why: String(item?.why ?? "").trim(), sourceSnippet: String(item?.sourceSnippet ?? "").trim(), sourceHints: { page: item?.sourceHints?.page === null || item?.sourceHints?.page === undefined ? null : String(item?.sourceHints?.page), section: item?.sourceHints?.section === null || item?.sourceHints?.section === undefined ? null : String(item?.sourceHints?.section), confidence: typeof item?.sourceHints?.confidence === "number" ? item.sourceHints.confidence : null, }, })) .filter((item: ExplainabilityEntry) => { return item.field && item.why && item.sourceSnippet; }) .slice(0, 24); }; const explainabilityItems = useMemo( () => getExplainabilityItems(selectedContract), [selectedContract], ); const getProofForField = useCallback( (fieldKey: string): ExplainabilityEntry | null => { const normalize = (value: string) => value.toLowerCase().replace(/[^a-z0-9]/g, ""); const aliases: Record = { title: ["title", "contracttitle"], provider: ["provider", "institution", "insurer", "company"], policyNumber: [ "policynumber", "policyno", "contractnumber", "reference", ], startDate: ["startdate", "effectivedate", "start"], endDate: ["enddate", "expirationdate", "expirydate", "maturitydate"], premium: ["premium", "amount", "totalcost", "fee", "price"], }; const normalizedTargets = new Set( (aliases[fieldKey] ?? [fieldKey]).map((item) => normalize(item)), ); const ranked = explainabilityItems .map((entry) => { const normalizedField = normalize(entry.field); const isExact = normalizedTargets.has(normalizedField); const isPartial = Array.from(normalizedTargets).some( (target) => normalizedField.includes(target) || target.includes(normalizedField), ); const confidence = entry.sourceHints?.confidence ?? 0; const score = (isExact ? 100 : 0) + (isPartial ? 40 : 0) + confidence; return { entry, score }; }) .filter((item) => item.score > 0) .sort((a, b) => b.score - a.score); const match = ranked[0]?.entry; return match ?? null; }, [explainabilityItems], ); const getFieldValueForProof = useCallback( (fieldKey: string): string => { if (!selectedContract) return ""; switch (fieldKey) { case "title": return selectedContract.title ?? ""; case "provider": return selectedContract.provider ?? ""; case "policyNumber": return selectedContract.policyNumber ?? ""; case "startDate": return selectedContract.startDate ?? ""; case "endDate": return selectedContract.endDate ?? ""; case "premium": return selectedContract.premium ? selectedContract.premium.toString() : ""; default: return ""; } }, [selectedContract], ); const resolvePremiumCurrency = useCallback((contract: Contract | null) => { if (!contract) return null; const fromMeta = String( (isContractKeyPoints(contract.keyPoints) ? contract.keyPoints.aiMeta?.premiumCurrency : null) ?? "", ).trim(); if (fromMeta) return fromMeta; const explainability = getExplainabilityItems(contract); const premiumEvidence = explainability.find((item) => { const normalized = item.field.toLowerCase(); return normalized.includes("premium") || normalized.includes("prime"); }); const currencyRegex = /(EUR|USD|TND|MAD|DZD|GBP|CHF|CAD|AUD|€|\$|£)/i; const snippetCurrency = premiumEvidence?.sourceSnippet.match(currencyRegex)?.[0]; if (snippetCurrency) return snippetCurrency.toUpperCase(); const textCurrency = contract.extractedText?.match(currencyRegex)?.[0]; if (textCurrency) return textCurrency.toUpperCase(); return null; }, []); const formatPremiumWithSourceCurrency = useCallback( (contract: Contract | null) => { if ( !contract || contract.premium === null || contract.premium === undefined ) { return "N/A"; } const currency = resolvePremiumCurrency(contract) ?? "EUR"; const numeric = new Intl.NumberFormat("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(contract.premium); if (currency === "€" || currency === "$" || currency === "£") { return `${currency}${numeric}`; } return `${numeric} ${currency}`; }, [resolvePremiumCurrency], ); const handleOpenFieldProof = (fieldKey: string, label: string) => { const fieldValueFallback = getFieldValueForProof(fieldKey); const proof = getProofForField(fieldKey); if (!proof) { if (fieldValueFallback) { handleProofClick( fieldKey, { field: label, why: "Resolved using extracted field value fallback when explicit explainability evidence is missing.", sourceSnippet: fieldValueFallback, sourceHints: { confidence: null, page: null, section: null, }, }, fieldValueFallback, null, ); return; } toast.error(`No proof snippet is available for ${label}`); return; } handleProofClick( fieldKey, proof, fieldValueFallback, proof.sourceHints?.section ?? null, ); }; const loadContracts = useCallback( async (options?: { silent?: boolean; search?: string }) => { const isSilentRefresh = options?.silent ?? false; if (!isSilentRefresh) { setIsLoading(true); } try { const result = await getContracts({ search: options?.search?.trim() || undefined, }); if (result.success && Array.isArray(result.contracts)) { setContracts(result.contracts); } } catch (error) { console.error("Failed to load contracts:", error); toast.error("Failed to load contracts"); } finally { if (!isSilentRefresh) { setIsLoading(false); } } }, [], ); useEffect(() => { const timeoutId = window.setTimeout(() => { setDebouncedSearchQuery(searchQuery.trim()); }, 300); return () => window.clearTimeout(timeoutId); }, [searchQuery]); useEffect(() => { void loadContracts({ search: debouncedSearchQuery }); }, [loadContracts, refreshTrigger, debouncedSearchQuery]); useEffect(() => { const hasPendingContracts = contracts.some( (contract) => contract.status === "PROCESSING" || contract.status === "UPLOADED", ); if (!hasPendingContracts) { return; } const intervalId = window.setInterval(() => { void loadContracts({ silent: true, search: debouncedSearchQuery, }); }, 7000); return () => window.clearInterval(intervalId); }, [contracts, loadContracts, debouncedSearchQuery]); const handleDelete = async (id: string) => { setDeletingId(id); try { const result = await deleteContract(id); if (result.success) { setContracts(contracts.filter((c) => c.id !== id)); toast.success("Contract deleted successfully"); emitNotificationRefresh(); } else { toast.error(result.error || "Failed to delete contract"); } } catch (error) { toast.error( error instanceof Error ? error.message : "Unknown error occurred", ); } finally { setDeletingId(null); } }; const requestDeleteContract = (contract: Contract) => { setContractToDelete(contract); setDeleteDialogOpen(true); }; const confirmDeleteContract = async () => { if (!contractToDelete) return; await handleDelete(contractToDelete.id); setDeleteDialogOpen(false); setContractToDelete(null); }; const handleDeleteAll = async () => { setIsDeletingAll(true); try { const result = await deleteAllContractsAction(); if (result.success) { setContracts([]); toast.success( `Deleted ${result.deletedCount ?? 0} contract${(result.deletedCount ?? 0) === 1 ? "" : "s"}.`, ); emitNotificationRefresh(); } else { toast.error(result.error || "Failed to delete all contracts"); } } catch (error) { toast.error( error instanceof Error ? error.message : "Unknown error occurred", ); } finally { setIsDeletingAll(false); setDeleteAllDialogOpen(false); } }; const handleOpenDetails = (contract: Contract) => { setSelectedContract(contract); setDetailsOpen(true); }; const handleOpenAsk = (contract: Contract) => { setChatContract(contract); setAskOpen(true); }; const formatDate = (date: Date | string) => { const dateObj = typeof date === "string" ? new Date(date) : date; return dateObj.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); }; const formatFileSize = (bytes: number) => { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]; }; const getFileIcon = (mimeType: string) => { if (mimeType.startsWith("image/")) { return ; } if (mimeType === "application/pdf") { return ; } return ; }; const getFileIconBg = (mimeType: string) => { if (mimeType.startsWith("image/")) { return "bg-violet-500/10 border-violet-500/20"; } if (mimeType === "application/pdf") { return "bg-red-500/10 border-red-500/20"; } return "bg-blue-500/10 border-blue-500/20"; }; const getStatusConfig = (status: string) => { switch (status) { case "COMPLETED": return { dot: "bg-emerald-500", bg: "bg-emerald-500/10 border-emerald-500/20 text-emerald-700 dark:text-emerald-300", label: "Completed", }; case "PROCESSING": return { dot: "bg-blue-500", bg: "bg-blue-500/10 border-blue-500/20 text-blue-700 dark:text-blue-300", label: "Processing", }; case "UPLOADED": return { dot: "bg-amber-500", bg: "bg-amber-500/10 border-amber-500/20 text-amber-700 dark:text-amber-300", label: "Uploaded", }; case "FAILED": return { dot: "bg-red-500", bg: "bg-red-500/10 border-red-500/20 text-red-700 dark:text-red-300", label: "Failed", }; default: return { dot: "bg-gray-500", bg: "bg-gray-500/10 border-gray-500/20 text-gray-700 dark:text-gray-300", label: status, }; } }; if (isLoading) { return (
{[1, 2, 3].map((i) => (
))}
); } if (contracts.length === 0 && !debouncedSearchQuery) { return null; } return ( <> {invalidContractReason && (

Invalid contract upload detected

{invalidContractFileName ? `${invalidContractFileName}: ` : ""} {invalidContractReason}

)} {/* Toolbar */}
setSearchQuery(event.target.value)} placeholder="Search by contract title or provider..." className="pl-10 pr-4 h-11 rounded-xl border-border/60 bg-background/60 backdrop-blur-xl focus:bg-background/80 focus:ring-2 focus:ring-primary/20 transition-all" />
{debouncedSearchQuery && (

{contracts.length} result{contracts.length !== 1 ? "s" : ""} for "{debouncedSearchQuery}"

)}
{/* Contract Cards */}
{contracts.map((contract, idx) => { const status = getStatusConfig(contract.status); return (
{getFileIcon(contract.mimeType)}

{contract.fileName}

{status.label} {contract.isRagged && ( RAG {contract.ragChunkCount ?? 0} )}
{formatFileSize(contract.fileSize)} {formatDate(contract.createdAt)}
handleOpenAsk(contract)} className="cursor-pointer rounded-lg focus:bg-primary/10" > Ask about this file handleOpenDetails(contract)} className="cursor-pointer rounded-lg focus:bg-primary/10" > Details exportToPDF(contract as any)} className="cursor-pointer rounded-lg focus:bg-primary/10" > Export Analysis (PDF) exportToCSV(contract as any)} className="cursor-pointer rounded-lg focus:bg-primary/10" > Export Analysis (CSV) requestDeleteContract(contract)} disabled={deletingId === contract.id} className="text-destructive focus:text-destructive cursor-pointer rounded-lg focus:bg-destructive/10" > {deletingId === contract.id ? ( <> Deleting... ) : ( <> Delete )}
); })} {contracts.length === 0 && debouncedSearchQuery && (

No contracts found

Try different keywords from the title or provider name.

)}
{/* Details Modal */}
Contract Details
{selectedContract && (
{/* Document Profile */}

Document Profile

{selectedContract.fileName}

{selectedContract.status}
{[ { label: "File Size", value: formatFileSize(selectedContract.fileSize), icon: , }, { label: "Mime Type", value: selectedContract.mimeType, icon: , }, { label: "Uploaded", value: formatDate(selectedContract.createdAt), icon: , }, { label: "Category", value: selectedContract.type || "Pending analysis", icon: , }, ].map((item) => (
{item.icon}

{item.label}

{item.value}

))}
{/* AI Analysis Results */} {selectedContract.status === "COMPLETED" && ( <>

Extracted Contract Information

{[ { key: "title", label: "Title", value: stripMarkdown(selectedContract.title) || "N/A", }, { key: "provider", label: "Provider", value: stripMarkdown(selectedContract.provider) || "N/A", }, { key: "policyNumber", label: "Policy Number", value: stripMarkdown(selectedContract.policyNumber) || "N/A", }, { key: "startDate", label: "Start Date", value: selectedContract.startDate ? formatDate(selectedContract.startDate) : "N/A", }, { key: "endDate", label: "End Date", value: selectedContract.endDate ? formatDate(selectedContract.endDate) : "N/A", }, { key: "premium", label: "Premium", value: formatPremiumWithSourceCurrency(selectedContract), }, ].map((field) => (

{field.label}

{field.value}

))}
{selectedContract.summary && (

Summary

{renderRichParagraphs( selectedContract.summary, `summary-${selectedContract.id}`, )}
)} {selectedContract.keyPoints && (

Key Points

{isContractKeyPoints(selectedContract.keyPoints) && selectedContract.keyPoints.guarantees && Array.isArray( selectedContract.keyPoints.guarantees, ) && (

Guarantees

{( selectedContract.keyPoints.guarantees ?? [] ).map((guarantee, idx: number) => (
{renderRichParagraphs( guarantee, `guarantee-${selectedContract.id}-${idx}`, )}
))}
)} {isContractKeyPoints(selectedContract.keyPoints) && selectedContract.keyPoints.exclusions && Array.isArray( selectedContract.keyPoints.exclusions, ) && (

Exclusions

{( selectedContract.keyPoints.exclusions ?? [] ).map((exclusion, idx: number) => (
{renderRichParagraphs( exclusion, `exclusion-${selectedContract.id}-${idx}`, )}
))}
)} {isContractKeyPoints(selectedContract.keyPoints) && selectedContract.keyPoints.franchise && (

Deductible

{renderRichParagraphs( String(selectedContract.keyPoints.franchise), `franchise-${selectedContract.id}`, )}
)}
)} )} {selectedContract.status === "PROCESSING" && (

AI analysis is in progress

Extracting entities, clauses, and generating insights...

)} {selectedContract.status === "UPLOADED" && (

Contract uploaded successfully

AI analysis will begin automatically momentarily

)} {selectedContract.status === "FAILED" && (

Analysis failed

{selectedContract.summary || "The uploaded file could not be processed as a valid contract. Please upload a clearer contract document and try again."}

)}
)}
Delete this contract? This action permanently removes the selected contract and its associated file. {contractToDelete ? ( {contractToDelete.fileName} ) : ( "" )} { setContractToDelete(null); }} className="rounded-xl" > Cancel void confirmDeleteContract()} className="bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-xl" > {contractToDelete && deletingId === contractToDelete.id ? "Deleting..." : "Delete"} Delete all contracts? This action permanently removes all contracts and related files for your account. This cannot be undone. Cancel void handleDeleteAll()} className="bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-xl" > {isDeletingAll ? "Deleting..." : "Delete All"} Invalid Contract File

The AI could not validate this file as a real contract.

Reason

{invalidContractReason || "This uploaded file does not appear to be a valid contract."}

{invalidContractFileName && (

File:{" "} {invalidContractFileName}

)}

Please upload a contract or policy document with readable legal terms and agreement details.

); }