fix(registratura): 5 post-3.02 fixes - QuickContact pre-fill (useEffect sync), form close on contact create (stopPropagation), Switch label 'Inchis' -> 'Status', CU moved from Avize to own category, close guard for active deadlines

This commit is contained in:
AI Assistant
2026-02-27 16:02:10 +02:00
parent 2be0462e0d
commit 80e41d4842
8 changed files with 470 additions and 131 deletions
+1
View File
@@ -264,6 +264,7 @@
- **Legături între intrări/ieșiri (Thread-uri & Branches):** Posibilitatea de a lega o ieșire de o intrare specifică (ex: "Răspuns la adresa nr. X"), creând un "fir" (thread) vizual al conversației instituționale. Trebuie să suporte și "branching" (ex: o intrare generează mai multe ieșiri conexe către instituții diferite). UI-ul trebuie să rămână extrem de simplu și intuitiv (ex: vizualizare tip arbore simplificat sau listă indentată în detaliile documentului).
**Status:** ✅ Done. All 6 sub-features implemented:
1. **Dynamic doc types** — Select + inline "Tip nou" input. New types auto-created as Tag Manager tags (category: document-type). Added "Apel telefonic" and "Videoconferință" as defaults.
2. **Bidirectional Address Book** — Autocomplete shows "Creează contact" button when no match. QuickContactDialog popup creates contact in Address Book with minimal data (Name required, Phone/Email optional).
3. **Simplified status** — Replaced Status dropdown with Switch toggle "Închis/Deschis". Default is open.
+4
View File
@@ -46,6 +46,7 @@ Continued Phase 3 refinements. Picked task 3.02 (HEAVY) — Registratura bidirec
## Session — 2026-02-27 evening (GitHub Copilot - Claude Opus 4.6)
### Context
Continued from earlier session (Gemini 3.1 Pro got stuck on Authentik testing). This session focused on fixing deployment pipeline, then implementing Phase 3 visual/UX tasks + infrastructure documentation.
### Completed
@@ -85,9 +86,11 @@ Continued from earlier session (Gemini 3.1 Pro got stuck on Authentik testing).
- SESSION-LOG.md: this entry
### Files Created
- `src/shared/components/common/theme-toggle.tsx` — Animated sun/moon theme toggle
### Files Modified
- `Dockerfile` — Added `npx prisma generate`
- `docker-compose.yml` — All env vars hardcoded
- `package.json`@prisma/client moved to dependencies
@@ -101,6 +104,7 @@ Continued from earlier session (Gemini 3.1 Pro got stuck on Authentik testing).
- `src/modules/hot-desk/components/desk-room-layout.tsx` — Window + door landmarks
### Notes
- Portainer CE requires manual "Pull and redeploy" — no auto-rebuild on webhook
- "Re-pull image" checkbox only needed for base image updates (node:20-alpine), not for code changes
- Logo SVGs have very different aspect ratios (BTG ~7:1, US ~6:1, SDT ~3:1) — using flex-1 min-w-0 to handle this
@@ -0,0 +1,252 @@
"use client";
import { useState, useMemo } from "react";
import { AlertTriangle, Search, Link2 } 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 { Textarea } from "@/shared/components/ui/textarea";
import { Badge } from "@/shared/components/ui/badge";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/shared/components/ui/dialog";
import type { RegistryEntry, TrackedDeadline } from "../types";
import { getDeadlineType } from "../services/deadline-catalog";
interface CloseGuardDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
/** The entry being closed */
entry: RegistryEntry;
/** All entries for search/linking */
allEntries: RegistryEntry[];
/** Active deadlines on this entry */
activeDeadlines: TrackedDeadline[];
/** Called when user provides justification and confirms close */
onConfirmClose: (justification: {
linkedEntryId?: string;
reason: string;
}) => void;
}
/**
* Guard dialog shown when a user tries to close an entry that has
* unresolved (active) legal deadlines. Requires justification:
* either link to a continuation entry or provide manual reason text.
*/
export function CloseGuardDialog({
open,
onOpenChange,
entry,
allEntries,
activeDeadlines,
onConfirmClose,
}: CloseGuardDialogProps) {
const [mode, setMode] = useState<"link" | "manual">("link");
const [search, setSearch] = useState("");
const [selectedEntryId, setSelectedEntryId] = useState("");
const [reason, setReason] = useState("");
// Reset on open
const handleOpenChange = (o: boolean) => {
if (o) {
setMode("link");
setSearch("");
setSelectedEntryId("");
setReason("");
}
onOpenChange(o);
};
// Searchable entries (exclude self and closed entries)
const searchResults = useMemo(() => {
if (!search || search.length < 2) return [];
const q = search.toLowerCase();
return allEntries
.filter(
(e) =>
e.id !== entry.id &&
e.status !== "inchis" &&
(e.subject.toLowerCase().includes(q) ||
e.number.toLowerCase().includes(q) ||
e.sender.toLowerCase().includes(q) ||
e.recipient.toLowerCase().includes(q)),
)
.slice(0, 8);
}, [search, allEntries, entry.id]);
const selectedEntry = allEntries.find((e) => e.id === selectedEntryId);
const canSubmit =
mode === "link"
? selectedEntryId !== ""
: reason.trim().length >= 5;
const handleSubmit = () => {
if (!canSubmit) return;
onConfirmClose({
linkedEntryId: mode === "link" ? selectedEntryId : undefined,
reason:
mode === "link"
? `Continuat în #${selectedEntry?.number ?? selectedEntryId}`
: reason.trim(),
});
};
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-amber-600 dark:text-amber-400">
<AlertTriangle className="h-5 w-5" />
Termene legale active
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* Warning message */}
<p className="text-sm text-muted-foreground">
Înregistrarea <strong>{entry.number}</strong> are{" "}
<strong>{activeDeadlines.length}</strong> termen
{activeDeadlines.length > 1 ? "e" : ""} legal
{activeDeadlines.length > 1 ? "e" : ""} nerezolvat
{activeDeadlines.length > 1 ? "e" : ""}. Pentru a o închide,
justifică decizia.
</p>
{/* Active deadlines list */}
<div className="rounded border p-2 space-y-1.5">
{activeDeadlines.map((dl) => {
const def = getDeadlineType(dl.typeId);
const isOverdue = new Date(dl.dueDate) < new Date();
return (
<div
key={dl.id}
className="flex items-center gap-2 text-sm"
>
<Badge
variant={isOverdue ? "destructive" : "outline"}
className="text-[10px] shrink-0"
>
{isOverdue ? "Depășit" : "Activ"}
</Badge>
<span className="font-medium">
{def?.label ?? dl.typeId}
</span>
<span className="text-muted-foreground text-xs ml-auto">
scadent {dl.dueDate}
</span>
</div>
);
})}
</div>
{/* Mode toggle */}
<div className="flex gap-2">
<Button
type="button"
variant={mode === "link" ? "default" : "outline"}
size="sm"
onClick={() => setMode("link")}
className="flex-1"
>
<Link2 className="mr-1.5 h-3.5 w-3.5" />
Leagă de altă înregistrare
</Button>
<Button
type="button"
variant={mode === "manual" ? "default" : "outline"}
size="sm"
onClick={() => setMode("manual")}
className="flex-1"
>
Justificare manuală
</Button>
</div>
{/* Link mode */}
{mode === "link" && (
<div className="space-y-2">
<Label>Caută înregistrarea-continuare</Label>
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
value={search}
onChange={(e) => {
setSearch(e.target.value);
setSelectedEntryId("");
}}
placeholder="Caută după nr., subiect, expeditor..."
className="pl-9"
/>
</div>
{searchResults.length > 0 && (
<div className="max-h-48 overflow-y-auto rounded border divide-y">
{searchResults.map((e) => (
<button
key={e.id}
type="button"
className={`w-full px-3 py-2 text-left text-sm hover:bg-accent transition-colors ${
selectedEntryId === e.id ? "bg-accent" : ""
}`}
onClick={() => setSelectedEntryId(e.id)}
>
<span className="font-mono font-medium text-xs">
{e.number}
</span>
<span className="ml-2">{e.subject}</span>
<span className="ml-2 text-muted-foreground text-xs">
{e.sender}
</span>
</button>
))}
</div>
)}
{selectedEntry && (
<p className="text-xs text-muted-foreground">
Selectat: <strong>{selectedEntry.number}</strong> {" "}
{selectedEntry.subject}
</p>
)}
</div>
)}
{/* Manual mode */}
{mode === "manual" && (
<div className="space-y-2">
<Label>Motiv închidere (min. 5 caractere)</Label>
<Textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Ex: Dosarul a fost retras de beneficiar..."
rows={3}
/>
</div>
)}
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
Anulează
</Button>
<Button
type="button"
onClick={handleSubmit}
disabled={!canSubmit}
variant="destructive"
>
Închide înregistrarea
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useEffect } from "react";
import { UserPlus } from "lucide-react";
import { Input } from "@/shared/components/ui/input";
import { Label } from "@/shared/components/ui/label";
@@ -35,24 +35,28 @@ export function QuickContactDialog({
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
// Reset when dialog opens with new name
const handleOpenChange = (o: boolean) => {
if (o) {
// Sync name with initialName whenever the dialog opens
// (useState(initialName) only works on first mount; controlled open
// bypasses onOpenChange so we need useEffect)
useEffect(() => {
if (open) {
setName(initialName);
setPhone("");
setEmail("");
}
onOpenChange(o);
};
}, [open, initialName]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Stop propagation so the submit doesn't bubble through the React portal
// to the outer registry-entry-form, which would close the whole form.
e.stopPropagation();
if (!name.trim()) return;
onConfirm({ name: name.trim(), phone: phone.trim(), email: email.trim() });
};
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
@@ -30,6 +30,7 @@ import { RegistryFilters } from "./registry-filters";
import { RegistryTable } from "./registry-table";
import { RegistryEntryForm } from "./registry-entry-form";
import { DeadlineDashboard } from "./deadline-dashboard";
import { CloseGuardDialog } from "./close-guard-dialog";
import { getOverdueDays } from "../services/registry-service";
import { aggregateDeadlines } from "../services/deadline-service";
import type { RegistryEntry, DeadlineResolution } from "../types";
@@ -59,6 +60,7 @@ export function RegistraturaModule() {
const [viewMode, setViewMode] = useState<ViewMode>("list");
const [editingEntry, setEditingEntry] = useState<RegistryEntry | null>(null);
const [closingId, setClosingId] = useState<string | null>(null);
const [guardingId, setGuardingId] = useState<string | null>(null);
// ── Bidirectional Address Book integration ──
const handleCreateContact = useCallback(
@@ -143,13 +145,51 @@ export function RegistraturaModule() {
const handleCloseRequest = (id: string) => {
const entry = allEntries.find((e) => e.id === id);
if (entry && (entry.linkedEntryIds ?? []).length > 0) {
if (!entry) return;
// Check for active (unresolved) legal deadlines
const activeDeadlines = (entry.trackedDeadlines ?? []).filter(
(d) => d.resolution === "pending",
);
if (activeDeadlines.length > 0) {
// Show guard dialog first
setGuardingId(id);
return;
}
// No active deadlines — proceed to linked-entries check
if ((entry.linkedEntryIds ?? []).length > 0) {
setClosingId(id);
} else {
closeEntry(id, false);
}
};
/** Called after the close-guard dialog is confirmed */
const handleGuardConfirm = (justification: {
linkedEntryId?: string;
reason: string;
}) => {
if (!guardingId) return;
const entry = allEntries.find((e) => e.id === guardingId);
// Store the justification in notes
if (entry) {
const justNote = `\n[Închis cu termene active] ${justification.reason}`;
updateEntry(guardingId, {
notes: (entry.notes ?? "") + justNote,
});
}
// Now check for linked entries
if (entry && (entry.linkedEntryIds ?? []).length > 0) {
setClosingId(guardingId);
} else {
closeEntry(guardingId, false);
}
setGuardingId(null);
};
const handleCloseConfirm = (closeLinked: boolean) => {
if (closingId) {
closeEntry(closingId, closeLinked);
@@ -202,6 +242,15 @@ export function RegistraturaModule() {
? allEntries.find((e) => e.id === closingId)
: null;
const guardingEntry = guardingId
? allEntries.find((e) => e.id === guardingId)
: null;
const guardingActiveDeadlines = guardingEntry
? (guardingEntry.trackedDeadlines ?? []).filter(
(d) => d.resolution === "pending",
)
: [];
return (
<Tabs defaultValue="registru">
<TabsList>
@@ -333,6 +382,20 @@ export function RegistraturaModule() {
</DialogFooter>
</DialogContent>
</Dialog>
{/* Close guard for entries with active legal deadlines */}
{guardingEntry && (
<CloseGuardDialog
open={guardingId !== null}
onOpenChange={(open) => {
if (!open) setGuardingId(null);
}}
entry={guardingEntry}
allEntries={allEntries}
activeDeadlines={guardingActiveDeadlines}
onConfirmClose={handleGuardConfirm}
/>
)}
</div>
</TabsContent>
@@ -609,7 +609,7 @@ export function RegistryEntryForm({
</div>
<div>
<Label className="flex items-center gap-1.5">
Închis
Status
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
@@ -1,220 +1,234 @@
import type { DeadlineTypeDef, DeadlineCategory } from '../types';
import type { DeadlineTypeDef, DeadlineCategory } from "../types";
export const DEADLINE_CATALOG: DeadlineTypeDef[] = [
// ── Certificat de Urbanism ──
{
id: "cerere-cu",
label: "Cerere CU",
description:
"Termen de emitere a Certificatului de Urbanism de la data depunerii cererii.",
days: 15,
dayType: "calendar",
startDateLabel: "Data depunerii",
requiresCustomStartDate: false,
tacitApprovalApplicable: true,
category: "certificat",
legalReference: "Legea 50/1991, art. 6¹",
},
// ── Avize ──
{
id: 'cerere-cu',
label: 'Cerere CU',
description: 'Termen de emitere a Certificatului de Urbanism de la data depunerii cererii.',
id: "avize-normale",
label: "Cerere Avize normale",
description: "Termen de emitere a avizelor de la data depunerii cererii.",
days: 15,
dayType: 'calendar',
startDateLabel: 'Data depunerii',
dayType: "calendar",
startDateLabel: "Data depunerii",
requiresCustomStartDate: false,
tacitApprovalApplicable: true,
category: 'avize',
legalReference: 'Legea 50/1991, art. 6¹',
category: "avize",
},
{
id: 'avize-normale',
label: 'Cerere Avize normale',
description: 'Termen de emitere a avizelor de la data depunerii cererii.',
days: 15,
dayType: 'calendar',
startDateLabel: 'Data depunerii',
requiresCustomStartDate: false,
tacitApprovalApplicable: true,
category: 'avize',
},
{
id: 'aviz-cultura',
label: 'Aviz Cultură',
description: 'Termen de emitere a avizului Ministerului Culturii de la data comisiei.',
id: "aviz-cultura",
label: "Aviz Cultură",
description:
"Termen de emitere a avizului Ministerului Culturii de la data comisiei.",
days: 30,
dayType: 'calendar',
startDateLabel: 'Data comisie',
dayType: "calendar",
startDateLabel: "Data comisie",
requiresCustomStartDate: true,
startDateHint: 'Data ședinței comisiei de specialitate',
startDateHint: "Data ședinței comisiei de specialitate",
tacitApprovalApplicable: true,
category: 'avize',
category: "avize",
},
{
id: 'aviz-mediu',
label: 'Aviz Mediu',
description: 'Termen de emitere a avizului de mediu de la finalizarea procedurilor.',
id: "aviz-mediu",
label: "Aviz Mediu",
description:
"Termen de emitere a avizului de mediu de la finalizarea procedurilor.",
days: 15,
dayType: 'calendar',
startDateLabel: 'Data finalizare proceduri',
dayType: "calendar",
startDateLabel: "Data finalizare proceduri",
requiresCustomStartDate: true,
startDateHint: 'Data finalizării procedurii de evaluare de mediu',
startDateHint: "Data finalizării procedurii de evaluare de mediu",
tacitApprovalApplicable: true,
category: 'avize',
category: "avize",
},
{
id: 'aviz-aeronautica',
label: 'Aviz Aeronautică',
description: 'Termen de emitere a avizului de la Autoritatea Aeronautică.',
id: "aviz-aeronautica",
label: "Aviz Aeronautică",
description: "Termen de emitere a avizului de la Autoritatea Aeronautică.",
days: 30,
dayType: 'calendar',
startDateLabel: 'Data depunerii',
dayType: "calendar",
startDateLabel: "Data depunerii",
requiresCustomStartDate: false,
tacitApprovalApplicable: true,
category: 'avize',
category: "avize",
},
// ── Completări ──
{
id: 'completare-beneficiar',
label: 'Completare — termen beneficiar',
description: 'Termen acordat beneficiarului pentru completarea documentației.',
id: "completare-beneficiar",
label: "Completare — termen beneficiar",
description:
"Termen acordat beneficiarului pentru completarea documentației.",
days: 60,
dayType: 'calendar',
startDateLabel: 'Data notificării',
dayType: "calendar",
startDateLabel: "Data notificării",
requiresCustomStartDate: false,
tacitApprovalApplicable: false,
chainNextTypeId: 'completare-emitere',
chainNextActionLabel: 'Adaugă termen emitere (15 zile)',
category: 'completari',
chainNextTypeId: "completare-emitere",
chainNextActionLabel: "Adaugă termen emitere (15 zile)",
category: "completari",
},
{
id: 'completare-emitere',
label: 'Completare — termen emitere',
description: 'Termen de emitere după depunerea completărilor.',
id: "completare-emitere",
label: "Completare — termen emitere",
description: "Termen de emitere după depunerea completărilor.",
days: 15,
dayType: 'calendar',
startDateLabel: 'Data depunere completări',
dayType: "calendar",
startDateLabel: "Data depunere completări",
requiresCustomStartDate: true,
startDateHint: 'Data la care beneficiarul a depus completările',
startDateHint: "Data la care beneficiarul a depus completările",
tacitApprovalApplicable: true,
category: 'completari',
category: "completari",
},
// ── Analiză ──
{
id: 'ctatu-analiza',
label: 'Analiză CTATU',
description: 'Termen de analiză în Comisia Tehnică de Amenajare a Teritoriului și Urbanism.',
id: "ctatu-analiza",
label: "Analiză CTATU",
description:
"Termen de analiză în Comisia Tehnică de Amenajare a Teritoriului și Urbanism.",
days: 30,
dayType: 'calendar',
startDateLabel: 'Data depunerii',
dayType: "calendar",
startDateLabel: "Data depunerii",
requiresCustomStartDate: false,
tacitApprovalApplicable: false,
category: 'analiza',
category: "analiza",
},
{
id: 'consiliu-promovare',
label: 'Promovare Consiliu Local',
description: 'Termen de promovare în ședința Consiliului Local.',
id: "consiliu-promovare",
label: "Promovare Consiliu Local",
description: "Termen de promovare în ședința Consiliului Local.",
days: 30,
dayType: 'calendar',
startDateLabel: 'Data depunerii',
dayType: "calendar",
startDateLabel: "Data depunerii",
requiresCustomStartDate: false,
tacitApprovalApplicable: false,
category: 'analiza',
category: "analiza",
},
{
id: 'consiliu-vot',
label: 'Vot Consiliu Local',
description: 'Termen de vot în Consiliu Local de la finalizarea dezbaterii publice.',
id: "consiliu-vot",
label: "Vot Consiliu Local",
description:
"Termen de vot în Consiliu Local de la finalizarea dezbaterii publice.",
days: 45,
dayType: 'calendar',
startDateLabel: 'Data finalizare dezbatere',
dayType: "calendar",
startDateLabel: "Data finalizare dezbatere",
requiresCustomStartDate: true,
startDateHint: 'Data finalizării dezbaterii publice',
startDateHint: "Data finalizării dezbaterii publice",
tacitApprovalApplicable: false,
category: 'analiza',
category: "analiza",
},
// ── Autorizare ──
{
id: 'verificare-ac',
label: 'Verificare AC',
description: 'Termen de verificare a documentației pentru Autorizația de Construire.',
id: "verificare-ac",
label: "Verificare AC",
description:
"Termen de verificare a documentației pentru Autorizația de Construire.",
days: 5,
dayType: 'working',
startDateLabel: 'Data depunerii',
dayType: "working",
startDateLabel: "Data depunerii",
requiresCustomStartDate: false,
tacitApprovalApplicable: false,
category: 'autorizare',
category: "autorizare",
},
{
id: 'prelungire-ac',
label: 'Cerere prelungire AC',
description: 'Cererea de prelungire trebuie depusă cu minim 45 zile lucrătoare ÎNAINTE de expirarea AC.',
id: "prelungire-ac",
label: "Cerere prelungire AC",
description:
"Cererea de prelungire trebuie depusă cu minim 45 zile lucrătoare ÎNAINTE de expirarea AC.",
days: 45,
dayType: 'working',
startDateLabel: 'Data expirare AC',
dayType: "working",
startDateLabel: "Data expirare AC",
requiresCustomStartDate: true,
startDateHint: 'Data de expirare a Autorizației de Construire',
startDateHint: "Data de expirare a Autorizației de Construire",
tacitApprovalApplicable: false,
category: 'autorizare',
category: "autorizare",
isBackwardDeadline: true,
},
{
id: 'prelungire-ac-comunicare',
label: 'Comunicare decizie prelungire',
description: 'Termen de comunicare a deciziei privind prelungirea AC.',
id: "prelungire-ac-comunicare",
label: "Comunicare decizie prelungire",
description: "Termen de comunicare a deciziei privind prelungirea AC.",
days: 15,
dayType: 'working',
startDateLabel: 'Data depunere cerere',
dayType: "working",
startDateLabel: "Data depunere cerere",
requiresCustomStartDate: true,
startDateHint: 'Data depunerii cererii de prelungire',
startDateHint: "Data depunerii cererii de prelungire",
tacitApprovalApplicable: false,
category: 'autorizare',
category: "autorizare",
},
// ── Publicitate ──
{
id: 'publicitate-ac',
label: 'Publicitate AC',
description: 'Termen de publicitate a Autorizației de Construire.',
id: "publicitate-ac",
label: "Publicitate AC",
description: "Termen de publicitate a Autorizației de Construire.",
days: 30,
dayType: 'calendar',
startDateLabel: 'Data emitere AC',
dayType: "calendar",
startDateLabel: "Data emitere AC",
requiresCustomStartDate: true,
startDateHint: 'Data emiterii Autorizației de Construire',
startDateHint: "Data emiterii Autorizației de Construire",
tacitApprovalApplicable: false,
category: 'publicitate',
category: "publicitate",
},
{
id: 'plangere-prealabila',
label: 'Plângere prealabilă',
description: 'Termen de depunere a plângerii prealabile.',
id: "plangere-prealabila",
label: "Plângere prealabilă",
description: "Termen de depunere a plângerii prealabile.",
days: 30,
dayType: 'calendar',
startDateLabel: 'Data ultimă publicitate',
dayType: "calendar",
startDateLabel: "Data ultimă publicitate",
requiresCustomStartDate: true,
startDateHint: 'Data ultimei publicități / aduceri la cunoștință',
startDateHint: "Data ultimei publicități / aduceri la cunoștință",
tacitApprovalApplicable: false,
chainNextTypeId: 'contestare-instanta',
chainNextActionLabel: 'Adaugă termen contestare instanță (60 zile)',
category: 'publicitate',
chainNextTypeId: "contestare-instanta",
chainNextActionLabel: "Adaugă termen contestare instanță (60 zile)",
category: "publicitate",
},
{
id: 'contestare-instanta',
label: 'Contestare în instanță',
description: 'Termen de contestare în instanța de contencios administrativ.',
id: "contestare-instanta",
label: "Contestare în instanță",
description:
"Termen de contestare în instanța de contencios administrativ.",
days: 60,
dayType: 'calendar',
startDateLabel: 'Data răspuns plângere',
dayType: "calendar",
startDateLabel: "Data răspuns plângere",
requiresCustomStartDate: true,
startDateHint: 'Data primirii răspunsului la plângerea prealabilă',
startDateHint: "Data primirii răspunsului la plângerea prealabilă",
tacitApprovalApplicable: false,
category: 'publicitate',
category: "publicitate",
},
];
export const CATEGORY_LABELS: Record<DeadlineCategory, string> = {
avize: 'Avize',
completari: 'Completări',
analiza: 'Analiză',
autorizare: 'Autorizare',
publicitate: 'Publicitate',
certificat: "Certificat de Urbanism",
avize: "Avize",
completari: "Completări",
analiza: "Analiză",
autorizare: "Autorizare",
publicitate: "Publicitate",
};
export function getDeadlineType(typeId: string): DeadlineTypeDef | undefined {
return DEADLINE_CATALOG.find((d) => d.id === typeId);
}
export function getDeadlinesByCategory(category: DeadlineCategory): DeadlineTypeDef[] {
export function getDeadlinesByCategory(
category: DeadlineCategory,
): DeadlineTypeDef[] {
return DEADLINE_CATALOG.filter((d) => d.category === category);
}
+1
View File
@@ -63,6 +63,7 @@ export type DeadlineResolution =
| "anulat";
export type DeadlineCategory =
| "certificat"
| "avize"
| "completari"
| "analiza"