feat: Registratura thread explorer, AC validity tracker, interactive I/O toggle + Password Vault rework

Registratura improvements:
- Thread Explorer: new 'Fire conversatie' tab with timeline view, search, stats, gap tracking (la noi/la institutie), export to text report
- Interactive I/O toggle: replaced direction dropdown with visual blue/orange button group (Intrat/Iesit with icons)
- Doc type UX: alphabetical sort + immediate selection after adding custom type
- AC Validity Tracker: full Autorizatie de Construire lifecycle workflow (12mo validity, execution phases, extension request, required docs checklist, monthly reminders, abandonment/expiry tracking)

Password Vault rework (renamed to 'Parole Uzuale' v0.3.0):
- New categories: WiFi, Portale Primarii, Avize Online, PIN Semnatura, Software, Hardware (replaced server/database/api)
- Category icons (lucide-react) throughout list and form
- WiFi QR code dialog with connection string copy
- Context-aware form (PIN vs password label, hide email for WiFi/PIN, hide URL for WiFi, hide generator for PIN)
- Dynamic stat cards showing top 3 categories by count
- Removed encryption banner
- Updated i18n, flags, config
This commit is contained in:
AI Assistant
2026-02-28 16:33:36 +02:00
parent 25338ea4d8
commit 3abf0d189c
13 changed files with 2575 additions and 830 deletions
@@ -13,6 +13,8 @@ import {
AlertTriangle,
Calendar,
Globe,
ArrowDownToLine,
ArrowUpFromLine,
} from "lucide-react";
import type { CompanyId } from "@/core/auth/types";
import type {
@@ -22,6 +24,7 @@ import type {
RegistryAttachment,
TrackedDeadline,
DeadlineResolution,
ACValidityTracking,
} from "../types";
import { DEFAULT_DOC_TYPE_LABELS } from "../types";
import { Input } from "@/shared/components/ui/input";
@@ -53,6 +56,8 @@ import { DeadlineResolveDialog } from "./deadline-resolve-dialog";
import { QuickContactDialog } from "./quick-contact-dialog";
import { ThreadView } from "./thread-view";
import { ClosureBanner } from "./closure-banner";
import { ACValidityTracker } from "./ac-validity-tracker";
import { cn } from "@/shared/lib/utils";
import {
createTrackedDeadline,
resolveDeadline as resolveDeadlineFn,
@@ -91,7 +96,12 @@ export function RegistryEntryForm({
const { tags: docTypeTags } = useTags("document-type");
const fileInputRef = useRef<HTMLInputElement>(null);
// ── Build dynamic doc type list from defaults + Tag Manager ──
// Track locally-added custom types that may not yet be in Tag Manager
const [localCustomTypes, setLocalCustomTypes] = useState<Map<string, string>>(
new Map(),
);
// ── Build dynamic doc type list from defaults + Tag Manager + local ──
const allDocTypes = useMemo(() => {
const map = new Map<string, string>();
// Add defaults
@@ -105,8 +115,18 @@ export function RegistryEntryForm({
map.set(key, tag.label);
}
}
return map;
}, [docTypeTags]);
// Add locally-created types (before Tag Manager syncs)
for (const [key, label] of localCustomTypes) {
if (!map.has(key)) {
map.set(key, label);
}
}
// Sort alphabetically by label
const sorted = new Map(
[...map.entries()].sort((a, b) => a[1].localeCompare(b[1], "ro")),
);
return sorted;
}, [docTypeTags, localCustomTypes]);
const [direction, setDirection] = useState<RegistryDirection>(
initial?.direction ?? "intrat",
@@ -169,6 +189,9 @@ export function RegistryEntryForm({
const [externalTrackingId, setExternalTrackingId] = useState(
initial?.externalTrackingId ?? "",
);
const [acValidity, setAcValidity] = useState<ACValidityTracking | undefined>(
initial?.acValidity,
);
// ── Submission lock + file upload tracking ──
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -295,11 +318,14 @@ export function RegistryEntryForm({
setQuickContactOpen(false);
};
// ── Custom doc type creation ──
// ── Custom doc type creation — adds to local state immediately + Tag Manager ──
const handleAddCustomDocType = async () => {
const label = customDocType.trim();
if (!label) return;
const key = label.toLowerCase().replace(/\s+/g, "-");
// Add to local types immediately so it appears in the select
setLocalCustomTypes((prev) => new Map(prev).set(key, label));
// Select the newly created type
setDocumentType(key);
setCustomDocType("");
if (onCreateDocType) {
@@ -367,6 +393,7 @@ export function RegistryEntryForm({
expiryAlertDays: expiryDate ? expiryAlertDays : undefined,
externalStatusUrl: externalStatusUrl || undefined,
externalTrackingId: externalTrackingId || undefined,
acValidity: acValidity,
linkedEntryIds,
attachments,
trackedDeadlines:
@@ -465,18 +492,34 @@ export function RegistryEntryForm({
<div className="grid gap-4 sm:grid-cols-3">
<div>
<Label>Direcție</Label>
<Select
value={direction}
onValueChange={(v) => setDirection(v as RegistryDirection)}
>
<SelectTrigger className="mt-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="intrat">Intrat</SelectItem>
<SelectItem value="iesit">Ieșit</SelectItem>
</SelectContent>
</Select>
<div className="mt-1 flex rounded-lg border bg-muted/30 p-1">
<button
type="button"
onClick={() => setDirection("intrat")}
className={cn(
"flex flex-1 items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-all",
direction === "intrat"
? "bg-blue-500 text-white shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background",
)}
>
<ArrowDownToLine className="h-4 w-4" />
Intrat
</button>
<button
type="button"
onClick={() => setDirection("iesit")}
className={cn(
"flex flex-1 items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-all",
direction === "iesit"
? "bg-orange-500 text-white shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background",
)}
>
<ArrowUpFromLine className="h-4 w-4" />
Ieșit
</button>
</div>
</div>
<div>
<Label>Tip document</Label>
@@ -859,6 +902,13 @@ export function RegistryEntryForm({
})()}
</div>
{/* AC Validity Tracker */}
<ACValidityTracker
value={acValidity}
onChange={setAcValidity}
entryDate={date}
/>
{/* Web scraping prep — external tracking */}
<div className="grid gap-4 sm:grid-cols-2">
<div>