From cd4b0de1e9e3231de9b5a95f70c9618ea2f9d24e Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Thu, 19 Feb 2026 07:08:59 +0200 Subject: [PATCH] feat(registratura): linked-entry search filter, remove 20-item cap --- .../components/registry-entry-form.tsx | 349 +++++++++++++----- 1 file changed, 263 insertions(+), 86 deletions(-) diff --git a/src/modules/registratura/components/registry-entry-form.tsx b/src/modules/registratura/components/registry-entry-form.tsx index 2ccbdf3..d3faf0b 100644 --- a/src/modules/registratura/components/registry-entry-form.tsx +++ b/src/modules/registratura/components/registry-entry-form.tsx @@ -1,76 +1,130 @@ -'use client'; +"use client"; -import { useState, useMemo, useRef } from 'react'; -import { Paperclip, X, Clock, Plus } from 'lucide-react'; -import type { CompanyId } from '@/core/auth/types'; -import type { RegistryEntry, RegistryDirection, RegistryStatus, DocumentType, RegistryAttachment, TrackedDeadline, DeadlineResolution } from '../types'; -import { Input } from '@/shared/components/ui/input'; -import { Label } from '@/shared/components/ui/label'; -import { Textarea } from '@/shared/components/ui/textarea'; -import { Button } from '@/shared/components/ui/button'; -import { Badge } from '@/shared/components/ui/badge'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select'; -import { useContacts } from '@/modules/address-book/hooks/use-contacts'; -import { v4 as uuid } from 'uuid'; -import { DeadlineCard } from './deadline-card'; -import { DeadlineAddDialog } from './deadline-add-dialog'; -import { DeadlineResolveDialog } from './deadline-resolve-dialog'; -import { createTrackedDeadline, resolveDeadline as resolveDeadlineFn } from '../services/deadline-service'; -import { getDeadlineType } from '../services/deadline-catalog'; +import { useState, useMemo, useRef } from "react"; +import { Paperclip, X, Clock, Plus } from "lucide-react"; +import type { CompanyId } from "@/core/auth/types"; +import type { + RegistryEntry, + RegistryDirection, + RegistryStatus, + DocumentType, + RegistryAttachment, + TrackedDeadline, + DeadlineResolution, +} from "../types"; +import { Input } from "@/shared/components/ui/input"; +import { Label } from "@/shared/components/ui/label"; +import { Textarea } from "@/shared/components/ui/textarea"; +import { Button } from "@/shared/components/ui/button"; +import { Badge } from "@/shared/components/ui/badge"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/shared/components/ui/select"; +import { useContacts } from "@/modules/address-book/hooks/use-contacts"; +import { v4 as uuid } from "uuid"; +import { DeadlineCard } from "./deadline-card"; +import { DeadlineAddDialog } from "./deadline-add-dialog"; +import { DeadlineResolveDialog } from "./deadline-resolve-dialog"; +import { + createTrackedDeadline, + resolveDeadline as resolveDeadlineFn, +} from "../services/deadline-service"; +import { getDeadlineType } from "../services/deadline-catalog"; interface RegistryEntryFormProps { initial?: RegistryEntry; allEntries?: RegistryEntry[]; - onSubmit: (data: Omit) => void; + onSubmit: ( + data: Omit, + ) => void; onCancel: () => void; } const DOC_TYPE_LABELS: Record = { - contract: 'Contract', - oferta: 'Ofertă', - factura: 'Factură', - scrisoare: 'Scrisoare', - aviz: 'Aviz', - 'nota-de-comanda': 'Notă de comandă', - raport: 'Raport', - cerere: 'Cerere', - altele: 'Altele', + contract: "Contract", + oferta: "Ofertă", + factura: "Factură", + scrisoare: "Scrisoare", + aviz: "Aviz", + "nota-de-comanda": "Notă de comandă", + raport: "Raport", + cerere: "Cerere", + altele: "Altele", }; -export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: RegistryEntryFormProps) { +export function RegistryEntryForm({ + initial, + allEntries, + onSubmit, + onCancel, +}: RegistryEntryFormProps) { const { allContacts } = useContacts(); const fileInputRef = useRef(null); - const [direction, setDirection] = useState(initial?.direction ?? 'intrat'); - const [documentType, setDocumentType] = useState(initial?.documentType ?? 'scrisoare'); - const [subject, setSubject] = useState(initial?.subject ?? ''); - const [date, setDate] = useState(initial?.date ?? new Date().toISOString().slice(0, 10)); - const [sender, setSender] = useState(initial?.sender ?? ''); - const [senderContactId, setSenderContactId] = useState(initial?.senderContactId ?? ''); - const [recipient, setRecipient] = useState(initial?.recipient ?? ''); - const [recipientContactId, setRecipientContactId] = useState(initial?.recipientContactId ?? ''); - const [company, setCompany] = useState(initial?.company ?? 'beletage'); - const [status, setStatus] = useState(initial?.status ?? 'deschis'); - const [deadline, setDeadline] = useState(initial?.deadline ?? ''); - const [notes, setNotes] = useState(initial?.notes ?? ''); - const [linkedEntryIds, setLinkedEntryIds] = useState(initial?.linkedEntryIds ?? []); - const [attachments, setAttachments] = useState(initial?.attachments ?? []); - const [trackedDeadlines, setTrackedDeadlines] = useState(initial?.trackedDeadlines ?? []); + const [direction, setDirection] = useState( + initial?.direction ?? "intrat", + ); + const [documentType, setDocumentType] = useState( + initial?.documentType ?? "scrisoare", + ); + const [subject, setSubject] = useState(initial?.subject ?? ""); + const [date, setDate] = useState( + initial?.date ?? new Date().toISOString().slice(0, 10), + ); + const [sender, setSender] = useState(initial?.sender ?? ""); + const [senderContactId, setSenderContactId] = useState( + initial?.senderContactId ?? "", + ); + const [recipient, setRecipient] = useState(initial?.recipient ?? ""); + const [recipientContactId, setRecipientContactId] = useState( + initial?.recipientContactId ?? "", + ); + const [company, setCompany] = useState( + initial?.company ?? "beletage", + ); + const [status, setStatus] = useState( + initial?.status ?? "deschis", + ); + const [deadline, setDeadline] = useState(initial?.deadline ?? ""); + const [notes, setNotes] = useState(initial?.notes ?? ""); + const [linkedEntryIds, setLinkedEntryIds] = useState( + initial?.linkedEntryIds ?? [], + ); + const [attachments, setAttachments] = useState( + initial?.attachments ?? [], + ); + const [trackedDeadlines, setTrackedDeadlines] = useState( + initial?.trackedDeadlines ?? [], + ); + const [linkedSearch, setLinkedSearch] = useState(""); // ── Deadline dialogs ── const [deadlineAddOpen, setDeadlineAddOpen] = useState(false); - const [resolvingDeadline, setResolvingDeadline] = useState(null); + const [resolvingDeadline, setResolvingDeadline] = + useState(null); - const handleAddDeadline = (typeId: string, startDate: string, chainParentId?: string) => { + const handleAddDeadline = ( + typeId: string, + startDate: string, + chainParentId?: string, + ) => { const tracked = createTrackedDeadline(typeId, startDate, chainParentId); if (tracked) setTrackedDeadlines((prev) => [...prev, tracked]); }; - const handleResolveDeadline = (resolution: DeadlineResolution, note: string, chainNext: boolean) => { + const handleResolveDeadline = ( + resolution: DeadlineResolution, + note: string, + chainNext: boolean, + ) => { if (!resolvingDeadline) return; const resolved = resolveDeadlineFn(resolvingDeadline, resolution, note); setTrackedDeadlines((prev) => - prev.map((d) => (d.id === resolved.id ? resolved : d)) + prev.map((d) => (d.id === resolved.id ? resolved : d)), ); // Handle chain @@ -78,7 +132,11 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R const def = getDeadlineType(resolvingDeadline.typeId); if (def?.chainNextTypeId) { const resolvedDate = new Date().toISOString().slice(0, 10); - const chained = createTrackedDeadline(def.chainNextTypeId, resolvedDate, resolvingDeadline.id); + const chained = createTrackedDeadline( + def.chainNextTypeId, + resolvedDate, + resolvingDeadline.id, + ); if (chained) setTrackedDeadlines((prev) => [...prev, chained]); } } @@ -96,13 +154,25 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R const senderSuggestions = useMemo(() => { if (!sender || sender.length < 2) return []; const q = sender.toLowerCase(); - return allContacts.filter((c) => c.name.toLowerCase().includes(q) || c.company.toLowerCase().includes(q)).slice(0, 5); + return allContacts + .filter( + (c) => + c.name.toLowerCase().includes(q) || + c.company.toLowerCase().includes(q), + ) + .slice(0, 5); }, [allContacts, sender]); const recipientSuggestions = useMemo(() => { if (!recipient || recipient.length < 2) return []; const q = recipient.toLowerCase(); - return allContacts.filter((c) => c.name.toLowerCase().includes(q) || c.company.toLowerCase().includes(q)).slice(0, 5); + return allContacts + .filter( + (c) => + c.name.toLowerCase().includes(q) || + c.company.toLowerCase().includes(q), + ) + .slice(0, 5); }, [allContacts, recipient]); const handleFileUpload = (e: React.ChangeEvent) => { @@ -126,7 +196,7 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R }; reader.readAsDataURL(file); } - if (fileInputRef.current) fileInputRef.current.value = ''; + if (fileInputRef.current) fileInputRef.current.value = ""; }; const removeAttachment = (id: string) => { @@ -149,10 +219,11 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R deadline: deadline || undefined, linkedEntryIds, attachments, - trackedDeadlines: trackedDeadlines.length > 0 ? trackedDeadlines : undefined, + trackedDeadlines: + trackedDeadlines.length > 0 ? trackedDeadlines : undefined, notes, tags: initial?.tags ?? [], - visibility: initial?.visibility ?? 'all', + visibility: initial?.visibility ?? "all", }); }; @@ -162,8 +233,13 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
- setDirection(v as RegistryDirection)} + > + + + Intrat Ieșit @@ -172,25 +248,44 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
- setDocumentType(v as DocumentType)} + > + + + - {(Object.entries(DOC_TYPE_LABELS) as [DocumentType, string][]).map(([key, label]) => ( - {label} + {( + Object.entries(DOC_TYPE_LABELS) as [DocumentType, string][] + ).map(([key, label]) => ( + + {label} + ))}
- setDate(e.target.value)} className="mt-1" /> + setDate(e.target.value)} + className="mt-1" + />
{/* Subject */}
- setSubject(e.target.value)} className="mt-1" required /> + setSubject(e.target.value)} + className="mt-1" + required + />
{/* Sender / Recipient with autocomplete */} @@ -199,7 +294,10 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R { setSender(e.target.value); setSenderContactId(''); }} + onChange={(e) => { + setSender(e.target.value); + setSenderContactId(""); + }} onFocus={() => setSenderFocused(true)} onBlur={() => setTimeout(() => setSenderFocused(false), 200)} className="mt-1" @@ -219,7 +317,11 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R }} > {c.name} - {c.company && {c.company}} + {c.company && ( + + {c.company} + + )} ))} @@ -229,7 +331,10 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R { setRecipient(e.target.value); setRecipientContactId(''); }} + onChange={(e) => { + setRecipient(e.target.value); + setRecipientContactId(""); + }} onFocus={() => setRecipientFocused(true)} onBlur={() => setTimeout(() => setRecipientFocused(false), 200)} className="mt-1" @@ -243,13 +348,19 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R type="button" className="w-full rounded px-2 py-1.5 text-left text-sm hover:bg-accent" onMouseDown={() => { - setRecipient(c.company ? `${c.name} (${c.company})` : c.name); + setRecipient( + c.company ? `${c.name} (${c.company})` : c.name, + ); setRecipientContactId(c.id); setRecipientFocused(false); }} > {c.name} - {c.company && {c.company}} + {c.company && ( + + {c.company} + + )} ))} @@ -261,8 +372,13 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
- setCompany(v as CompanyId)} + > + + + Beletage Urban Switch @@ -273,8 +389,13 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
- setStatus(v as RegistryStatus)} + > + + + Deschis Închis @@ -283,7 +404,12 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
- setDeadline(e.target.value)} className="mt-1" /> + setDeadline(e.target.value)} + className="mt-1" + />
@@ -291,26 +417,50 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R {allEntries && allEntries.length > 0 && (
+ setLinkedSearch(e.target.value)} + />
{allEntries - .filter((e) => e.id !== initial?.id) - .slice(0, 20) + .filter((e) => { + if (e.id === initial?.id) return false; + if (!linkedSearch.trim()) return true; + const q = linkedSearch.toLowerCase(); + return ( + e.number.toLowerCase().includes(q) || + e.subject.toLowerCase().includes(q) || + (e.sender ?? "").toLowerCase().includes(q) + ); + }) .map((e) => ( ))}
@@ -324,7 +474,12 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R Termene legale -
@@ -342,7 +497,8 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R )} {trackedDeadlines.length === 0 && (

- Niciun termen legal. Apăsați "Adaugă termen" pentru a urmări un termen. + Niciun termen legal. Apăsați "Adaugă termen" pentru a + urmări un termen.

)} @@ -357,7 +513,9 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R { if (!open) setResolvingDeadline(null); }} + onOpenChange={(open) => { + if (!open) setResolvingDeadline(null); + }} onResolve={handleResolveDeadline} /> @@ -365,7 +523,12 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
- 0 && (
{attachments.map((att) => ( -
+
{att.name} {(att.size / 1024).toFixed(0)} KB -
@@ -398,12 +568,19 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R {/* Notes */}
-