feat: simplify deadline dashboard + add flow diagrams for document chains
Major UX overhaul of the "Termene legale" and thread tabs: Deadline dashboard: - Replace 6 KPI cards with simple summary bar (active/urgent/overdue) - Replace flat table with grouped list by entry (cards with progress bars) - Chain deadlines collapsed by default with expand toggle - Auto-tracked/background deadlines hidden from main list Flow diagram (new component): - CSS-only horizontal flow diagram showing document chains - Nodes with direction bar (blue=intrat, orange=iesit), number, subject, status - Solid arrows for thread links, dashed for conex/linked entries - Used in both "Dosare" tab (full) and detail panel (compact, max 5 nodes) Thread explorer → Dosare: - Renamed tab "Fire conversatie" → "Dosare" - Each thread shown as a card with flow diagram inside - Simplified stats (just active/finalized count) Background tracking: - comunicare-aviz-beneficiar marked as backgroundOnly (not shown in dashboard) - Transmission status computed and shown in detail panel (on-time/late) Auto-resolution: - When closing entry via reply, matching parent deadlines auto-resolve - Resolution note includes the reply entry number Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,9 @@ import {
|
||||
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";
|
||||
|
||||
interface RegistryEntryDetailProps {
|
||||
entry: RegistryEntry | null;
|
||||
@@ -191,6 +193,36 @@ export function RegistryEntryDetail({
|
||||
(e) => e.threadParentId === entry.id,
|
||||
);
|
||||
|
||||
// Build full chain for mini flow diagram
|
||||
const threadChain = useMemo(() => {
|
||||
if (!threadParent && threadChildren.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<string>();
|
||||
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, threadParent, threadChildren, allEntries]);
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent
|
||||
@@ -387,48 +419,52 @@ export function RegistryEntryDetail({
|
||||
)}
|
||||
</DetailSection>
|
||||
|
||||
{/* ── Thread links ── */}
|
||||
{(threadParent ||
|
||||
threadChildren.length > 0 ||
|
||||
{/* ── Thread links (flow diagram) ── */}
|
||||
{(threadChain.length >= 2 ||
|
||||
(entry.linkedEntryIds ?? []).length > 0) && (
|
||||
<DetailSection title="Legături">
|
||||
{threadParent && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<GitBranch className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<span className="text-muted-foreground">Răspuns la:</span>
|
||||
<span className="font-mono text-xs">
|
||||
{threadParent.number}
|
||||
</span>
|
||||
<span className="truncate text-muted-foreground">
|
||||
— {threadParent.subject}
|
||||
</span>
|
||||
</div>
|
||||
<DetailSection title="Dosar">
|
||||
{threadChain.length >= 2 && (
|
||||
<FlowDiagram
|
||||
chain={threadChain}
|
||||
allEntries={allEntries}
|
||||
highlightEntryId={entry.id}
|
||||
compact
|
||||
maxNodes={5}
|
||||
/>
|
||||
)}
|
||||
{/* Transmission status for thread children */}
|
||||
{threadChildren.length > 0 && (
|
||||
<div className="mt-1 space-y-0.5">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Răspunsuri ({threadChildren.length}):
|
||||
</p>
|
||||
{threadChildren.map((child) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex items-center gap-2 text-xs ml-4"
|
||||
>
|
||||
<span className="font-mono">{child.number}</span>
|
||||
<span className="truncate text-muted-foreground">
|
||||
{child.subject}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-2 space-y-1">
|
||||
{threadChildren.map((child) => {
|
||||
const txStatus = computeTransmissionStatus(entry, child);
|
||||
if (!txStatus) return null;
|
||||
return (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex items-center gap-2 text-[10px]"
|
||||
>
|
||||
<span className="font-mono">{child.number}</span>
|
||||
{txStatus === "on-time" ? (
|
||||
<span className="text-green-600 dark:text-green-400">
|
||||
Transmis in termen
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-amber-600 dark:text-amber-400">
|
||||
Transmis cu intarziere
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{(entry.linkedEntryIds ?? []).length > 0 && (
|
||||
<div className="mt-1">
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
<Link2 className="h-3 w-3" />
|
||||
{entry.linkedEntryIds.length} înregistrăr
|
||||
{entry.linkedEntryIds.length} inregistrar
|
||||
{entry.linkedEntryIds.length === 1 ? "e" : "i"} legat
|
||||
{entry.linkedEntryIds.length === 1 ? "ă" : "e"}
|
||||
{entry.linkedEntryIds.length === 1 ? "a" : "e"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user