From 4dae06be44f10c586f3977d5e767403b1cb20905 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Sat, 28 Feb 2026 17:45:18 +0200 Subject: [PATCH] feat(registratura): detail sheet side panel + configurable column visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New registry-entry-detail.tsx: full entry visualization in Sheet (side panel) - Status badges, document info, parties, dates, thread links - Attachment preview: images inline, NAS paths with IP fallback - Legal deadlines, external tracking, tags, notes sections - Action buttons: Editează, Închide, Șterge - Registry table rewrite: - 10 column defs with Romanian tooltip explanations on each header - Column visibility dropdown (Settings icon) with checkboxes - Default: Nr/Data/Dir/Subiect/Exped./Dest./Status (7/10) - Persisted in localStorage (registratura:visible-columns) - Row click opens detail sheet, actions reduced to Eye + Pencil - Docs updated: CLAUDE.md, ROADMAP.md (3.03c), SESSION-LOG.md --- CLAUDE.md | 2 +- ROADMAP.md | 12 + SESSION-LOG.md | 29 +- .../components/registratura-module.tsx | 22 + .../components/registry-entry-detail.tsx | 727 ++++++++++++++++++ .../components/registry-table.tsx | 547 +++++++++---- 6 files changed, 1181 insertions(+), 158 deletions(-) create mode 100644 src/modules/registratura/components/registry-entry-detail.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 5c93f1a..f679eec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,7 +101,7 @@ legacy/ # Original HTML tools for reference | 1 | **Dashboard** | `/` | 0.1.0 | KPI cards (6), activity feed (last 20), module grid, external tools | | 2 | **Email Signature** | `/email-signature` | 0.1.0 | Multi-company branding, address toggle (BTG/US/SDT), live preview, zoom/copy/download | | 3 | **Word XML Generator** | `/word-xml` | 0.1.0 | Category-based XML gen, simple/advanced mode, ZIP export | -| 4 | **Registratura** | `/registratura` | 0.3.0 | CRUD registry, dynamic doc types, bidirectional Address Book, threads, backdating, **legal deadline tracking**, recipient registration, document expiry, **NAS network path attachments** (A/O/P/T drives, hostname+IP fallback) | +| 4 | **Registratura** | `/registratura` | 0.3.0 | CRUD registry, dynamic doc types, bidirectional Address Book, threads, backdating, **legal deadline tracking**, recipient registration, document expiry, **NAS network path attachments** (A/O/P/T drives, hostname+IP fallback), **detail sheet side panel**, **configurable column visibility** | | 5 | **Tag Manager** | `/tag-manager` | 0.2.0 | CRUD tags, category/scope/color, US/SDT seeds, mandatory categories, **ManicTime bidirectional sync** | | 6 | **IT Inventory** | `/it-inventory` | 0.2.0 | Dynamic equipment types, rented status (purple pulse), **42U rack visualization**, type/status/company filters | | 7 | **Address Book** | `/address-book` | 0.1.0 | CRUD contacts, card grid, vCard export, Registratura reverse lookup, **dynamic types (creatable)** | diff --git a/ROADMAP.md b/ROADMAP.md index c4640b4..2e888b0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -304,6 +304,18 @@ - ✅ **Drive letter → UNC** normalization automată (P:\ → \\newamun\Proiecte) - ✅ **`shareLabelFor()`** helper returns human-readable share name +### 3.03c ✅ `[STANDARD]` Registratura — Detail Sheet + Column Manager (2026-02-28) + +**Implementat:** + +- ✅ **Entry Detail Sheet**: panou lateral (Sheet) cu vizualizare completă a înregistrării — status badges, date, părți, thread-uri, atașamente cu preview inline, termene legale, etichete, note +- ✅ **Attachment Preview**: imagini afișate inline, fișiere cu download, NAS paths cu UNC complet + IP fallback +- ✅ **Column Visibility Manager**: dropdown cu checkbox-uri pentru 10 coloane, persistat în localStorage +- ✅ **Default columns**: Nr., Data, Dir., Subiect, Exped., Dest., Status (7/10 vizibile implicit) +- ✅ **Tooltip naming convention**: fiecare header de coloană are tooltip cu explicație completă în română +- ✅ **Table UX cleanup**: click pe rând deschide detail sheet, acțiuni reduse la View + Edit, Close/Delete mutate în sheet +- ✅ **Row click navigation**: cursor-pointer pe rânduri, Eye icon + Pencil icon în acțiuni + ### 3.04 ✅ `[ARCHITECTURE]` Autentificare & Identitate (2026-02-27) **Cerințe noi:** diff --git a/SESSION-LOG.md b/SESSION-LOG.md index 1929448..ab2f19f 100644 --- a/SESSION-LOG.md +++ b/SESSION-LOG.md @@ -20,14 +20,39 @@ Continuation of QA improvements. NAS path enhancement: all 4 drives + DNS→IP f - Badge now shows share label (Proiecte/Arhiva/Organizare/Transfer) instead of generic "NAS" - Validation hint updated to show all 4 drive letters +- **Registratura — Entry Detail Sheet (Side Panel):** + - New `registry-entry-detail.tsx` component (~500 lines) + - Side panel (Sheet) slides in from the right on Eye icon click or row click + - Full entry visualization: status badges, document info, parties, dates, thread links, legal deadlines, attachments with inline image preview, NAS path links with IP fallback, external tracking, tags, notes + - Action buttons inside sheet: Edită, Închide, Șterge + - Attachment preview: images display inline, files show download button, NAS paths show full UNC + short display + copy + IP fallback + +- **Registratura — Column Visibility Manager:** + - Configurable columns via Settings dropdown in table header + - 10 columns defined with Romanian tooltips explaining each abbreviation + - Default visible: Nr., Data, Dir., Subiect, Exped., Dest., Status (7/10) + - Hidden by default: Tip, Resp., Termen (can be toggled on) + - Persisted in localStorage per user (`registratura:visible-columns`) + - Reset button restores defaults + +- **Registratura — Table UX Cleanup:** + - Row click opens detail sheet (cursor-pointer) + - Actions reduced from 3 buttons (close/edit/delete) to 2 (view/edit) + - Close and Delete moved into detail sheet + - Column headers have tooltips explaining naming convention + - Attachment/thread/deadline indicators compacted in Subject column + - **Documentation updated:** - - CLAUDE.md: Registratura v0.3.0, NAS drives in module table, NAS row in integrations table - - ROADMAP.md: Registratura version bump, new task 3.03b with full feature list + - CLAUDE.md: Registratura v0.3.0 updated description + - ROADMAP.md: New task 3.03c with detail sheet + column manager features - SESSION-LOG.md: This session entry ### Files Changed +- **New:** `src/modules/registratura/components/registry-entry-detail.tsx` - **Modified:** `src/config/nas-paths.ts` (4 drives, IP fallback helpers, shareLabelFor) +- **Modified:** `src/modules/registratura/components/registry-table.tsx` (column visibility, tooltips, view button, row click) +- **Modified:** `src/modules/registratura/components/registratura-module.tsx` (detail sheet integration, handleView) - **Modified:** `src/modules/registratura/components/registry-entry-form.tsx` (IP fallback link, share badge, validation hints) - **Modified:** `CLAUDE.md`, `ROADMAP.md`, `SESSION-LOG.md` diff --git a/src/modules/registratura/components/registratura-module.tsx b/src/modules/registratura/components/registratura-module.tsx index a3d9172..9028763 100644 --- a/src/modules/registratura/components/registratura-module.tsx +++ b/src/modules/registratura/components/registratura-module.tsx @@ -29,6 +29,7 @@ import { v4 as uuid } from "uuid"; import { RegistryFilters } from "./registry-filters"; import { RegistryTable } from "./registry-table"; import { RegistryEntryForm } from "./registry-entry-form"; +import { RegistryEntryDetail } from "./registry-entry-detail"; import { DeadlineDashboard } from "./deadline-dashboard"; import { ThreadExplorer } from "./thread-explorer"; import { CloseGuardDialog } from "./close-guard-dialog"; @@ -62,6 +63,7 @@ export function RegistraturaModule() { const [viewMode, setViewMode] = useState("list"); const [editingEntry, setEditingEntry] = useState(null); + const [viewingEntry, setViewingEntry] = useState(null); const [closingId, setClosingId] = useState(null); const [linkCheckId, setLinkCheckId] = useState(null); @@ -135,9 +137,15 @@ export function RegistraturaModule() { // Load full entry with attachment data (list mode strips base64) const full = await loadFullEntry(entry.id); setEditingEntry(full ?? entry); + setViewingEntry(null); // Close detail sheet if open setViewMode("edit"); }; + const handleView = async (entry: RegistryEntry) => { + const full = await loadFullEntry(entry.id); + setViewingEntry(full ?? entry); + }; + const handleNavigateEntry = async (entry: RegistryEntry) => { const full = await loadFullEntry(entry.id); setEditingEntry(full ?? entry); @@ -305,6 +313,7 @@ export function RegistraturaModule() { )} + {/* Entry detail side panel */} + { + if (!open) setViewingEntry(null); + }} + onEdit={handleEdit} + onClose={handleCloseRequest} + onDelete={handleDelete} + allEntries={allEntries} + /> + {/* Universal close dialog — reason, attachment, linked entry */} {closingEntry && ( void; + onEdit: (entry: RegistryEntry) => void; + onClose: (id: string) => void; + onDelete: (id: string) => 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", + }, +} 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, + allEntries, +}: RegistryEntryDetailProps) { + const [previewAttachment, setPreviewAttachment] = useState( + null, + ); + + 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 */} +
+ + {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 ── */} + {(threadParent || + threadChildren.length > 0 || + (entry.linkedEntryIds ?? []).length > 0) && ( + + {threadParent && ( +
+ + Răspuns la: + + {threadParent.number} + + + — {threadParent.subject} + +
+ )} + {threadChildren.length > 0 && ( +
+

+ Răspunsuri ({threadChildren.length}): +

+ {threadChildren.map((child) => ( +
+ {child.number} + + {child.subject} + +
+ ))} +
+ )} + {(entry.linkedEntryIds ?? []).length > 0 && ( +
+

+ + {entry.linkedEntryIds.length} înregistrăr + {entry.linkedEntryIds.length === 1 ? "e" : "i"} legat + {entry.linkedEntryIds.length === 1 ? "ă" : "e"} +

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

{att.name}

+

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

+
+
+ {/* Preview for images */} + {att.type.startsWith("image/") && + att.data && + att.data !== "" && + att.data !== "__network__" && ( + + )} + {/* Download for files with data */} + {att.data && + att.data !== "" && + att.data !== "__network__" && ( + + )} + + + Fișier + +
+
+ ), + )} +
+ + {/* Image preview modal */} + {previewAttachment && ( +
+ {(() => { + const att = entry.attachments.find( + (a) => a.id === previewAttachment, + ); + if ( + !att || + !att.type.startsWith("image/") || + !att.data || + att.data === "" || + att.data === "__network__" + ) + return ( +

+ Previzualizare indisponibilă (fișierul nu conține + date inline). +

+ ); + return ( +
+
+

{att.name}

+ +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {att.name} +
+ ); + })()} +
+ )} +
+ )} + + {/* ── Legal deadlines ── */} + {(entry.trackedDeadlines ?? []).length > 0 && ( + +
+ {entry.trackedDeadlines!.map((dl) => ( +
+
+ {dl.typeId} + + {DEADLINE_RES_LABELS[dl.resolution] ?? dl.resolution} + +
+
+ + + Start: {formatDate(dl.startDate)} + + + + Scadent: {formatDate(dl.dueDate)} + +
+ {dl.resolutionNote && ( +

+ {dl.resolutionNote} +

+ )} +
+ ))} +
+
+ )} + + {/* ── External tracking ── */} + {(entry.externalStatusUrl || entry.externalTrackingId) && ( + +
+ {entry.externalTrackingId && ( + + )} + {entry.externalStatusUrl && ( +
+

+ URL verificare +

+ + {entry.externalStatusUrl} + +
+ )} +
+
+ )} + + {/* ── Tags ── */} + {entry.tags.length > 0 && ( + +
+ {entry.tags.map((tag) => ( + + {tag} + + ))} +
+
+ )} + + {/* ── Notes ── */} + {entry.notes && ( + +

+ {entry.notes} +

+
+ )} +
+
+
+
+ ); +} + +// ── Sub-components ── + +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} + + )} +

