"use client"; import React, { useState, useMemo } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { ChevronDown, ChevronRight, Download, FileIcon, XCircle, AlertCircle, } from "lucide-react"; import { cancelVendorFile, downloadVendorFile, } from "@/lib/swp/vendor-actions"; import type { SwpFileApiResponse } from "@/lib/swp/api-client"; import type { InboxDocumentItem } from "./swp-inbox-table"; import { toast } from "sonner"; interface SwpInboxDocumentDetailDialogProps { open: boolean; onOpenChange: (open: boolean) => void; document: InboxDocumentItem | null; projNo: string; vendorCode: string; userId: string; } // Group type by revision interface RevisionGroup { revNo: string; stage: string; activities: ActivityGroup[]; totalFiles: number; } // Group type by Activity (activity can be null) interface ActivityGroup { actvNo: string | null; files: SwpFileApiResponse[]; } export function SwpInboxDocumentDetailDialog({ open, onOpenChange, document, projNo, }: SwpInboxDocumentDetailDialogProps) { const [expandedRevisions, setExpandedRevisions] = useState>(new Set()); const [expandedActivities, setExpandedActivities] = useState>(new Set()); const [isAllExpanded, setIsAllExpanded] = useState(true); // Group files into Revision > Activity structure const revisionGroups = useMemo(() => { if (!document) return []; const revMap = new Map(); document.files.forEach((file) => { const revKey = `${file.REV_NO}|${file.STAGE}`; if (!revMap.has(revKey)) { revMap.set(revKey, []); } revMap.get(revKey)!.push(file); }); const result: RevisionGroup[] = []; revMap.forEach((revFiles, revKey) => { const [revNo, stage] = revKey.split("|"); // Group by Activity (nullable) const actMap = new Map(); revFiles.forEach((file) => { const actvNo = file.ACTV_NO || null; if (!actMap.has(actvNo)) { actMap.set(actvNo, []); } actMap.get(actvNo)!.push(file); }); const activities: ActivityGroup[] = []; actMap.forEach((files, actvNo) => { activities.push({ actvNo, files }); }); // Sort: No Activity first, then with Activity activities.sort((a, b) => { if (a.actvNo === null && b.actvNo !== null) return -1; if (a.actvNo !== null && b.actvNo === null) return 1; if (a.actvNo === null && b.actvNo === null) return 0; return (a.actvNo || "").localeCompare(b.actvNo || ""); }); result.push({ revNo, stage, activities, totalFiles: revFiles.length, }); }); // Sort by revision number (newest first) return result.sort((a, b) => b.revNo.localeCompare(a.revNo)); }, [document]); // Expand all when dialog opens React.useEffect(() => { if (open && revisionGroups.length > 0) { const allRevKeys = new Set(); const allActKeys = new Set(); revisionGroups.forEach((revision) => { const revKey = revision.revNo; allRevKeys.add(revKey); revision.activities.forEach((activity) => { const actKey = `${revKey}|${activity.actvNo || "NO_ACTIVITY"}`; allActKeys.add(actKey); }); }); setExpandedRevisions(allRevKeys); setExpandedActivities(allActKeys); setIsAllExpanded(true); } }, [open, revisionGroups]); const toggleRevision = (revKey: string) => { setExpandedRevisions((prev) => { const newSet = new Set(prev); if (newSet.has(revKey)) { newSet.delete(revKey); } else { newSet.add(revKey); } return newSet; }); }; const toggleActivity = (actKey: string) => { setExpandedActivities((prev) => { const newSet = new Set(prev); if (newSet.has(actKey)) { newSet.delete(actKey); } else { newSet.add(actKey); } return newSet; }); }; // Toggle all const handleToggleAll = () => { if (isAllExpanded) { // Collapse all setExpandedRevisions(new Set()); setExpandedActivities(new Set()); setIsAllExpanded(false); } else { // Expand all const allRevKeys = new Set(); const allActKeys = new Set(); revisionGroups.forEach((revision) => { const revKey = revision.revNo; allRevKeys.add(revKey); revision.activities.forEach((activity) => { const actKey = `${revKey}|${activity.actvNo || "NO_ACTIVITY"}`; allActKeys.add(actKey); }); }); setExpandedRevisions(allRevKeys); setExpandedActivities(allActKeys); setIsAllExpanded(true); } }; const handleCancelFile = async (boxSeq: string, actvSeq: string, fileName: string) => { try { await cancelVendorFile(boxSeq, actvSeq); toast.success(`File cancelled: ${fileName}`); // Close dialog and trigger parent refresh onOpenChange(false); } catch (error) { console.error("Failed to cancel file:", error); toast.error("Failed to cancel file"); } }; const handleDownloadFile = async (fileName: string, ownDocNo: string) => { try { toast.info("Downloading file..."); const result = await downloadVendorFile(projNo, ownDocNo, fileName); if (!result.success || !result.data) { toast.error(result.error || "File download failed"); return; } // Create Blob and download const blob = new Blob([Buffer.from(result.data)], { type: result.mimeType }); const url = URL.createObjectURL(blob); const link = window.document.createElement("a"); link.href = url; link.download = result.fileName || fileName; window.document.body.appendChild(link); link.click(); window.document.body.removeChild(link); URL.revokeObjectURL(url); toast.success(`File downloaded: ${fileName}`); } catch (error) { console.error("File download failed:", error); toast.error("Failed to download file"); } }; return ( Uploaded File Details {document && ( {document.ownDocNo} )} {document && (
{/* Document Info */}
OWN_DOC_NO:
{document.ownDocNo}
Latest Stage:
{document.latestStage || "-"}
Latest Revision:
{document.latestRevNo || "-"}
Latest REV Files:
{document.latestRevFileCount}
{/* Revision and Activity Tree */} {revisionGroups.length > 0 ? (
{/* Toggle All Button */}
{revisionGroups.map((revision) => { const revKey = revision.revNo; const isRevExpanded = expandedRevisions.has(revKey); return (
{/* Revision Header */}
toggleRevision(revKey)} >
{isRevExpanded ? ( ) : ( )} REV {revision.revNo} {revision.stage} {revision.activities.length} Groups / {revision.totalFiles} Files
{/* Activity List (or files without Activity) */} {isRevExpanded && (
{revision.activities.map((activity) => { const actKey = `${revKey}|${activity.actvNo || "NO_ACTIVITY"}`; const isActExpanded = expandedActivities.has(actKey); return (
{/* Activity Header */}
toggleActivity(actKey)} >
{isActExpanded ? ( ) : ( )} {activity.actvNo ? ( <> Activity {activity.actvNo} ) : ( No Activity )} {activity.files.length} Files
{/* File List */} {isActExpanded && (
{activity.files.map((file, idx) => (
{file.FILE_NM} {file.FILE_SZ && ( ({formatFileSize(file.FILE_SZ)}) )} {file.STAT && ( {file.STAT_NM || file.STAT} )}
{file.STAT === "SCW01" && file.BOX_SEQ && file.ACTV_SEQ && ( )} {file.FLD_PATH && ( )}
))}
)}
); })}
)}
); })}
) : (

No file information available

)}
)}
); } function formatFileSize(sizeStr: string): string { const bytes = parseInt(sizeStr, 10); if (isNaN(bytes)) return sizeStr; const kb = bytes / 1024; const mb = kb / 1024; return mb >= 1 ? `${mb.toFixed(2)} MB` : `${kb.toFixed(2)} KB`; }