diff --git a/src/modules/registratura/components/close-guard-dialog.tsx b/src/modules/registratura/components/close-guard-dialog.tsx index 4d142b0..43db31f 100644 --- a/src/modules/registratura/components/close-guard-dialog.tsx +++ b/src/modules/registratura/components/close-guard-dialog.tsx @@ -1,7 +1,14 @@ "use client"; -import { useState, useMemo } from "react"; -import { AlertTriangle, Search, Link2 } from "lucide-react"; +import { useState, useMemo, useRef } from "react"; +import { + AlertTriangle, + Search, + Link2, + Paperclip, + X, + FileText, +} from "lucide-react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; import { Label } from "@/shared/components/ui/label"; @@ -14,8 +21,14 @@ import { DialogTitle, DialogFooter, } from "@/shared/components/ui/dialog"; -import type { RegistryEntry, TrackedDeadline } from "../types"; +import type { + RegistryEntry, + TrackedDeadline, + RegistryAttachment, + ClosureInfo, +} from "../types"; import { getDeadlineType } from "../services/deadline-catalog"; +import { v4 as uuid } from "uuid"; interface CloseGuardDialogProps { open: boolean; @@ -24,19 +37,18 @@ interface CloseGuardDialogProps { entry: RegistryEntry; /** All entries for search/linking */ allEntries: RegistryEntry[]; - /** Active deadlines on this entry */ + /** Active (pending) deadlines — empty if none */ activeDeadlines: TrackedDeadline[]; - /** Called when user provides justification and confirms close */ - onConfirmClose: (justification: { - linkedEntryId?: string; - reason: string; - }) => void; + /** Called when user confirms close with all structured data */ + onConfirmClose: (info: ClosureInfo) => 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. + * Universal close dialog: always shown when closing any entry. + * - If active deadlines exist → shows a warning banner + * - Requires a reason (free text) + * - Optionally link a continuation entry + * - Optionally attach a closing document (e.g. scanned letter) */ export function CloseGuardDialog({ open, @@ -46,23 +58,26 @@ export function CloseGuardDialog({ activeDeadlines, onConfirmClose, }: CloseGuardDialogProps) { - const [mode, setMode] = useState<"link" | "manual">("link"); const [search, setSearch] = useState(""); const [selectedEntryId, setSelectedEntryId] = useState(""); const [reason, setReason] = useState(""); + const [attachment, setAttachment] = useState(null); + const fileRef = useRef(null); + + const hasDeadlines = activeDeadlines.length > 0; // Reset on open const handleOpenChange = (o: boolean) => { if (o) { - setMode("link"); setSearch(""); setSelectedEntryId(""); setReason(""); + setAttachment(null); } onOpenChange(o); }; - // Searchable entries (exclude self and closed entries) + // Searchable entries (exclude self) const searchResults = useMemo(() => { if (!search || search.length < 2) return []; const q = search.toLowerCase(); @@ -70,7 +85,6 @@ export function CloseGuardDialog({ .filter( (e) => e.id !== entry.id && - e.status !== "inchis" && (e.subject.toLowerCase().includes(q) || e.number.toLowerCase().includes(q) || e.sender.toLowerCase().includes(q) || @@ -81,17 +95,36 @@ export function CloseGuardDialog({ const selectedEntry = allEntries.find((e) => e.id === selectedEntryId); - const canSubmit = - mode === "link" ? selectedEntryId !== "" : reason.trim().length >= 5; + const canSubmit = reason.trim().length >= 3; + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + setAttachment({ + id: uuid(), + name: file.name, + data: reader.result as string, + type: file.type, + size: file.size, + addedAt: new Date().toISOString(), + }); + }; + reader.readAsDataURL(file); + e.target.value = ""; + }; const handleSubmit = () => { if (!canSubmit) return; onConfirmClose({ - linkedEntryId: mode === "link" ? selectedEntryId : undefined, - reason: - mode === "link" - ? `Continuat în #${selectedEntry?.number ?? selectedEntryId}` - : reason.trim(), + reason: reason.trim(), + closedBy: "Utilizator", // TODO: replace with SSO identity + closedAt: new Date().toISOString(), + linkedEntryId: selectedEntryId || undefined, + linkedEntryNumber: selectedEntry?.number, + hadActiveDeadlines: hasDeadlines, + attachment: attachment ?? undefined, }); }; @@ -99,127 +132,170 @@ export function CloseGuardDialog({ - - - Termene legale active + + {hasDeadlines ? ( + <> + + + Închidere cu termene active + + + ) : ( + <>Închide înregistrarea {entry.number} + )}
- {/* 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} -

- )} + {isOverdue ? "Depășit" : "Activ"} + + + {def?.label ?? dl.typeId} + + + scadent {dl.dueDate} + +
+ ); + })}
)} - {/* Manual mode */} - {mode === "manual" && ( -
- -