+
+ ); +} diff --git a/src/modules/registratura/components/registry-table.tsx b/src/modules/registratura/components/registry-table.tsx index 544ded7..f595fc4 100644 --- a/src/modules/registratura/components/registry-table.tsx +++ b/src/modules/registratura/components/registry-table.tsx @@ -1,16 +1,32 @@ "use client"; +import { useState, useEffect, useCallback } from "react"; import { + Eye, Pencil, - Trash2, - CheckCircle2, Link2, Clock, GitBranch, User, + Settings2, + Paperclip, } from "lucide-react"; import { Button } from "@/shared/components/ui/button"; import { Badge } from "@/shared/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/shared/components/ui/tooltip"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/shared/components/ui/dropdown-menu"; import type { RegistryEntry } from "../types"; import { DEFAULT_DOC_TYPE_LABELS } from "../types"; import { getOverdueDays } from "../services/registry-service"; @@ -19,21 +35,117 @@ import { cn } from "@/shared/lib/utils"; interface RegistryTableProps { entries: RegistryEntry[]; loading: boolean; + onView: (entry: RegistryEntry) => void; onEdit: (entry: RegistryEntry) => void; onDelete: (id: string) => void; onClose: (id: string) => void; } +// ── Column definitions ── + +export type ColumnId = + | "number" + | "date" + | "direction" + | "type" + | "subject" + | "sender" + | "recipient" + | "assignee" + | "deadline" + | "status"; + +interface ColumnDef { + id: ColumnId; + /** Short header label */ + label: string; + /** Full tooltip explanation */ + tooltip: string; + /** Whether visible by default */ + defaultVisible: boolean; +} + +const COLUMNS: ColumnDef[] = [ + { + id: "number", + label: "Nr.", + tooltip: "Număr de înregistrare (format: PREFIX-NNNN/AN)", + defaultVisible: true, + }, + { + id: "date", + label: "Data", + tooltip: "Data documentului (nu data înregistrării în sistem)", + defaultVisible: true, + }, + { + id: "direction", + label: "Dir.", + tooltip: + "Direcție: Intrat = primit de la terți, Ieșit = trimis către terți", + defaultVisible: true, + }, + { + id: "type", + label: "Tip", + tooltip: "Tipul documentului (contract, cerere, aviz etc.)", + defaultVisible: false, + }, + { + id: "subject", + label: "Subiect", + tooltip: "Subiectul sau descrierea pe scurt a documentului", + defaultVisible: true, + }, + { + id: "sender", + label: "Exped.", + tooltip: "Expeditor — persoana sau instituția care a trimis documentul", + defaultVisible: true, + }, + { + id: "recipient", + label: "Dest.", + tooltip: + "Destinatar — persoana sau instituția căreia i se adresează documentul", + defaultVisible: true, + }, + { + id: "assignee", + label: "Resp.", + tooltip: + "Responsabil intern — persoana din echipă alocată pe acest document", + defaultVisible: false, + }, + { + id: "deadline", + label: "Termen", + tooltip: + "Termen limită intern (nu termen legal — acela apare în tab-ul Termene)", + defaultVisible: false, + }, + { + id: "status", + label: "Status", + tooltip: "Deschis = în lucru, Închis = finalizat/arhivat", + defaultVisible: true, + }, +]; + +const DEFAULT_VISIBLE = new Set( + COLUMNS.filter((c) => c.defaultVisible).map((c) => c.id), +); + +const STORAGE_KEY = "registratura:visible-columns"; + const DIRECTION_LABELS: Record = { intrat: "Intrat", iesit: "Ieșit", }; -/** Resolve doc type label from defaults or capitalize custom type */ function getDocTypeLabel(type: string): string { const label = DEFAULT_DOC_TYPE_LABELS[type]; if (label) return label; - // For custom types, capitalize first letter return type.replace(/-/g, " ").replace(/^\w/, (c) => c.toUpperCase()); } @@ -42,13 +154,58 @@ const STATUS_LABELS: Record = { inchis: "Închis", }; +function loadVisibleColumns(): Set { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) { + const arr = JSON.parse(raw) as ColumnId[]; + if (Array.isArray(arr) && arr.length > 0) return new Set(arr); + } + } catch { + // ignore + } + return new Set(DEFAULT_VISIBLE); +} + export function RegistryTable({ entries, loading, + onView, onEdit, onDelete, onClose, }: RegistryTableProps) { + const [visibleCols, setVisibleCols] = useState>( + () => DEFAULT_VISIBLE, + ); + + // Load from localStorage on mount (client-only) + useEffect(() => { + setVisibleCols(loadVisibleColumns()); + }, []); + + const toggleColumn = useCallback((id: ColumnId) => { + setVisibleCols((prev) => { + const next = new Set(prev); + if (next.has(id)) { + // Don't allow hiding all columns + if (next.size > 2) next.delete(id); + } else { + next.add(id); + } + localStorage.setItem(STORAGE_KEY, JSON.stringify([...next])); + return next; + }); + }, []); + + const resetColumns = useCallback(() => { + const def = new Set(DEFAULT_VISIBLE); + setVisibleCols(def); + localStorage.setItem(STORAGE_KEY, JSON.stringify([...def])); + }, []); + + const visibleColumns = COLUMNS.filter((c) => visibleCols.has(c.id)); + if (loading) { return (

