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:
@@ -0,0 +1,741 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import {
|
||||
Shield,
|
||||
Calendar,
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
Bell,
|
||||
BellOff,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
FileText,
|
||||
Building2,
|
||||
Newspaper,
|
||||
Construction,
|
||||
Info,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Input } from "@/shared/components/ui/input";
|
||||
import { Label } from "@/shared/components/ui/label";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { Switch } from "@/shared/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/shared/components/ui/tooltip";
|
||||
import { Textarea } from "@/shared/components/ui/textarea";
|
||||
import type {
|
||||
ACValidityTracking,
|
||||
ACExecutionDuration,
|
||||
ACPhase,
|
||||
} from "../types";
|
||||
import { addWorkingDays, addCalendarDays } from "../services/working-days";
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
|
||||
interface ACValidityTrackerProps {
|
||||
value: ACValidityTracking | undefined;
|
||||
onChange: (value: ACValidityTracking | undefined) => void;
|
||||
/** The entry's date (used as default issuance date) */
|
||||
entryDate: string;
|
||||
}
|
||||
|
||||
const EXECUTION_LABELS: Record<ACExecutionDuration, string> = {
|
||||
6: "6 luni",
|
||||
12: "12 luni",
|
||||
24: "24 luni",
|
||||
36: "36 luni",
|
||||
};
|
||||
|
||||
const PHASE_LABELS: Record<ACPhase, string> = {
|
||||
validity: "Valabilitate AC",
|
||||
execution: "Execuție lucrări",
|
||||
extended: "Prelungit (+24 luni)",
|
||||
abandoned: "Abandonat",
|
||||
expired: "Expirat",
|
||||
};
|
||||
|
||||
const PHASE_COLORS: Record<ACPhase, string> = {
|
||||
validity: "bg-blue-500",
|
||||
execution: "bg-green-500",
|
||||
extended: "bg-amber-500",
|
||||
abandoned: "bg-gray-500",
|
||||
expired: "bg-red-500",
|
||||
};
|
||||
|
||||
function createDefault(entryDate: string): ACValidityTracking {
|
||||
return {
|
||||
enabled: true,
|
||||
issuanceDate: entryDate,
|
||||
phase: "validity",
|
||||
executionDuration: 12,
|
||||
requiredDocs: {
|
||||
cfNotation: false,
|
||||
newspaperPublication: false,
|
||||
sitePanel: false,
|
||||
},
|
||||
reminder: { snoozeCount: 0, dismissed: false },
|
||||
extensionGranted: false,
|
||||
abandonedDeclaration: false,
|
||||
notes: [],
|
||||
};
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString("ro-RO", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
function daysBetween(d1: string, d2: string): number {
|
||||
const a = new Date(d1);
|
||||
const b = new Date(d2);
|
||||
a.setHours(0, 0, 0, 0);
|
||||
b.setHours(0, 0, 0, 0);
|
||||
return Math.round((b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
function monthsBetween(d1: string, d2: string): number {
|
||||
const a = new Date(d1);
|
||||
const b = new Date(d2);
|
||||
return (
|
||||
(b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth())
|
||||
);
|
||||
}
|
||||
|
||||
export function ACValidityTracker({
|
||||
value,
|
||||
onChange,
|
||||
entryDate,
|
||||
}: ACValidityTrackerProps) {
|
||||
const [expanded, setExpanded] = useState(!!value?.enabled);
|
||||
|
||||
const ac = value ?? createDefault(entryDate);
|
||||
|
||||
const handleToggle = (enabled: boolean) => {
|
||||
if (enabled) {
|
||||
const newAc = createDefault(entryDate);
|
||||
onChange(newAc);
|
||||
setExpanded(true);
|
||||
} else {
|
||||
onChange(undefined);
|
||||
setExpanded(false);
|
||||
}
|
||||
};
|
||||
|
||||
const update = (changes: Partial<ACValidityTracking>) => {
|
||||
onChange({ ...ac, ...changes });
|
||||
};
|
||||
|
||||
const updateDocs = (changes: Partial<ACValidityTracking["requiredDocs"]>) => {
|
||||
onChange({ ...ac, requiredDocs: { ...ac.requiredDocs, ...changes } });
|
||||
};
|
||||
|
||||
// ── Computed dates ──
|
||||
const computedData = useMemo(() => {
|
||||
if (!ac.enabled || !ac.issuanceDate) return null;
|
||||
|
||||
const issuance = new Date(ac.issuanceDate);
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
const today = now.toISOString().slice(0, 10);
|
||||
|
||||
// 12-month validity period
|
||||
const validityEnd = new Date(issuance);
|
||||
validityEnd.setMonth(validityEnd.getMonth() + 12);
|
||||
const validityEndStr = validityEnd.toISOString().slice(0, 10);
|
||||
const daysToValidityEnd = daysBetween(today, validityEndStr);
|
||||
const monthsToValidityEnd = monthsBetween(today, validityEndStr);
|
||||
|
||||
// Announcement deadline: 10 days before starting works (within 12-month window)
|
||||
const announcementDeadline = addCalendarDays(validityEnd, -10);
|
||||
const announcementDeadlineStr = announcementDeadline
|
||||
.toISOString()
|
||||
.slice(0, 10);
|
||||
|
||||
// Extension request deadline: 45 working days before AC expiry
|
||||
const extensionRequestDeadline = addWorkingDays(validityEnd, -45);
|
||||
const extensionRequestDeadlineStr = extensionRequestDeadline
|
||||
.toISOString()
|
||||
.slice(0, 10);
|
||||
const daysToExtensionDeadline = daysBetween(
|
||||
today,
|
||||
extensionRequestDeadlineStr,
|
||||
);
|
||||
|
||||
// Execution period end (if works started)
|
||||
let executionEnd: string | null = null;
|
||||
if (ac.worksStartDate) {
|
||||
const start = new Date(ac.worksStartDate);
|
||||
start.setMonth(start.getMonth() + ac.executionDuration);
|
||||
executionEnd = start.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
// Extension end (if granted, +24 months from original end)
|
||||
let extendedEnd: string | null = null;
|
||||
if (ac.extensionGranted && executionEnd) {
|
||||
const execEnd = new Date(executionEnd);
|
||||
execEnd.setMonth(execEnd.getMonth() + 24);
|
||||
extendedEnd = execEnd.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
// Are all required docs fulfilled?
|
||||
const allDocsComplete =
|
||||
ac.requiredDocs.cfNotation &&
|
||||
ac.requiredDocs.newspaperPublication &&
|
||||
ac.requiredDocs.sitePanel;
|
||||
|
||||
// Can the validity period be "closed" (works announced)?
|
||||
const canAnnounce = allDocsComplete && !ac.worksAnnouncedDate;
|
||||
|
||||
// Extension prelungire deadline for execution (45 working days before exec end)
|
||||
let execExtensionDeadline: string | null = null;
|
||||
if (executionEnd) {
|
||||
const d = addWorkingDays(new Date(executionEnd), -45);
|
||||
execExtensionDeadline = d.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
return {
|
||||
validityEndStr,
|
||||
daysToValidityEnd,
|
||||
monthsToValidityEnd,
|
||||
announcementDeadlineStr,
|
||||
extensionRequestDeadlineStr,
|
||||
daysToExtensionDeadline,
|
||||
executionEnd,
|
||||
extendedEnd,
|
||||
allDocsComplete,
|
||||
canAnnounce,
|
||||
execExtensionDeadline,
|
||||
};
|
||||
}, [ac]);
|
||||
|
||||
const isEnabled = !!value?.enabled;
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-indigo-500/30 bg-indigo-500/5 p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-1.5 text-sm font-medium text-indigo-700 dark:text-indigo-300">
|
||||
<Shield className="h-4 w-4" />
|
||||
Valabilitate Autorizație de Construire (AC)
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-sm">
|
||||
<p className="text-xs">
|
||||
Urmărirea completă a ciclului de viață al AC: valabilitate 12
|
||||
luni, anunțare lucrări, documente obligatorii, durată
|
||||
execuție, prelungire. Conform Legii 50/1991 și normelor
|
||||
aferente.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Label>
|
||||
<Switch checked={isEnabled} onCheckedChange={handleToggle} />
|
||||
</div>
|
||||
|
||||
{isEnabled && computedData && (
|
||||
<>
|
||||
{/* Phase badge + overview */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div
|
||||
className={cn("h-2.5 w-2.5 rounded-full", PHASE_COLORS[ac.phase])}
|
||||
/>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{PHASE_LABELS[ac.phase]}
|
||||
</Badge>
|
||||
{computedData.daysToValidityEnd > 0 && ac.phase === "validity" && (
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs",
|
||||
computedData.daysToValidityEnd <= 30
|
||||
? "text-red-600 dark:text-red-400 font-medium"
|
||||
: computedData.daysToValidityEnd <= 90
|
||||
? "text-amber-600 dark:text-amber-400"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{computedData.daysToValidityEnd} zile rămase din valabilitate
|
||||
</span>
|
||||
)}
|
||||
{computedData.daysToValidityEnd <= 0 &&
|
||||
ac.phase === "validity" &&
|
||||
!ac.worksAnnouncedDate && (
|
||||
<span className="text-xs text-red-600 dark:text-red-400 font-medium">
|
||||
Valabilitate expirată!
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Issuance date + duration */}
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
<div>
|
||||
<Label className="text-xs">Data emitere AC</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={ac.issuanceDate}
|
||||
onChange={(e) => update({ issuanceDate: e.target.value })}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">Valabilitate până la</Label>
|
||||
<div className="mt-1 rounded border bg-muted/50 px-3 py-2 text-sm font-mono">
|
||||
{formatDate(computedData.validityEndStr)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">Durată execuție</Label>
|
||||
<Select
|
||||
value={String(ac.executionDuration)}
|
||||
onValueChange={(v) =>
|
||||
update({
|
||||
executionDuration: parseInt(v, 10) as ACExecutionDuration,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{([6, 12, 24, 36] as ACExecutionDuration[]).map((d) => (
|
||||
<SelectItem key={d} value={String(d)}>
|
||||
{EXECUTION_LABELS[d]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 1: Required documents for starting works */}
|
||||
{ac.phase === "validity" && !ac.abandonedDeclaration && (
|
||||
<div className="space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="flex items-center gap-1.5 text-xs font-medium text-indigo-700 dark:text-indigo-300"
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
Documente obligatorii pentru începere lucrări
|
||||
</button>
|
||||
|
||||
{expanded && (
|
||||
<div className="ml-4 space-y-2 rounded border bg-background/50 p-3">
|
||||
<p className="text-[10px] text-muted-foreground mb-2">
|
||||
Toate cele 3 documente sunt necesare pentru a putea anunța
|
||||
începerea lucrărilor. Minim 10 zile înainte de începerea
|
||||
lucrărilor, anunțați la Primărie și ISC.
|
||||
</p>
|
||||
|
||||
{/* CF Notation */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={ac.requiredDocs.cfNotation}
|
||||
onCheckedChange={(v) =>
|
||||
updateDocs({
|
||||
cfNotation: v,
|
||||
cfNotationDate: v
|
||||
? new Date().toISOString().slice(0, 10)
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label className="text-xs flex items-center gap-1.5 cursor-pointer">
|
||||
<FileText className="h-3.5 w-3.5" />
|
||||
Notare în Cartea Funciară (CF)
|
||||
</Label>
|
||||
</div>
|
||||
{ac.requiredDocs.cfNotation && (
|
||||
<Input
|
||||
type="date"
|
||||
value={ac.requiredDocs.cfNotationDate ?? ""}
|
||||
onChange={(e) =>
|
||||
updateDocs({ cfNotationDate: e.target.value })
|
||||
}
|
||||
className="w-[150px] text-xs h-7"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Newspaper publication */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={ac.requiredDocs.newspaperPublication}
|
||||
onCheckedChange={(v) =>
|
||||
updateDocs({
|
||||
newspaperPublication: v,
|
||||
newspaperPublicationDate: v
|
||||
? new Date().toISOString().slice(0, 10)
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label className="text-xs flex items-center gap-1.5 cursor-pointer">
|
||||
<Newspaper className="h-3.5 w-3.5" />
|
||||
Publicare în ziar
|
||||
</Label>
|
||||
</div>
|
||||
{ac.requiredDocs.newspaperPublication && (
|
||||
<Input
|
||||
type="date"
|
||||
value={ac.requiredDocs.newspaperPublicationDate ?? ""}
|
||||
onChange={(e) =>
|
||||
updateDocs({
|
||||
newspaperPublicationDate: e.target.value,
|
||||
})
|
||||
}
|
||||
className="w-[150px] text-xs h-7"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Site panel */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={ac.requiredDocs.sitePanel}
|
||||
onCheckedChange={(v) =>
|
||||
updateDocs({
|
||||
sitePanel: v,
|
||||
sitePanelDate: v
|
||||
? new Date().toISOString().slice(0, 10)
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label className="text-xs flex items-center gap-1.5 cursor-pointer">
|
||||
<Construction className="h-3.5 w-3.5" />
|
||||
Afișare panou de șantier
|
||||
</Label>
|
||||
</div>
|
||||
{ac.requiredDocs.sitePanel && (
|
||||
<Input
|
||||
type="date"
|
||||
value={ac.requiredDocs.sitePanelDate ?? ""}
|
||||
onChange={(e) =>
|
||||
updateDocs({ sitePanelDate: e.target.value })
|
||||
}
|
||||
className="w-[150px] text-xs h-7"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Progress indicator */}
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<div className="h-1.5 flex-1 rounded-full bg-muted overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-indigo-500 transition-all"
|
||||
style={{
|
||||
width: `${
|
||||
(((ac.requiredDocs.cfNotation ? 1 : 0) +
|
||||
(ac.requiredDocs.newspaperPublication ? 1 : 0) +
|
||||
(ac.requiredDocs.sitePanel ? 1 : 0)) /
|
||||
3) *
|
||||
100
|
||||
}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{(ac.requiredDocs.cfNotation ? 1 : 0) +
|
||||
(ac.requiredDocs.newspaperPublication ? 1 : 0) +
|
||||
(ac.requiredDocs.sitePanel ? 1 : 0)}
|
||||
/3
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 2: Announce works (when all docs are ready) */}
|
||||
{ac.phase === "validity" &&
|
||||
computedData.allDocsComplete &&
|
||||
!ac.worksAnnouncedDate &&
|
||||
!ac.abandonedDeclaration && (
|
||||
<div className="rounded border border-green-500/30 bg-green-500/5 p-3 space-y-2">
|
||||
<p className="text-xs font-medium text-green-700 dark:text-green-300 flex items-center gap-1.5">
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
Documente complete! Puteți anunța începerea lucrărilor.
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Anunțați la Primărie și ISC cu minim 10 zile înainte de
|
||||
începerea lucrărilor. Termen recomandat anunțare:{" "}
|
||||
{formatDate(computedData.announcementDeadlineStr)}
|
||||
</p>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<Label className="text-xs">Data anunțare lucrări</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={ac.worksAnnouncedDate ?? ""}
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
update({
|
||||
worksAnnouncedDate: e.target.value,
|
||||
worksStartDate: e.target.value,
|
||||
phase: "execution",
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Works announced — execution phase */}
|
||||
{(ac.phase === "execution" || ac.phase === "extended") &&
|
||||
ac.worksStartDate &&
|
||||
computedData.executionEnd && (
|
||||
<div className="rounded border border-green-500/30 bg-green-500/5 p-3 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
<span className="text-xs font-medium text-green-700 dark:text-green-300">
|
||||
Lucrări în execuție
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-3 text-xs">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Început:</span>
|
||||
<p className="font-mono">{formatDate(ac.worksStartDate)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Durată:</span>
|
||||
<p className="font-medium">
|
||||
{ac.executionDuration} luni
|
||||
{ac.phase === "extended" ? " + 24 luni" : ""}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
Termen execuție:
|
||||
</span>
|
||||
<p className="font-mono">
|
||||
{formatDate(
|
||||
ac.phase === "extended" && computedData.extendedEnd
|
||||
? computedData.extendedEnd
|
||||
: computedData.executionEnd,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Extension request reminder */}
|
||||
{computedData.execExtensionDeadline &&
|
||||
!ac.extensionGranted &&
|
||||
ac.phase !== "extended" && (
|
||||
<div className="rounded border border-amber-500/30 bg-amber-500/5 p-2 text-[10px]">
|
||||
<p className="text-amber-700 dark:text-amber-400 flex items-center gap-1.5">
|
||||
<AlertTriangle className="h-3.5 w-3.5 shrink-0" />
|
||||
Cererea de prelungire trebuie depusă cu cel puțin 45
|
||||
zile lucrătoare înainte de expirare. Termen limită:{" "}
|
||||
<strong>
|
||||
{formatDate(computedData.execExtensionDeadline)}
|
||||
</strong>
|
||||
</p>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
Prelungirea se acordă o singură dată, gratuit, pentru
|
||||
max 24 luni. Se înscrie pe originalul AC fără
|
||||
documentație nouă. Decizia vine în max 15 zile
|
||||
lucrătoare de la depunere.
|
||||
</p>
|
||||
|
||||
{!ac.extensionRequestDate && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Label className="text-[10px]">
|
||||
Data cerere prelungire:
|
||||
</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value=""
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
update({
|
||||
extensionRequestDate: e.target.value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="w-[140px] text-[10px] h-6"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{ac.extensionRequestDate && !ac.extensionGranted && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<span className="text-green-600 text-[10px]">
|
||||
Cerere depusă la{" "}
|
||||
{formatDate(ac.extensionRequestDate)}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-[10px] px-2"
|
||||
onClick={() =>
|
||||
update({
|
||||
extensionGranted: true,
|
||||
extensionGrantedDate: new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10),
|
||||
phase: "extended",
|
||||
})
|
||||
}
|
||||
>
|
||||
Prelungire acordată
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Reminders section */}
|
||||
{ac.phase === "validity" &&
|
||||
!ac.worksAnnouncedDate &&
|
||||
!ac.abandonedDeclaration && (
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
{ac.reminder.dismissed ? (
|
||||
<BellOff className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Bell className="h-3.5 w-3.5 text-amber-500" />
|
||||
)}
|
||||
<span className="text-muted-foreground">
|
||||
{ac.reminder.dismissed
|
||||
? "Remindere dezactivate"
|
||||
: `Reminder lunar activ (luna ${computedData.monthsToValidityEnd > 0 ? 12 - computedData.monthsToValidityEnd : 12}/12)`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{!ac.reminder.dismissed &&
|
||||
computedData.monthsToValidityEnd >= 2 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 text-[10px] px-2"
|
||||
onClick={() =>
|
||||
update({
|
||||
reminder: {
|
||||
...ac.reminder,
|
||||
snoozeCount: ac.reminder.snoozeCount + 1,
|
||||
lastSnoozed: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<Clock className="mr-1 h-3 w-3" /> Amână 1 lună
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Abandon option (after month 10 or when snooze limit reached) */}
|
||||
{ac.phase === "validity" &&
|
||||
!ac.worksAnnouncedDate &&
|
||||
!ac.abandonedDeclaration && (
|
||||
<div className="border-t pt-3 space-y-2">
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Dacă nu se mai dorește începerea construcției:
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={ac.abandonedDeclaration}
|
||||
onCheckedChange={(v) => {
|
||||
if (v) {
|
||||
update({
|
||||
abandonedDeclaration: true,
|
||||
abandonedDate: new Date().toISOString().slice(0, 10),
|
||||
phase: "abandoned",
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label className="text-xs cursor-pointer flex items-center gap-1.5">
|
||||
<XCircle className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
Declar că nu se mai dorește începerea construcției
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Abandoned state */}
|
||||
{ac.abandonedDeclaration && (
|
||||
<div className="rounded border border-gray-500/30 bg-gray-500/5 p-3 text-xs space-y-2">
|
||||
<p className="font-medium text-muted-foreground flex items-center gap-1.5">
|
||||
<XCircle className="h-4 w-4" />
|
||||
Construcția a fost declarată abandonată
|
||||
</p>
|
||||
{ac.abandonedDate && (
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Data declarației: {formatDate(ac.abandonedDate)}
|
||||
</p>
|
||||
)}
|
||||
<Textarea
|
||||
value={ac.abandonedReason ?? ""}
|
||||
onChange={(e) => update({ abandonedReason: e.target.value })}
|
||||
placeholder="Motiv (opțional)..."
|
||||
rows={2}
|
||||
className="text-xs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Validity extension option (within validity phase) */}
|
||||
{ac.phase === "validity" &&
|
||||
!ac.worksAnnouncedDate &&
|
||||
!ac.abandonedDeclaration &&
|
||||
computedData.daysToExtensionDeadline <= 90 && (
|
||||
<div className="rounded border border-amber-500/30 bg-amber-500/5 p-3 space-y-2">
|
||||
<p className="text-xs text-amber-700 dark:text-amber-300 flex items-center gap-1.5">
|
||||
<AlertTriangle className="h-4 w-4 shrink-0" />
|
||||
Opțiune: Prelungire valabilitate AC cu 24 luni
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Termen limită depunere cerere:{" "}
|
||||
<strong>
|
||||
{formatDate(computedData.extensionRequestDeadlineStr)}
|
||||
</strong>{" "}
|
||||
(45 zile lucrătoare înainte de expirare). Atenție: este mai
|
||||
avantajos să declarați începerea lucrărilor și să cereți
|
||||
prelungirea duratei de execuție — astfel aveți mai mult timp
|
||||
total.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user