"use client"; import { useState, useEffect, useCallback } from "react"; import { Download, Trash2, Eye, MoreVertical, Loader2, Sparkles, FileText, MessageSquare, Send, Scale, Briefcase, User, Bot, AlertTriangle, X, Search, } 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, analyzeContractAction, askContractQuestionAction, } from "@/lib/actions/contract.action"; import { toast } from "sonner"; import { Textarea } from "@/components/ui/textarea"; 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?: Record | null; } interface ChatMessage { role: "user" | "assistant"; content: string; } 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 [analyzingId, setAnalyzingId] = useState(null); const [detailsOpen, setDetailsOpen] = useState(false); const [selectedContract, setSelectedContract] = useState( null, ); const [isAnalyzing, setIsAnalyzing] = useState(false); const [askOpen, setAskOpen] = useState(false); const [chatContract, setChatContract] = useState(null); const [question, setQuestion] = useState(""); const [isAsking, setIsAsking] = useState(false); const [messages, setMessages] = useState([]); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [contractToDelete, setContractToDelete] = useState( null, ); const [invalidContractDialogOpen, setInvalidContractDialogOpen] = useState(false); const [invalidContractReason, setInvalidContractReason] = useState(""); const [invalidContractFileName, setInvalidContractFileName] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(""); const quickQuestions = [ "What are the main obligations and deadlines?", "What are the non-compliance risks under general EU/US principles?", "What are the most important exclusions and liabilities?", ]; 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 handleAnalyze = async (id: string) => { const selected = contracts.find((contract) => contract.id === id); setAnalyzingId(id); setIsAnalyzing(true); try { const result = await analyzeContractAction(id); if (result.success) { // Reload contracts to get all AI analysis data await loadContracts(); toast.success("Contract analyzed successfully!"); emitNotificationRefresh(); } else { const errorCode = (result as { errorCode?: string }).errorCode; if (errorCode === "INVALID_CONTRACT") { const reason = result.error || "This uploaded file is not recognized as a valid contract."; setInvalidContractReason(reason); setInvalidContractFileName(selected?.fileName || "Unknown file"); setInvalidContractDialogOpen(true); toast.error("Invalid contract file detected"); } else { toast.error(result.error || "Failed to analyze contract"); } } } catch (error) { toast.error( error instanceof Error ? error.message : "Unknown error occurred", ); } finally { setAnalyzingId(null); setIsAnalyzing(false); } }; const handleOpenDetails = (contract: Contract) => { setSelectedContract(contract); setDetailsOpen(true); }; const handleOpenAsk = (contract: Contract) => { setChatContract(contract); setMessages([ { role: "assistant", content: "Ask me anything about this contract. I will answer based on the file analysis.", }, ]); setQuestion(""); setAskOpen(true); }; const handleAskQuestion = async () => { if (!chatContract) return; const trimmedQuestion = question.trim(); if (!trimmedQuestion) return; setMessages((prev) => [ ...prev, { role: "user", content: trimmedQuestion }, ]); setQuestion(""); setIsAsking(true); try { const result = await askContractQuestionAction( chatContract.id, trimmedQuestion, ); if (result.success && result.answer) { setMessages((prev) => [ ...prev, { role: "assistant", content: result.answer as string }, ]); } else { const errorMessage = result.error || "Failed to get AI response"; setMessages((prev) => [ ...prev, { role: "assistant", content: `Error: ${errorMessage}` }, ]); } } catch (error) { const fallbackMessage = error instanceof Error ? error.message : "Unknown error occurred"; setMessages((prev) => [ ...prev, { role: "assistant", content: `Error: ${fallbackMessage}` }, ]); } finally { setIsAsking(false); } }; 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 getStatusColor = (status: string) => { switch (status) { case "COMPLETED": return "text-green-500 dark:text-green-400 bg-green-50 dark:bg-green-950/30"; case "PROCESSING": return "text-blue-500 dark:text-blue-400 bg-blue-50 dark:bg-blue-950/30"; case "UPLOADED": return "text-amber-500 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/30"; case "FAILED": return "text-red-500 dark:text-red-400 bg-red-50 dark:bg-red-950/30"; default: return "text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-950/30"; } }; if (isLoading) { return (
Loading contracts...
); } if (contracts.length === 0 && !debouncedSearchQuery) { return null; } return ( <> {invalidContractReason && (

Invalid contract upload detected

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

)}
setSearchQuery(event.target.value)} placeholder="Search by contract title or provider..." className="pl-9" />
{debouncedSearchQuery && (

Showing results for: "{debouncedSearchQuery}"

)}
{contracts.map((contract) => (
{getFileIcon(contract.mimeType)}

{contract.fileName}

{contract.status}
{formatFileSize(contract.fileSize)} {formatDate(contract.createdAt)}
handleOpenAsk(contract)} className="cursor-pointer" > Ask about this file handleOpenDetails(contract)} className="cursor-pointer" > Details requestDeleteContract(contract)} disabled={deletingId === contract.id} className="text-destructive focus:text-destructive cursor-pointer" > {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

{selectedContract.fileName}

{selectedContract.status}

File Size

{formatFileSize(selectedContract.fileSize)}

Mime Type

{selectedContract.mimeType}

Uploaded

{formatDate(selectedContract.createdAt)}

Category

{selectedContract.type || "Pending analysis"}

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

Extracted Contract Information

Title

{selectedContract.title || "N/A"}

Provider

{selectedContract.provider || "N/A"}

Policy Number

{selectedContract.policyNumber || "N/A"}

Start Date

{selectedContract.startDate ? formatDate(selectedContract.startDate) : "N/A"}

End Date

{selectedContract.endDate ? formatDate(selectedContract.endDate) : "N/A"}

Premium

{selectedContract.premium ? `€${selectedContract.premium.toFixed(2)}` : "N/A"}

{selectedContract.summary && (

Summary

{selectedContract.summary}

)} {selectedContract.keyPoints && (

Key Points

{(selectedContract.keyPoints as any)?.guarantees && Array.isArray( (selectedContract.keyPoints as any).guarantees, ) && (

Guarantees:

    {( (selectedContract.keyPoints as any) .guarantees as string[] ).map((guarantee: string, idx: number) => (
  • {guarantee}
  • ))}
)} {(selectedContract.keyPoints as any)?.exclusions && Array.isArray( (selectedContract.keyPoints as any).exclusions, ) && (

Exclusions:

    {( (selectedContract.keyPoints as any) .exclusions as string[] ).map((exclusion: string, idx: number) => (
  • {exclusion}
  • ))}
)} {(selectedContract.keyPoints as any)?.franchise && (

Deductible:

{String( (selectedContract.keyPoints as any).franchise, )}

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

AI analysis is in progress...

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

Click the Sparkles button to analyze this contract

)} {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."}

)}
)}
Ask About This File {chatContract && (

Contract Intelligence Assistant

{chatContract.fileName}

Business Legal Context

Quick prompts

{quickQuestions.map((quickQuestion) => ( ))}
{messages.map((message, index) => (
{message.role === "assistant" && ( )}
{message.content}
{message.role === "user" && ( )}
))} {isAsking && (
Preparing a professional legal-business answer...
)}