@@ -66,170 +223,250 @@ export function RegistryTable({ } return ( -

- - - - - - - - - - - - - - - - - - {entries.map((entry) => { - const overdueDays = - entry.status === "deschis" || !entry.status - ? getOverdueDays(entry.deadline) - : null; - const isOverdue = overdueDays !== null && overdueDays > 0; - return ( - + {/* Column toggle button */} +
+ + + + + + + Coloane vizibile + + + {COLUMNS.map((col) => ( + toggleColumn(col.id)} + onSelect={(e) => e.preventDefault()} > -
- + + ); + })} + +
Nr.Data doc.Dir.TipSubiectExpeditorDestinatarResp.TermenStatusAcțiuni
- {entry.number} - - {formatDate(entry.date)} - {entry.registrationDate && - entry.registrationDate !== entry.date && ( - + {col.label}{" "} + — {col.tooltip} + + + ))} + + e.preventDefault()} + > + Resetează la implicit + + + + + + {/* Table */} +
+ + + + {visibleColumns.map((col) => ( + + + + + + +

{col.tooltip}

+
+ + + ))} + {/* Actions column is always shown */} + + + + + {entries.map((entry) => { + const overdueDays = + entry.status === "deschis" || !entry.status + ? getOverdueDays(entry.deadline) + : null; + const isOverdue = overdueDays !== null && overdueDays > 0; + return ( + onView(entry)} + > + {visibleCols.has("number") && ( + + )} + {visibleCols.has("date") && ( + + )} + {visibleCols.has("direction") && ( + + )} + {visibleCols.has("type") && ( + + )} + {visibleCols.has("subject") && ( + - - - )} - {(entry.linkedEntryIds ?? []).length > 0 && ( - - )} - {(entry.attachments ?? []).length > 0 && ( - - {entry.attachments.length} fișiere - - )} - {(entry.trackedDeadlines ?? []).length > 0 && ( - - - {(entry.trackedDeadlines ?? []).length} - - )} - - - - - + )} + {visibleCols.has("recipient") && ( + + )} + {visibleCols.has("assignee") && ( + )} - - - + )} + {visibleCols.has("status") && ( + + )} + {/* Actions — always visible */} + - - ); - })} - -
+ {col.label} + + Acț. +
+ {entry.number} + + {formatDate(entry.date)} + {entry.registrationDate && + entry.registrationDate !== entry.date && ( + + (înr. {formatDate(entry.registrationDate)}) + + )} + + - (înr. {formatDate(entry.registrationDate)}) + {DIRECTION_LABELS[entry.direction] ?? + entry.direction ?? + "—"} + + + {getDocTypeLabel(entry.documentType)} + + {entry.subject} + {/* Inline indicators */} + + {entry.threadParentId && ( + + )} + {(entry.linkedEntryIds ?? []).length > 0 && ( + + )} + {(entry.attachments ?? []).length > 0 && ( + + + {entry.attachments.length} + + )} + {(entry.trackedDeadlines ?? []).length > 0 && ( + + + {(entry.trackedDeadlines ?? []).length} + + )} - )} - - - {DIRECTION_LABELS[entry.direction] ?? - entry.direction ?? - "—"} - - - {getDocTypeLabel(entry.documentType)} - - {entry.subject} - {entry.threadParentId && ( - + - {entry.sender} - - {entry.recipient} - - {entry.assignee ? ( - - - {entry.assignee} - - ) : ( - - )} - - {entry.deadline ? ( - + {entry.sender || ( + )} - > - {formatDate(entry.deadline)} - {overdueDays !== null && overdueDays > 0 && ( - - ({overdueDays}z depășit) + + {entry.recipient || ( + + )} + + {entry.assignee ? ( + + + {entry.assignee} + ) : ( + )} - {overdueDays !== null && overdueDays < 0 && ( - - ({Math.abs(overdueDays)}z) - - )} - - ) : ( - + - - {STATUS_LABELS[entry.status]} - - -
- {entry.status === "deschis" && ( + {visibleCols.has("deadline") && ( +
+ {entry.deadline ? ( + + {formatDate(entry.deadline)} + {overdueDays !== null && overdueDays > 0 && ( + + ({overdueDays}z) + + )} + + ) : ( + + )} + + + {STATUS_LABELS[entry.status]} + + +
- )} - - -
-
+ +
+
+
); }