"use client"; import { ArrowDownToLine, ArrowUpFromLine, Calendar, CheckCircle2, Clock, Copy, ExternalLink, Eye, FileText, GitBranch, HardDrive, Link2, Paperclip, Pencil, Trash2, User, X, Image as ImageIcon, Reply, Radio, RefreshCw, ChevronDown, ChevronUp, } from "lucide-react"; import { Button } from "@/shared/components/ui/button"; import { Badge } from "@/shared/components/ui/badge"; import { Separator } from "@/shared/components/ui/separator"; import { ScrollArea } from "@/shared/components/ui/scroll-area"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, } from "@/shared/components/ui/sheet"; import type { RegistryEntry } from "../types"; import { DEFAULT_DOC_TYPE_LABELS, EXTERNAL_STATUS_LABELS } from "../types"; import type { ExternalDocStatus } from "../types"; import { getAuthority } from "../services/authority-catalog"; import { getOverdueDays } from "../services/registry-service"; import { pathFileName, shareLabelFor } from "@/config/nas-paths"; import { cn } from "@/shared/lib/utils"; import { useState, useCallback, useMemo } from "react"; import { AttachmentPreview, getPreviewableAttachments, } from "./attachment-preview"; import { findAuthorityForContact } from "../services/authority-catalog"; import { computeTransmissionStatus } from "../services/deadline-service"; import { StatusMonitorConfig } from "./status-monitor-config"; import { FlowDiagram } from "./flow-diagram"; import { DeadlineTimeline } from "./deadline-timeline"; interface RegistryEntryDetailProps { entry: RegistryEntry | null; open: boolean; onOpenChange: (open: boolean) => void; onEdit: (entry: RegistryEntry) => void; onClose: (entry: RegistryEntry) => void; onDelete: (id: string) => void; /** Create a new entry linked as reply (conex) to this entry */ onReply?: (entry: RegistryEntry) => void; allEntries: RegistryEntry[]; } const DIRECTION_CONFIG = { intrat: { label: "Intrat", icon: ArrowDownToLine, class: "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300", }, iesit: { label: "Ieșit", icon: ArrowUpFromLine, class: "bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300", }, } as const; const STATUS_CONFIG = { deschis: { label: "Deschis", class: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300", }, inchis: { label: "Închis", class: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400", }, reserved: { label: "Rezervat", class: "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300", }, } as const; const RESOLUTION_LABELS: Record = { finalizat: "Finalizat", "aprobat-tacit": "Aprobat tacit", respins: "Respins", retras: "Retras", altele: "Altele", }; const DEADLINE_RES_LABELS: Record = { pending: "În așteptare", completed: "Finalizat", "aprobat-tacit": "Aprobat tacit", respins: "Respins", anulat: "Anulat", }; function getDocTypeLabel(type: string): string { const label = DEFAULT_DOC_TYPE_LABELS[type]; if (label) return label; return type.replace(/-/g, " ").replace(/^\w/, (c) => c.toUpperCase()); } function formatDate(iso: string): string { try { return new Date(iso).toLocaleDateString("ro-RO", { day: "2-digit", month: "2-digit", year: "numeric", }); } catch { return iso; } } function formatDateTime(iso: string): string { try { return new Date(iso).toLocaleString("ro-RO", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", }); } catch { return iso; } } export function RegistryEntryDetail({ entry, open, onOpenChange, onEdit, onClose, onDelete, onReply, allEntries, }: RegistryEntryDetailProps) { const [previewIndex, setPreviewIndex] = useState(null); const [copiedPath, setCopiedPath] = useState(null); const [monitorConfigOpen, setMonitorConfigOpen] = useState(false); // Auto-detect if recipient matches a known authority const matchedAuthority = useMemo(() => { if (!entry) return undefined; if (entry.externalStatusTracking?.active) return undefined; if (!entry.recipientRegNumber) return undefined; return findAuthorityForContact(entry.recipient); }, [entry]); const previewableAtts = useMemo( () => (entry ? getPreviewableAttachments(entry.attachments) : []), [entry], ); const copyPath = useCallback(async (path: string) => { await navigator.clipboard.writeText(path); setCopiedPath(path); setTimeout(() => setCopiedPath(null), 2000); }, []); // Build full chain for mini flow diagram (must be before early return to keep hook order stable) const threadChain = useMemo(() => { if (!entry) return []; const threadParentEntry = entry.threadParentId ? allEntries.find((e) => e.id === entry.threadParentId) : null; const threadChildEntries = allEntries.filter( (e) => e.threadParentId === entry.id, ); if (!threadParentEntry && threadChildEntries.length === 0) return []; const byId = new Map(allEntries.map((e) => [e.id, e])); // Walk up to root let root = entry; while (root.threadParentId) { const parent = byId.get(root.threadParentId); if (!parent) break; root = parent; } // BFS down from root const chain: RegistryEntry[] = []; const queue = [root.id]; const visited = new Set(); while (queue.length > 0) { const id = queue.shift()!; if (visited.has(id)) continue; visited.add(id); const e = byId.get(id); if (!e) continue; chain.push(e); for (const child of allEntries.filter((c) => c.threadParentId === id)) { queue.push(child.id); } } chain.sort((a, b) => a.date.localeCompare(b.date)); return chain; }, [entry, allEntries]); if (!entry) return null; const dir = DIRECTION_CONFIG[entry.direction] ?? DIRECTION_CONFIG.intrat; const DirIcon = dir.icon; const status = STATUS_CONFIG[entry.status] ?? STATUS_CONFIG.deschis; const overdueDays = entry.status === "deschis" ? getOverdueDays(entry.deadline) : null; const isOverdue = overdueDays !== null && overdueDays > 0; const threadParent = entry.threadParentId ? allEntries.find((e) => e.id === entry.threadParentId) : null; const threadChildren = allEntries.filter( (e) => e.threadParentId === entry.id, ); return (
{entry.number} {entry.subject}
{/* Action bar */}
{onReply && ( )} {entry.status === "deschis" && ( )}
{/* ── Status row ── */}
{dir.label} {status.label} {getDocTypeLabel(entry.documentType)} {entry.company && ( {entry.company} )}
{/* ── Closure info ── */} {entry.closureInfo && (
Rezoluție: {RESOLUTION_LABELS[entry.closureInfo.resolution] ?? entry.closureInfo.resolution}
{entry.closureInfo.reason && (

Motiv:{" "} {entry.closureInfo.reason}

)} {entry.closureInfo.closedBy && (

Închis de {entry.closureInfo.closedBy} la{" "} {formatDateTime(entry.closureInfo.closedAt)}

)}
)} {/* ── Date ── */}
{entry.registrationDate && entry.registrationDate !== entry.date && ( )} {entry.deadline && ( 0 ? `(${overdueDays} zile depășit)` : overdueDays !== null && overdueDays < 0 ? `(mai sunt ${Math.abs(overdueDays)} zile)` : undefined } /> )} {entry.expiryDate && ( )}
{/* ── Parties ── */}
{entry.sender && ( )} {entry.recipient && ( )} {entry.assignee && ( } /> )}
{(entry.recipientRegNumber || entry.recipientRegDate) && (
{entry.recipientRegNumber && ( Nr. destinatar: {entry.recipientRegNumber} )} {entry.recipientRegDate && ( Data: {formatDate(entry.recipientRegDate)} )}
)}
{/* ── Thread links (flow diagram) ── */} {(threadChain.length >= 2 || (entry.linkedEntryIds ?? []).length > 0) && ( {threadChain.length >= 2 && ( )} {/* Transmission status for thread children */} {threadChildren.length > 0 && (
{threadChildren.map((child) => { const txStatus = computeTransmissionStatus(entry, child); if (!txStatus) return null; return (
{child.number} {txStatus === "on-time" ? ( Transmis in termen ) : ( Transmis cu intarziere )}
); })}
)} {(entry.linkedEntryIds ?? []).length > 0 && (

{entry.linkedEntryIds.length} inregistrar {entry.linkedEntryIds.length === 1 ? "e" : "i"} legat {entry.linkedEntryIds.length === 1 ? "a" : "e"}

)}
)} {/* ── Attachments ── */} {entry.attachments.length > 0 && (
{entry.attachments.map((att) => att.networkPath ? ( ) : (
{att.type.startsWith("image/") ? ( ) : ( )}

{att.name}

{(att.size / 1024).toFixed(0)} KB •{" "} {att.type.split("/")[1]?.toUpperCase() ?? att.type}

{/* Preview button (images + PDFs) */} {att.data && att.data !== "" && att.data !== "__network__" && (att.type.startsWith("image/") || att.type === "application/pdf") && ( )} {/* Download for files with data */} {att.data && att.data !== "" && att.data !== "__network__" && ( )} Fișier
), )}
)} {/* ── Legal deadlines (timeline view) ── */} {(entry.trackedDeadlines ?? []).length > 0 && ( )} {/* ── External status monitoring ── */} {entry.externalStatusTracking?.active && ( )} {/* ── Auto-detect: suggest monitoring activation ── */} {matchedAuthority && !entry.externalStatusTracking?.active && (

{matchedAuthority.name} suporta monitorizare automata

Se poate verifica automat statusul cererii nr.{" "} {entry.recipientRegNumber} de 4 ori pe zi.

{ // Save tracking to entry via API try { await fetch("/api/registratura", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: entry.id, updates: { externalStatusTracking: tracking }, }), }); window.location.reload(); } catch { // Best effort } }} />
)} {/* ── External tracking (legacy fields) ── */} {!entry.externalStatusTracking?.active && (entry.externalStatusUrl || entry.externalTrackingId) && (
{entry.externalTrackingId && ( )} {entry.externalStatusUrl && ( )}
)} {/* ── Tags ── */} {entry.tags.length > 0 && (
{entry.tags.map((tag) => ( {tag} ))}
)} {/* ── Notes ── */} {entry.notes && (

{entry.notes}

)}
{/* QuickLook-style attachment preview */} {previewIndex !== null && ( setPreviewIndex(null)} /> )}
); } // ── Sub-components ── // ── External Status Monitoring Section ── const STATUS_COLORS: Record = { "in-operare": "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400", trimis: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", solutionat: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", respins: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", necunoscut: "bg-muted text-muted-foreground", }; function ExternalStatusSection({ entry }: { entry: RegistryEntry }) { const tracking = entry.externalStatusTracking; if (!tracking) return null; const [checking, setChecking] = useState(false); const [showHistory, setShowHistory] = useState(false); const authority = getAuthority(tracking.authorityId); const handleManualCheck = useCallback(async () => { setChecking(true); try { await fetch("/api/registratura/status-check/single", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ entryId: entry.id }), }); // Reload page to show updated status window.location.reload(); } catch { // Ignore — user will see if it worked on reload } finally { setChecking(false); } }, [entry.id]); const relativeTime = (iso: string) => { const diff = Date.now() - new Date(iso).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return "chiar acum"; if (mins < 60) return `acum ${mins} min`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `acum ${hrs}h`; const days = Math.floor(hrs / 24); return `acum ${days}z`; }; return (

