Files
ArchiTools/src/modules/registratura/components/deadline-add-dialog.tsx
T
AI Assistant 31565b418a fix: doc type persistence on edit + filter deadlines by document type
- Fix doc type showing "altele" after edit: preserve initial documentType
  in allDocTypes map even if not in defaults or Tag Manager
- Filter deadline categories by document type: only cerere/aviz unlock
  full permitting categories (CU, avize, urbanism, autorizare)
- Other doc types (scrisoare, notificare, etc.) only get completari +
  contestatie as deadline categories
- Add completari to intrat direction (was missing)
- Pass documentType to DeadlineAddDialog for category filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 21:58:06 +02:00

455 lines
17 KiB
TypeScript

"use client";
import { useState, useMemo } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/shared/components/ui/dialog";
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 { Info, Building2, Calendar } from "lucide-react";
import {
CATEGORY_LABELS,
getCategoriesForDirection,
getSelectableDeadlines,
} from "../services/deadline-catalog";
import { computeDueDate } from "../services/working-days";
import type {
DeadlineCategory,
DeadlineTypeDef,
RegistryDirection,
} from "../types";
interface DeadlineAddDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
entryDate: string;
direction: RegistryDirection;
/** Document type — filters which deadline categories are available */
documentType?: string;
/** Callback: typeId, startDate, options (CJ toggle etc.) */
onAdd: (
typeId: string,
startDate: string,
options?: { isCJ?: boolean },
) => void;
}
type Step = "category" | "type" | "date";
export function DeadlineAddDialog({
open,
onOpenChange,
entryDate,
direction,
documentType,
onAdd,
}: DeadlineAddDialogProps) {
const [step, setStep] = useState<Step>("category");
const [selectedCategory, setSelectedCategory] =
useState<DeadlineCategory | null>(null);
const [selectedType, setSelectedType] = useState<DeadlineTypeDef | null>(
null,
);
const [startDate, setStartDate] = useState(entryDate);
const [isCJ, setIsCJ] = useState(false);
// ── Prelungire helper state ──
const [cuIssueDate, setCuIssueDate] = useState("");
const [cuDurationMonths, setCuDurationMonths] = useState<number | null>(null);
const categories = useMemo(
() => getCategoriesForDirection(direction, documentType),
[direction, documentType],
);
const typesForCategory = useMemo(() => {
if (!selectedCategory) return [];
return getSelectableDeadlines(selectedCategory);
}, [selectedCategory]);
const dueDatePreview = useMemo(() => {
if (!selectedType || !startDate) return null;
const start = new Date(startDate);
start.setHours(0, 0, 0, 0);
const due = computeDueDate(
start,
selectedType.days,
selectedType.dayType,
selectedType.isBackwardDeadline,
);
return due.toLocaleDateString("ro-RO", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}, [selectedType, startDate]);
// Compute CU expiry when user uses the prelungire helper
const computedExpiryDate = useMemo(() => {
if (!cuIssueDate || !cuDurationMonths) return null;
const issue = new Date(cuIssueDate);
issue.setMonth(issue.getMonth() + cuDurationMonths);
return issue;
}, [cuIssueDate, cuDurationMonths]);
const handleClose = () => {
setStep("category");
setSelectedCategory(null);
setSelectedType(null);
setStartDate(entryDate);
setIsCJ(false);
setCuIssueDate("");
setCuDurationMonths(null);
onOpenChange(false);
};
const handleCategorySelect = (cat: DeadlineCategory) => {
setSelectedCategory(cat);
setStep("type");
};
const handleTypeSelect = (typ: DeadlineTypeDef) => {
setSelectedType(typ);
if (!typ.requiresCustomStartDate) {
setStartDate(entryDate);
}
setStep("date");
};
const handleBack = () => {
if (step === "type") {
setStep("category");
setSelectedCategory(null);
setIsCJ(false);
} else if (step === "date") {
setStep("type");
setSelectedType(null);
setCuIssueDate("");
setCuDurationMonths(null);
}
};
const handleConfirm = () => {
if (!selectedType || !startDate) return;
const isCUType =
selectedType.id === "cu-emitere-l50" ||
selectedType.id === "cu-emitere-l350";
onAdd(selectedType.id, startDate, {
isCJ: isCUType ? isCJ : undefined,
});
handleClose();
};
// Apply prelungire helper: set start date from computed expiry
const handleApplyExpiryHelper = () => {
if (computedExpiryDate) {
const y = computedExpiryDate.getFullYear();
const m = String(computedExpiryDate.getMonth() + 1).padStart(2, "0");
const d = String(computedExpiryDate.getDate()).padStart(2, "0");
setStartDate(`${y}-${m}-${d}`);
}
};
const isPrelungireType = selectedType?.id === "cu-prelungire-emitere";
const isCUEmitere =
selectedType?.id === "cu-emitere-l50" ||
selectedType?.id === "cu-emitere-l350";
return (
<Dialog
open={open}
onOpenChange={(o) => {
if (!o) handleClose();
}}
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>
{step === "category" && "Adauga termen legal — Categorie"}
{step === "type" &&
`Adauga termen legal — ${selectedCategory ? CATEGORY_LABELS[selectedCategory] : ""}`}
{step === "date" &&
`Adauga termen legal — ${selectedType?.label ?? ""}`}
</DialogTitle>
</DialogHeader>
{/* ── Step 1: Category selection ── */}
{step === "category" && (
<div className="grid gap-2 py-2">
{categories.map((cat) => {
const count = getSelectableDeadlines(cat).length;
return (
<button
key={cat}
type="button"
className="flex items-center justify-between rounded-lg border p-3 text-left transition-colors hover:bg-accent"
onClick={() => handleCategorySelect(cat)}
>
<span className="font-medium text-sm">
{CATEGORY_LABELS[cat]}
</span>
<Badge variant="outline" className="text-xs">
{count}
</Badge>
</button>
);
})}
{/* Direction info */}
<div className="flex items-start gap-2 mt-2 rounded-lg bg-muted/50 p-2.5">
<Info className="h-3.5 w-3.5 mt-0.5 text-muted-foreground shrink-0" />
<p className="text-[11px] text-muted-foreground">
{direction === "iesit"
? "Categoriile afisate sunt pentru demersuri depuse de noi (iesiri) — termene pe care le urmarim la institutii."
: "Categoriile afisate sunt pentru acte administrative primite (intrari) — termene de contestare/raspuns."}
</p>
</div>
</div>
)}
{/* ── Step 2: Type selection ── */}
{step === "type" && (
<div className="space-y-2 py-2">
{/* CJ toggle for CU category */}
{selectedCategory === "certificat" && (
<label className="flex items-center gap-2 rounded-lg border border-dashed p-2.5 cursor-pointer hover:bg-accent/50 transition-colors">
<input
type="checkbox"
checked={isCJ}
onChange={(e) => setIsCJ(e.target.checked)}
className="h-4 w-4 rounded border-gray-300"
/>
<Building2 className="h-4 w-4 text-muted-foreground" />
<div>
<span className="text-sm font-medium">
Solicitat la Consiliul Judetean
</span>
<p className="text-[10px] text-muted-foreground">
Activeaza sub-termene automate: arhitect-sef solicita aviz
primar (3z) + primar emite aviz (5z)
</p>
</div>
</label>
)}
<div className="grid gap-2 max-h-[400px] overflow-y-auto">
{typesForCategory.map((typ) => (
<button
key={typ.id}
type="button"
className="rounded-lg border p-3 text-left transition-colors hover:bg-accent"
onClick={() => handleTypeSelect(typ)}
>
<div className="flex items-center gap-2 flex-wrap">
<span className="font-medium text-sm">{typ.label}</span>
<Badge variant="outline" className="text-[10px]">
{typ.days}{" "}
{typ.dayType === "working" ? "zile lucr." : "zile cal."}
</Badge>
{typ.tacitApprovalApplicable && (
<Badge
variant="outline"
className="text-[10px] text-blue-600"
>
tacit
</Badge>
)}
{typ.isBackwardDeadline && (
<Badge
variant="outline"
className="text-[10px] text-orange-600"
>
inapoi
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{typ.description}
</p>
{typ.legalReference && (
<p className="text-[10px] text-muted-foreground/70 mt-0.5 italic">
{typ.legalReference}
</p>
)}
</button>
))}
</div>
{/* Info about auto-tracked deadlines for CU */}
{selectedCategory === "certificat" && (
<div className="flex items-start gap-2 rounded-lg bg-blue-50 dark:bg-blue-950/20 border border-blue-200 dark:border-blue-800 p-2.5">
<Info className="h-3.5 w-3.5 mt-0.5 text-blue-600 shrink-0" />
<div className="text-[11px] text-blue-800 dark:text-blue-300">
<p className="font-medium">Termene automate (in fundal):</p>
<p className="mt-0.5">
Verificare cerere CU (10 zile lucr.) se creeaza automat.
Dupa expirare, institutia nu mai poate returna documentatia.
</p>
{isCJ && (
<p className="mt-0.5">
Aviz primar CJ (3z + 5z) se creeaza automat la
bifarea optiunii CJ.
</p>
)}
</div>
</div>
)}
</div>
)}
{/* ── Step 3: Date confirmation + preview ── */}
{step === "date" && selectedType && (
<div className="space-y-4 py-2">
{/* Prelungire helper: compute expiry from CU issue date */}
{isPrelungireType && (
<div className="rounded-lg border border-dashed p-3 space-y-2">
<div className="flex items-center gap-1.5">
<Calendar className="h-3.5 w-3.5 text-muted-foreground" />
<span className="text-xs font-medium">
Calculator data expirare CU
</span>
</div>
<p className="text-[10px] text-muted-foreground">
Introdu data emiterii CU si selecteaza durata de valabilitate
pentru a calcula automat data expirarii.
</p>
<div className="flex gap-2 items-end">
<div className="flex-1">
<Label className="text-[10px]">Data emitere CU</Label>
<Input
type="date"
value={cuIssueDate}
onChange={(e) => setCuIssueDate(e.target.value)}
className="mt-0.5 h-8 text-xs"
/>
</div>
<div className="flex gap-1">
{[6, 12, 24].map((months) => (
<Button
key={months}
type="button"
variant={
cuDurationMonths === months ? "default" : "outline"
}
size="sm"
className="h-8 text-xs px-2"
onClick={() => setCuDurationMonths(months)}
>
{months} luni
</Button>
))}
</div>
</div>
{computedExpiryDate && (
<div className="flex items-center justify-between rounded bg-muted/50 px-2 py-1.5">
<span className="text-xs text-muted-foreground">
Data expirare CU:{" "}
<strong>
{computedExpiryDate.toLocaleDateString("ro-RO", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})}
</strong>
</span>
<Button
type="button"
variant="outline"
size="sm"
className="h-6 text-[10px] px-2"
onClick={handleApplyExpiryHelper}
>
Aplica ca data start
</Button>
</div>
)}
</div>
)}
{/* Start date input */}
<div>
<Label>{selectedType.startDateLabel}</Label>
{selectedType.startDateHint && (
<p className="text-xs text-muted-foreground mt-0.5">
{selectedType.startDateHint}
</p>
)}
<Input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="mt-1"
/>
</div>
{/* Due date preview */}
{dueDatePreview && (
<div className="rounded-lg border bg-muted/30 p-3">
<p className="text-xs text-muted-foreground">
{selectedType.isBackwardDeadline
? "Termen limita depunere"
: "Termen limita calculat"}
</p>
<p className="text-lg font-bold">{dueDatePreview}</p>
<p className="text-xs text-muted-foreground mt-1">
{selectedType.days}{" "}
{selectedType.dayType === "working"
? "zile lucratoare"
: "zile calendaristice"}
{selectedType.isBackwardDeadline
? " INAINTE"
: " de la data start"}
</p>
{selectedType.legalReference && (
<p className="text-xs text-muted-foreground mt-1 italic">
Ref: {selectedType.legalReference}
</p>
)}
</div>
)}
{/* CJ info reminder */}
{isCUEmitere && isCJ && (
<div className="flex items-start gap-2 rounded-lg bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800 p-2.5">
<Building2 className="h-3.5 w-3.5 mt-0.5 text-amber-600 shrink-0" />
<p className="text-[11px] text-amber-800 dark:text-amber-300">
Solicitat la CJ se vor crea automat sub-termenele de aviz
primar (3z solicitat + 5z emis).
</p>
</div>
)}
</div>
)}
<DialogFooter className="gap-2 sm:gap-0">
{step !== "category" && (
<Button type="button" variant="outline" onClick={handleBack}>
Inapoi
</Button>
)}
{step === "category" && (
<Button type="button" variant="outline" onClick={handleClose}>
Anuleaza
</Button>
)}
{step === "date" && (
<Button
type="button"
onClick={handleConfirm}
disabled={!startDate}
>
Adauga termen
</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}