feat(registratura): linked-entry search filter, remove 20-item cap
This commit is contained in:
@@ -1,76 +1,130 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import { useState, useMemo, useRef } from 'react';
|
import { useState, useMemo, useRef } from "react";
|
||||||
import { Paperclip, X, Clock, Plus } from 'lucide-react';
|
import { Paperclip, X, Clock, Plus } from "lucide-react";
|
||||||
import type { CompanyId } from '@/core/auth/types';
|
import type { CompanyId } from "@/core/auth/types";
|
||||||
import type { RegistryEntry, RegistryDirection, RegistryStatus, DocumentType, RegistryAttachment, TrackedDeadline, DeadlineResolution } from '../types';
|
import type {
|
||||||
import { Input } from '@/shared/components/ui/input';
|
RegistryEntry,
|
||||||
import { Label } from '@/shared/components/ui/label';
|
RegistryDirection,
|
||||||
import { Textarea } from '@/shared/components/ui/textarea';
|
RegistryStatus,
|
||||||
import { Button } from '@/shared/components/ui/button';
|
DocumentType,
|
||||||
import { Badge } from '@/shared/components/ui/badge';
|
RegistryAttachment,
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
|
TrackedDeadline,
|
||||||
import { useContacts } from '@/modules/address-book/hooks/use-contacts';
|
DeadlineResolution,
|
||||||
import { v4 as uuid } from 'uuid';
|
} from "../types";
|
||||||
import { DeadlineCard } from './deadline-card';
|
import { Input } from "@/shared/components/ui/input";
|
||||||
import { DeadlineAddDialog } from './deadline-add-dialog';
|
import { Label } from "@/shared/components/ui/label";
|
||||||
import { DeadlineResolveDialog } from './deadline-resolve-dialog';
|
import { Textarea } from "@/shared/components/ui/textarea";
|
||||||
import { createTrackedDeadline, resolveDeadline as resolveDeadlineFn } from '../services/deadline-service';
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import { getDeadlineType } from '../services/deadline-catalog';
|
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 {
|
interface RegistryEntryFormProps {
|
||||||
initial?: RegistryEntry;
|
initial?: RegistryEntry;
|
||||||
allEntries?: RegistryEntry[];
|
allEntries?: RegistryEntry[];
|
||||||
onSubmit: (data: Omit<RegistryEntry, 'id' | 'number' | 'createdAt' | 'updatedAt'>) => void;
|
onSubmit: (
|
||||||
|
data: Omit<RegistryEntry, "id" | "number" | "createdAt" | "updatedAt">,
|
||||||
|
) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DOC_TYPE_LABELS: Record<DocumentType, string> = {
|
const DOC_TYPE_LABELS: Record<DocumentType, string> = {
|
||||||
contract: 'Contract',
|
contract: "Contract",
|
||||||
oferta: 'Ofertă',
|
oferta: "Ofertă",
|
||||||
factura: 'Factură',
|
factura: "Factură",
|
||||||
scrisoare: 'Scrisoare',
|
scrisoare: "Scrisoare",
|
||||||
aviz: 'Aviz',
|
aviz: "Aviz",
|
||||||
'nota-de-comanda': 'Notă de comandă',
|
"nota-de-comanda": "Notă de comandă",
|
||||||
raport: 'Raport',
|
raport: "Raport",
|
||||||
cerere: 'Cerere',
|
cerere: "Cerere",
|
||||||
altele: 'Altele',
|
altele: "Altele",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: RegistryEntryFormProps) {
|
export function RegistryEntryForm({
|
||||||
|
initial,
|
||||||
|
allEntries,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
}: RegistryEntryFormProps) {
|
||||||
const { allContacts } = useContacts();
|
const { allContacts } = useContacts();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const [direction, setDirection] = useState<RegistryDirection>(initial?.direction ?? 'intrat');
|
const [direction, setDirection] = useState<RegistryDirection>(
|
||||||
const [documentType, setDocumentType] = useState<DocumentType>(initial?.documentType ?? 'scrisoare');
|
initial?.direction ?? "intrat",
|
||||||
const [subject, setSubject] = useState(initial?.subject ?? '');
|
);
|
||||||
const [date, setDate] = useState(initial?.date ?? new Date().toISOString().slice(0, 10));
|
const [documentType, setDocumentType] = useState<DocumentType>(
|
||||||
const [sender, setSender] = useState(initial?.sender ?? '');
|
initial?.documentType ?? "scrisoare",
|
||||||
const [senderContactId, setSenderContactId] = useState(initial?.senderContactId ?? '');
|
);
|
||||||
const [recipient, setRecipient] = useState(initial?.recipient ?? '');
|
const [subject, setSubject] = useState(initial?.subject ?? "");
|
||||||
const [recipientContactId, setRecipientContactId] = useState(initial?.recipientContactId ?? '');
|
const [date, setDate] = useState(
|
||||||
const [company, setCompany] = useState<CompanyId>(initial?.company ?? 'beletage');
|
initial?.date ?? new Date().toISOString().slice(0, 10),
|
||||||
const [status, setStatus] = useState<RegistryStatus>(initial?.status ?? 'deschis');
|
);
|
||||||
const [deadline, setDeadline] = useState(initial?.deadline ?? '');
|
const [sender, setSender] = useState(initial?.sender ?? "");
|
||||||
const [notes, setNotes] = useState(initial?.notes ?? '');
|
const [senderContactId, setSenderContactId] = useState(
|
||||||
const [linkedEntryIds, setLinkedEntryIds] = useState<string[]>(initial?.linkedEntryIds ?? []);
|
initial?.senderContactId ?? "",
|
||||||
const [attachments, setAttachments] = useState<RegistryAttachment[]>(initial?.attachments ?? []);
|
);
|
||||||
const [trackedDeadlines, setTrackedDeadlines] = useState<TrackedDeadline[]>(initial?.trackedDeadlines ?? []);
|
const [recipient, setRecipient] = useState(initial?.recipient ?? "");
|
||||||
|
const [recipientContactId, setRecipientContactId] = useState(
|
||||||
|
initial?.recipientContactId ?? "",
|
||||||
|
);
|
||||||
|
const [company, setCompany] = useState<CompanyId>(
|
||||||
|
initial?.company ?? "beletage",
|
||||||
|
);
|
||||||
|
const [status, setStatus] = useState<RegistryStatus>(
|
||||||
|
initial?.status ?? "deschis",
|
||||||
|
);
|
||||||
|
const [deadline, setDeadline] = useState(initial?.deadline ?? "");
|
||||||
|
const [notes, setNotes] = useState(initial?.notes ?? "");
|
||||||
|
const [linkedEntryIds, setLinkedEntryIds] = useState<string[]>(
|
||||||
|
initial?.linkedEntryIds ?? [],
|
||||||
|
);
|
||||||
|
const [attachments, setAttachments] = useState<RegistryAttachment[]>(
|
||||||
|
initial?.attachments ?? [],
|
||||||
|
);
|
||||||
|
const [trackedDeadlines, setTrackedDeadlines] = useState<TrackedDeadline[]>(
|
||||||
|
initial?.trackedDeadlines ?? [],
|
||||||
|
);
|
||||||
|
const [linkedSearch, setLinkedSearch] = useState("");
|
||||||
|
|
||||||
// ── Deadline dialogs ──
|
// ── Deadline dialogs ──
|
||||||
const [deadlineAddOpen, setDeadlineAddOpen] = useState(false);
|
const [deadlineAddOpen, setDeadlineAddOpen] = useState(false);
|
||||||
const [resolvingDeadline, setResolvingDeadline] = useState<TrackedDeadline | null>(null);
|
const [resolvingDeadline, setResolvingDeadline] =
|
||||||
|
useState<TrackedDeadline | null>(null);
|
||||||
|
|
||||||
const handleAddDeadline = (typeId: string, startDate: string, chainParentId?: string) => {
|
const handleAddDeadline = (
|
||||||
|
typeId: string,
|
||||||
|
startDate: string,
|
||||||
|
chainParentId?: string,
|
||||||
|
) => {
|
||||||
const tracked = createTrackedDeadline(typeId, startDate, chainParentId);
|
const tracked = createTrackedDeadline(typeId, startDate, chainParentId);
|
||||||
if (tracked) setTrackedDeadlines((prev) => [...prev, tracked]);
|
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;
|
if (!resolvingDeadline) return;
|
||||||
const resolved = resolveDeadlineFn(resolvingDeadline, resolution, note);
|
const resolved = resolveDeadlineFn(resolvingDeadline, resolution, note);
|
||||||
setTrackedDeadlines((prev) =>
|
setTrackedDeadlines((prev) =>
|
||||||
prev.map((d) => (d.id === resolved.id ? resolved : d))
|
prev.map((d) => (d.id === resolved.id ? resolved : d)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle chain
|
// Handle chain
|
||||||
@@ -78,7 +132,11 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
const def = getDeadlineType(resolvingDeadline.typeId);
|
const def = getDeadlineType(resolvingDeadline.typeId);
|
||||||
if (def?.chainNextTypeId) {
|
if (def?.chainNextTypeId) {
|
||||||
const resolvedDate = new Date().toISOString().slice(0, 10);
|
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]);
|
if (chained) setTrackedDeadlines((prev) => [...prev, chained]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,13 +154,25 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
const senderSuggestions = useMemo(() => {
|
const senderSuggestions = useMemo(() => {
|
||||||
if (!sender || sender.length < 2) return [];
|
if (!sender || sender.length < 2) return [];
|
||||||
const q = sender.toLowerCase();
|
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]);
|
}, [allContacts, sender]);
|
||||||
|
|
||||||
const recipientSuggestions = useMemo(() => {
|
const recipientSuggestions = useMemo(() => {
|
||||||
if (!recipient || recipient.length < 2) return [];
|
if (!recipient || recipient.length < 2) return [];
|
||||||
const q = recipient.toLowerCase();
|
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]);
|
}, [allContacts, recipient]);
|
||||||
|
|
||||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -126,7 +196,7 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
if (fileInputRef.current) fileInputRef.current.value = '';
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeAttachment = (id: string) => {
|
const removeAttachment = (id: string) => {
|
||||||
@@ -149,10 +219,11 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
deadline: deadline || undefined,
|
deadline: deadline || undefined,
|
||||||
linkedEntryIds,
|
linkedEntryIds,
|
||||||
attachments,
|
attachments,
|
||||||
trackedDeadlines: trackedDeadlines.length > 0 ? trackedDeadlines : undefined,
|
trackedDeadlines:
|
||||||
|
trackedDeadlines.length > 0 ? trackedDeadlines : undefined,
|
||||||
notes,
|
notes,
|
||||||
tags: initial?.tags ?? [],
|
tags: initial?.tags ?? [],
|
||||||
visibility: initial?.visibility ?? 'all',
|
visibility: initial?.visibility ?? "all",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -162,8 +233,13 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<div className="grid gap-4 sm:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>Direcție</Label>
|
<Label>Direcție</Label>
|
||||||
<Select value={direction} onValueChange={(v) => setDirection(v as RegistryDirection)}>
|
<Select
|
||||||
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
|
value={direction}
|
||||||
|
onValueChange={(v) => setDirection(v as RegistryDirection)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="intrat">Intrat</SelectItem>
|
<SelectItem value="intrat">Intrat</SelectItem>
|
||||||
<SelectItem value="iesit">Ieșit</SelectItem>
|
<SelectItem value="iesit">Ieșit</SelectItem>
|
||||||
@@ -172,25 +248,44 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Tip document</Label>
|
<Label>Tip document</Label>
|
||||||
<Select value={documentType} onValueChange={(v) => setDocumentType(v as DocumentType)}>
|
<Select
|
||||||
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
|
value={documentType}
|
||||||
|
onValueChange={(v) => setDocumentType(v as DocumentType)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{(Object.entries(DOC_TYPE_LABELS) as [DocumentType, string][]).map(([key, label]) => (
|
{(
|
||||||
<SelectItem key={key} value={key}>{label}</SelectItem>
|
Object.entries(DOC_TYPE_LABELS) as [DocumentType, string][]
|
||||||
|
).map(([key, label]) => (
|
||||||
|
<SelectItem key={key} value={key}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Data</Label>
|
<Label>Data</Label>
|
||||||
<Input type="date" value={date} onChange={(e) => setDate(e.target.value)} className="mt-1" />
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={date}
|
||||||
|
onChange={(e) => setDate(e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Subject */}
|
{/* Subject */}
|
||||||
<div>
|
<div>
|
||||||
<Label>Subiect *</Label>
|
<Label>Subiect *</Label>
|
||||||
<Input value={subject} onChange={(e) => setSubject(e.target.value)} className="mt-1" required />
|
<Input
|
||||||
|
value={subject}
|
||||||
|
onChange={(e) => setSubject(e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sender / Recipient with autocomplete */}
|
{/* Sender / Recipient with autocomplete */}
|
||||||
@@ -199,7 +294,10 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<Label>Expeditor</Label>
|
<Label>Expeditor</Label>
|
||||||
<Input
|
<Input
|
||||||
value={sender}
|
value={sender}
|
||||||
onChange={(e) => { setSender(e.target.value); setSenderContactId(''); }}
|
onChange={(e) => {
|
||||||
|
setSender(e.target.value);
|
||||||
|
setSenderContactId("");
|
||||||
|
}}
|
||||||
onFocus={() => setSenderFocused(true)}
|
onFocus={() => setSenderFocused(true)}
|
||||||
onBlur={() => setTimeout(() => setSenderFocused(false), 200)}
|
onBlur={() => setTimeout(() => setSenderFocused(false), 200)}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
@@ -219,7 +317,11 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="font-medium">{c.name}</span>
|
<span className="font-medium">{c.name}</span>
|
||||||
{c.company && <span className="ml-1 text-muted-foreground text-xs">{c.company}</span>}
|
{c.company && (
|
||||||
|
<span className="ml-1 text-muted-foreground text-xs">
|
||||||
|
{c.company}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -229,7 +331,10 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<Label>Destinatar</Label>
|
<Label>Destinatar</Label>
|
||||||
<Input
|
<Input
|
||||||
value={recipient}
|
value={recipient}
|
||||||
onChange={(e) => { setRecipient(e.target.value); setRecipientContactId(''); }}
|
onChange={(e) => {
|
||||||
|
setRecipient(e.target.value);
|
||||||
|
setRecipientContactId("");
|
||||||
|
}}
|
||||||
onFocus={() => setRecipientFocused(true)}
|
onFocus={() => setRecipientFocused(true)}
|
||||||
onBlur={() => setTimeout(() => setRecipientFocused(false), 200)}
|
onBlur={() => setTimeout(() => setRecipientFocused(false), 200)}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
@@ -243,13 +348,19 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
type="button"
|
type="button"
|
||||||
className="w-full rounded px-2 py-1.5 text-left text-sm hover:bg-accent"
|
className="w-full rounded px-2 py-1.5 text-left text-sm hover:bg-accent"
|
||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
setRecipient(c.company ? `${c.name} (${c.company})` : c.name);
|
setRecipient(
|
||||||
|
c.company ? `${c.name} (${c.company})` : c.name,
|
||||||
|
);
|
||||||
setRecipientContactId(c.id);
|
setRecipientContactId(c.id);
|
||||||
setRecipientFocused(false);
|
setRecipientFocused(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="font-medium">{c.name}</span>
|
<span className="font-medium">{c.name}</span>
|
||||||
{c.company && <span className="ml-1 text-muted-foreground text-xs">{c.company}</span>}
|
{c.company && (
|
||||||
|
<span className="ml-1 text-muted-foreground text-xs">
|
||||||
|
{c.company}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -261,8 +372,13 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<div className="grid gap-4 sm:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>Companie</Label>
|
<Label>Companie</Label>
|
||||||
<Select value={company} onValueChange={(v) => setCompany(v as CompanyId)}>
|
<Select
|
||||||
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
|
value={company}
|
||||||
|
onValueChange={(v) => setCompany(v as CompanyId)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="beletage">Beletage</SelectItem>
|
<SelectItem value="beletage">Beletage</SelectItem>
|
||||||
<SelectItem value="urban-switch">Urban Switch</SelectItem>
|
<SelectItem value="urban-switch">Urban Switch</SelectItem>
|
||||||
@@ -273,8 +389,13 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Status</Label>
|
<Label>Status</Label>
|
||||||
<Select value={status} onValueChange={(v) => setStatus(v as RegistryStatus)}>
|
<Select
|
||||||
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
|
value={status}
|
||||||
|
onValueChange={(v) => setStatus(v as RegistryStatus)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="deschis">Deschis</SelectItem>
|
<SelectItem value="deschis">Deschis</SelectItem>
|
||||||
<SelectItem value="inchis">Închis</SelectItem>
|
<SelectItem value="inchis">Închis</SelectItem>
|
||||||
@@ -283,7 +404,12 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Termen limită</Label>
|
<Label>Termen limită</Label>
|
||||||
<Input type="date" value={deadline} onChange={(e) => setDeadline(e.target.value)} className="mt-1" />
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={deadline}
|
||||||
|
onChange={(e) => setDeadline(e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -291,26 +417,50 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
{allEntries && allEntries.length > 0 && (
|
{allEntries && allEntries.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<Label>Înregistrări legate</Label>
|
<Label>Înregistrări legate</Label>
|
||||||
|
<Input
|
||||||
|
className="mt-1.5"
|
||||||
|
placeholder="Caută după număr, subiect sau expeditor…"
|
||||||
|
value={linkedSearch}
|
||||||
|
onChange={(e) => setLinkedSearch(e.target.value)}
|
||||||
|
/>
|
||||||
<div className="mt-1.5 flex flex-wrap gap-1.5">
|
<div className="mt-1.5 flex flex-wrap gap-1.5">
|
||||||
{allEntries
|
{allEntries
|
||||||
.filter((e) => e.id !== initial?.id)
|
.filter((e) => {
|
||||||
.slice(0, 20)
|
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) => (
|
.map((e) => (
|
||||||
<button
|
<button
|
||||||
key={e.id}
|
key={e.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLinkedEntryIds((prev) =>
|
setLinkedEntryIds((prev) =>
|
||||||
prev.includes(e.id) ? prev.filter((id) => id !== e.id) : [...prev, e.id]
|
prev.includes(e.id)
|
||||||
|
? prev.filter((id) => id !== e.id)
|
||||||
|
: [...prev, e.id],
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className={`rounded border px-2 py-0.5 text-xs transition-colors ${
|
className={`rounded border px-2 py-0.5 text-xs transition-colors ${
|
||||||
linkedEntryIds.includes(e.id)
|
linkedEntryIds.includes(e.id)
|
||||||
? 'border-primary bg-primary/10 text-primary'
|
? "border-primary bg-primary/10 text-primary"
|
||||||
: 'border-muted-foreground/30 text-muted-foreground hover:border-primary/50'
|
: "border-muted-foreground/30 text-muted-foreground hover:border-primary/50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{e.number}
|
{e.number}
|
||||||
|
{e.subject && (
|
||||||
|
<span className="ml-1 opacity-60">
|
||||||
|
·{" "}
|
||||||
|
{e.subject.length > 30
|
||||||
|
? e.subject.slice(0, 30) + "…"
|
||||||
|
: e.subject}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -324,7 +474,12 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<Clock className="h-3.5 w-3.5" />
|
<Clock className="h-3.5 w-3.5" />
|
||||||
Termene legale
|
Termene legale
|
||||||
</Label>
|
</Label>
|
||||||
<Button type="button" variant="outline" size="sm" onClick={() => setDeadlineAddOpen(true)}>
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setDeadlineAddOpen(true)}
|
||||||
|
>
|
||||||
<Plus className="mr-1 h-3.5 w-3.5" /> Adaugă termen
|
<Plus className="mr-1 h-3.5 w-3.5" /> Adaugă termen
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -342,7 +497,8 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
)}
|
)}
|
||||||
{trackedDeadlines.length === 0 && (
|
{trackedDeadlines.length === 0 && (
|
||||||
<p className="mt-2 text-xs text-muted-foreground">
|
<p className="mt-2 text-xs text-muted-foreground">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -357,7 +513,9 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<DeadlineResolveDialog
|
<DeadlineResolveDialog
|
||||||
open={resolvingDeadline !== null}
|
open={resolvingDeadline !== null}
|
||||||
deadline={resolvingDeadline}
|
deadline={resolvingDeadline}
|
||||||
onOpenChange={(open) => { if (!open) setResolvingDeadline(null); }}
|
onOpenChange={(open) => {
|
||||||
|
if (!open) setResolvingDeadline(null);
|
||||||
|
}}
|
||||||
onResolve={handleResolveDeadline}
|
onResolve={handleResolveDeadline}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -365,7 +523,12 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label>Atașamente</Label>
|
<Label>Atașamente</Label>
|
||||||
<Button type="button" variant="outline" size="sm" onClick={() => fileInputRef.current?.click()}>
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
<Paperclip className="mr-1 h-3.5 w-3.5" /> Adaugă fișier
|
<Paperclip className="mr-1 h-3.5 w-3.5" /> Adaugă fișier
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
@@ -380,13 +543,20 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
{attachments.length > 0 && (
|
{attachments.length > 0 && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
{attachments.map((att) => (
|
{attachments.map((att) => (
|
||||||
<div key={att.id} className="flex items-center gap-2 rounded border px-2 py-1 text-sm">
|
<div
|
||||||
|
key={att.id}
|
||||||
|
className="flex items-center gap-2 rounded border px-2 py-1 text-sm"
|
||||||
|
>
|
||||||
<Paperclip className="h-3 w-3 text-muted-foreground" />
|
<Paperclip className="h-3 w-3 text-muted-foreground" />
|
||||||
<span className="flex-1 truncate">{att.name}</span>
|
<span className="flex-1 truncate">{att.name}</span>
|
||||||
<Badge variant="outline" className="text-[10px]">
|
<Badge variant="outline" className="text-[10px]">
|
||||||
{(att.size / 1024).toFixed(0)} KB
|
{(att.size / 1024).toFixed(0)} KB
|
||||||
</Badge>
|
</Badge>
|
||||||
<button type="button" onClick={() => removeAttachment(att.id)} className="text-destructive">
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeAttachment(att.id)}
|
||||||
|
className="text-destructive"
|
||||||
|
>
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -398,12 +568,19 @@ export function RegistryEntryForm({ initial, allEntries, onSubmit, onCancel }: R
|
|||||||
{/* Notes */}
|
{/* Notes */}
|
||||||
<div>
|
<div>
|
||||||
<Label>Note</Label>
|
<Label>Note</Label>
|
||||||
<Textarea value={notes} onChange={(e) => setNotes(e.target.value)} rows={3} className="mt-1" />
|
<Textarea
|
||||||
|
value={notes}
|
||||||
|
onChange={(e) => setNotes(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-2">
|
<div className="flex justify-end gap-2 pt-2">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>Anulează</Button>
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
<Button type="submit">{initial ? 'Actualizează' : 'Adaugă'}</Button>
|
Anulează
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">{initial ? "Actualizează" : "Adaugă"}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user