3abf0d189c
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
742 lines
28 KiB
TypeScript
742 lines
28 KiB
TypeScript
"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>
|
|
);
|
|
}
|