diff --git a/ROADMAP.md b/ROADMAP.md index 550b9a1..c87f260 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -264,6 +264,7 @@ - **Legături între intrări/ieșiri (Thread-uri & Branches):** Posibilitatea de a lega o ieșire de o intrare specifică (ex: "Răspuns la adresa nr. X"), creând un "fir" (thread) vizual al conversației instituționale. Trebuie să suporte și "branching" (ex: o intrare generează mai multe ieșiri conexe către instituții diferite). UI-ul trebuie să rămână extrem de simplu și intuitiv (ex: vizualizare tip arbore simplificat sau listă indentată în detaliile documentului). **Status:** ✅ Done. All 6 sub-features implemented: + 1. **Dynamic doc types** — Select + inline "Tip nou" input. New types auto-created as Tag Manager tags (category: document-type). Added "Apel telefonic" and "Videoconferință" as defaults. 2. **Bidirectional Address Book** — Autocomplete shows "Creează contact" button when no match. QuickContactDialog popup creates contact in Address Book with minimal data (Name required, Phone/Email optional). 3. **Simplified status** — Replaced Status dropdown with Switch toggle "Închis/Deschis". Default is open. diff --git a/SESSION-LOG.md b/SESSION-LOG.md index 05e74c7..7242f44 100644 --- a/SESSION-LOG.md +++ b/SESSION-LOG.md @@ -46,6 +46,7 @@ Continued Phase 3 refinements. Picked task 3.02 (HEAVY) — Registratura bidirec ## Session — 2026-02-27 evening (GitHub Copilot - Claude Opus 4.6) ### Context + Continued from earlier session (Gemini 3.1 Pro got stuck on Authentik testing). This session focused on fixing deployment pipeline, then implementing Phase 3 visual/UX tasks + infrastructure documentation. ### Completed @@ -85,9 +86,11 @@ Continued from earlier session (Gemini 3.1 Pro got stuck on Authentik testing). - SESSION-LOG.md: this entry ### Files Created + - `src/shared/components/common/theme-toggle.tsx` — Animated sun/moon theme toggle ### Files Modified + - `Dockerfile` — Added `npx prisma generate` - `docker-compose.yml` — All env vars hardcoded - `package.json` — @prisma/client moved to dependencies @@ -101,6 +104,7 @@ Continued from earlier session (Gemini 3.1 Pro got stuck on Authentik testing). - `src/modules/hot-desk/components/desk-room-layout.tsx` — Window + door landmarks ### Notes + - Portainer CE requires manual "Pull and redeploy" — no auto-rebuild on webhook - "Re-pull image" checkbox only needed for base image updates (node:20-alpine), not for code changes - Logo SVGs have very different aspect ratios (BTG ~7:1, US ~6:1, SDT ~3:1) — using flex-1 min-w-0 to handle this diff --git a/src/modules/registratura/components/close-guard-dialog.tsx b/src/modules/registratura/components/close-guard-dialog.tsx new file mode 100644 index 0000000..7a90bf3 --- /dev/null +++ b/src/modules/registratura/components/close-guard-dialog.tsx @@ -0,0 +1,252 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { AlertTriangle, Search, Link2 } from "lucide-react"; +import { Button } from "@/shared/components/ui/button"; +import { Input } from "@/shared/components/ui/input"; +import { Label } from "@/shared/components/ui/label"; +import { Textarea } from "@/shared/components/ui/textarea"; +import { Badge } from "@/shared/components/ui/badge"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/shared/components/ui/dialog"; +import type { RegistryEntry, TrackedDeadline } from "../types"; +import { getDeadlineType } from "../services/deadline-catalog"; + +interface CloseGuardDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + /** The entry being closed */ + entry: RegistryEntry; + /** All entries for search/linking */ + allEntries: RegistryEntry[]; + /** Active deadlines on this entry */ + activeDeadlines: TrackedDeadline[]; + /** Called when user provides justification and confirms close */ + onConfirmClose: (justification: { + linkedEntryId?: string; + reason: string; + }) => void; +} + +/** + * Guard dialog shown when a user tries to close an entry that has + * unresolved (active) legal deadlines. Requires justification: + * either link to a continuation entry or provide manual reason text. + */ +export function CloseGuardDialog({ + open, + onOpenChange, + entry, + allEntries, + activeDeadlines, + onConfirmClose, +}: CloseGuardDialogProps) { + const [mode, setMode] = useState<"link" | "manual">("link"); + const [search, setSearch] = useState(""); + const [selectedEntryId, setSelectedEntryId] = useState(""); + const [reason, setReason] = useState(""); + + // Reset on open + const handleOpenChange = (o: boolean) => { + if (o) { + setMode("link"); + setSearch(""); + setSelectedEntryId(""); + setReason(""); + } + onOpenChange(o); + }; + + // Searchable entries (exclude self and closed entries) + const searchResults = useMemo(() => { + if (!search || search.length < 2) return []; + const q = search.toLowerCase(); + return allEntries + .filter( + (e) => + e.id !== entry.id && + e.status !== "inchis" && + (e.subject.toLowerCase().includes(q) || + e.number.toLowerCase().includes(q) || + e.sender.toLowerCase().includes(q) || + e.recipient.toLowerCase().includes(q)), + ) + .slice(0, 8); + }, [search, allEntries, entry.id]); + + const selectedEntry = allEntries.find((e) => e.id === selectedEntryId); + + const canSubmit = + mode === "link" + ? selectedEntryId !== "" + : reason.trim().length >= 5; + + const handleSubmit = () => { + if (!canSubmit) return; + onConfirmClose({ + linkedEntryId: mode === "link" ? selectedEntryId : undefined, + reason: + mode === "link" + ? `Continuat în #${selectedEntry?.number ?? selectedEntryId}` + : reason.trim(), + }); + }; + + return ( + + + + + + Termene legale active + + + +
+ {/* Warning message */} +

+ Înregistrarea {entry.number} are{" "} + {activeDeadlines.length} termen + {activeDeadlines.length > 1 ? "e" : ""} legal + {activeDeadlines.length > 1 ? "e" : ""} nerezolvat + {activeDeadlines.length > 1 ? "e" : ""}. Pentru a o închide, + justifică decizia. +

+ + {/* Active deadlines list */} +
+ {activeDeadlines.map((dl) => { + const def = getDeadlineType(dl.typeId); + const isOverdue = new Date(dl.dueDate) < new Date(); + return ( +
+ + {isOverdue ? "Depășit" : "Activ"} + + + {def?.label ?? dl.typeId} + + + scadent {dl.dueDate} + +
+ ); + })} +
+ + {/* Mode toggle */} +
+ + +
+ + {/* Link mode */} + {mode === "link" && ( +
+ +
+ + { + setSearch(e.target.value); + setSelectedEntryId(""); + }} + placeholder="Caută după nr., subiect, expeditor..." + className="pl-9" + /> +
+ {searchResults.length > 0 && ( +
+ {searchResults.map((e) => ( + + ))} +
+ )} + {selectedEntry && ( +

+ Selectat: {selectedEntry.number} —{" "} + {selectedEntry.subject} +

+ )} +
+ )} + + {/* Manual mode */} + {mode === "manual" && ( +
+ +