Monitorizare status extern

{/* Authority + status badge */}
{authority?.name ?? tracking.authorityId} {EXTERNAL_STATUS_LABELS[tracking.semanticStatus]}
{/* Last check time */} {tracking.lastCheckAt && (

Ultima verificare: {relativeTime(tracking.lastCheckAt)}

)} {/* Error state */} {tracking.lastError && (

{tracking.lastError}

)} {/* Latest status row */} {tracking.lastStatusRow && (
Sursa:{" "} {tracking.lastStatusRow.sursa} {" "} {tracking.lastStatusRow.destinatie}
{tracking.lastStatusRow.modRezolvare && (
Rezolvare:{" "} {tracking.lastStatusRow.modRezolvare}
)} {tracking.lastStatusRow.comentarii && (
{tracking.lastStatusRow.comentarii}
)}
{tracking.lastStatusRow.dataVenire} {tracking.lastStatusRow.oraVenire}
)} {/* History toggle */} {tracking.history.length > 0 && (
{showHistory && (
{[...tracking.history].reverse().map((change, i) => (
{EXTERNAL_STATUS_LABELS[change.semanticStatus]} {new Date(change.timestamp).toLocaleString("ro-RO")}
{change.row.sursa} → {change.row.destinatie} {change.row.modRezolvare ? ` (${change.row.modRezolvare})` : ""}
))}
)}
)}
); } function DetailSection({ title, children, }: { title: string; children: React.ReactNode; }) { return (

{title}

{children}
); } function DetailField({ label, value, className, extra, icon, }: { label: string; value: string; className?: string; extra?: string; icon?: React.ReactNode; }) { return (

{label}

{icon && {icon}} {value} {extra && ( {extra} )}

); }