3.03 Registratura Termene Legale recipient registration, audit log, expiry tracking
- Added recipientRegNumber/recipientRegDate fields for outgoing docs (deadline triggers from recipient registration date) - Added prelungire-CU deadline type in catalog (15 calendar days, tacit approval) - CU category already first in catalog verified - DeadlineAuditEntry interface + audit log on TrackedDeadline (created/resolved entries) - Document expiry tracking: expiryDate + expiryAlertDays with live countdown - Web scraping prep fields: externalStatusUrl + externalTrackingId - Dashboard: 6 stat cards (added missing recipient + expiring soon) - Alert banners for missing recipient data and expiring documents - Version bump to 0.2.0
This commit is contained in:
@@ -10,6 +10,9 @@ import {
|
||||
Info,
|
||||
GitBranch,
|
||||
Loader2,
|
||||
AlertTriangle,
|
||||
Calendar,
|
||||
Globe,
|
||||
} from "lucide-react";
|
||||
import type { CompanyId } from "@/core/auth/types";
|
||||
import type {
|
||||
@@ -149,6 +152,24 @@ export function RegistryEntryForm({
|
||||
const [linkedSearch, setLinkedSearch] = useState("");
|
||||
const [threadSearch, setThreadSearch] = useState("");
|
||||
|
||||
// ── 3.03 new fields ──
|
||||
const [recipientRegNumber, setRecipientRegNumber] = useState(
|
||||
initial?.recipientRegNumber ?? "",
|
||||
);
|
||||
const [recipientRegDate, setRecipientRegDate] = useState(
|
||||
initial?.recipientRegDate ?? "",
|
||||
);
|
||||
const [expiryDate, setExpiryDate] = useState(initial?.expiryDate ?? "");
|
||||
const [expiryAlertDays, setExpiryAlertDays] = useState(
|
||||
initial?.expiryAlertDays ?? 30,
|
||||
);
|
||||
const [externalStatusUrl, setExternalStatusUrl] = useState(
|
||||
initial?.externalStatusUrl ?? "",
|
||||
);
|
||||
const [externalTrackingId, setExternalTrackingId] = useState(
|
||||
initial?.externalTrackingId ?? "",
|
||||
);
|
||||
|
||||
// ── Submission lock + file upload tracking ──
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [uploadingCount, setUploadingCount] = useState(0);
|
||||
@@ -340,6 +361,12 @@ export function RegistryEntryForm({
|
||||
assignee: assignee || undefined,
|
||||
assigneeContactId: assigneeContactId || undefined,
|
||||
threadParentId: threadParentId || undefined,
|
||||
recipientRegNumber: recipientRegNumber || undefined,
|
||||
recipientRegDate: recipientRegDate || undefined,
|
||||
expiryDate: expiryDate || undefined,
|
||||
expiryAlertDays: expiryDate ? expiryAlertDays : undefined,
|
||||
externalStatusUrl: externalStatusUrl || undefined,
|
||||
externalTrackingId: externalTrackingId || undefined,
|
||||
linkedEntryIds,
|
||||
attachments,
|
||||
trackedDeadlines:
|
||||
@@ -595,7 +622,59 @@ export function RegistryEntryForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Assignee (Responsabil) */}
|
||||
{/* Recipient registration fields — only for outgoing (iesit) documents */}
|
||||
{direction === "iesit" && (
|
||||
<div className="rounded-md border border-blue-500/30 bg-blue-500/5 p-3 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||||
<Label className="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
Înregistrare la destinatar
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p className="text-xs">
|
||||
Termenul legal pentru ieșiri curge DOAR de la data
|
||||
înregistrării la destinatar, nu de la data trimiterii.
|
||||
Completează aceste câmpuri când primești confirmarea.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<Label className="text-xs">Nr. înregistrare destinatar</Label>
|
||||
<Input
|
||||
value={recipientRegNumber}
|
||||
onChange={(e) => setRecipientRegNumber(e.target.value)}
|
||||
className="mt-1"
|
||||
placeholder="Ex: 12345/2026"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">Data înregistrare destinatar</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={recipientRegDate}
|
||||
onChange={(e) => setRecipientRegDate(e.target.value)}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!recipientRegNumber && !recipientRegDate && (
|
||||
<p className="text-[10px] text-amber-600 dark:text-amber-400">
|
||||
Atenție: Datele de la destinatar nu sunt completate. Termenele
|
||||
legale atașate nu vor porni până la completarea lor.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Assignee (Responsabil) — kept below */}
|
||||
<div className="relative">
|
||||
<Label className="flex items-center gap-1.5">
|
||||
Responsabil
|
||||
@@ -707,6 +786,104 @@ export function RegistryEntryForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Document Expiry — CU/AC validity tracking */}
|
||||
<div className="rounded-md border border-muted p-3 space-y-3">
|
||||
<Label className="flex items-center gap-1.5 text-sm font-medium">
|
||||
<Calendar className="h-3.5 w-3.5" />
|
||||
Valabilitate document
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p className="text-xs">
|
||||
Pentru documente cu termen de valabilitate (CU, AC etc.).
|
||||
Sistemul va genera alerte înainte de expirare.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Label>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<Label className="text-xs">Data expirare</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={expiryDate}
|
||||
onChange={(e) => setExpiryDate(e.target.value)}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
{expiryDate && (
|
||||
<div>
|
||||
<Label className="text-xs">Alertă cu X zile înainte</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={expiryAlertDays}
|
||||
onChange={(e) =>
|
||||
setExpiryAlertDays(parseInt(e.target.value, 10) || 30)
|
||||
}
|
||||
className="mt-1"
|
||||
min={1}
|
||||
max={365}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{expiryDate &&
|
||||
(() => {
|
||||
const expiry = new Date(expiryDate);
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
expiry.setHours(0, 0, 0, 0);
|
||||
const daysLeft = Math.ceil(
|
||||
(expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
|
||||
);
|
||||
if (daysLeft < 0) {
|
||||
return (
|
||||
<p className="text-[10px] text-red-600 dark:text-red-400 font-medium">
|
||||
Document expirat de {Math.abs(daysLeft)} zile!
|
||||
</p>
|
||||
);
|
||||
}
|
||||
if (daysLeft <= expiryAlertDays) {
|
||||
return (
|
||||
<p className="text-[10px] text-amber-600 dark:text-amber-400">
|
||||
Expiră în {daysLeft} zile — inițiați procedurile de
|
||||
prelungire.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* Web scraping prep — external tracking */}
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<Label className="flex items-center gap-1.5 text-xs">
|
||||
<Globe className="h-3 w-3" />
|
||||
URL verificare status (opțional)
|
||||
</Label>
|
||||
<Input
|
||||
value={externalStatusUrl}
|
||||
onChange={(e) => setExternalStatusUrl(e.target.value)}
|
||||
className="mt-1 text-xs"
|
||||
placeholder="https://portal.primaria.ro/..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">ID urmărire extern (opțional)</Label>
|
||||
<Input
|
||||
value={externalTrackingId}
|
||||
onChange={(e) => setExternalTrackingId(e.target.value)}
|
||||
className="mt-1 text-xs"
|
||||
placeholder="Ex: REF-2026-001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Thread parent — reply to another entry */}
|
||||
{allEntries && allEntries.length > 0 && (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user