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:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ export type DeadlineResolution =
|
||||
| "anulat";
|
||||
|
||||
export type DeadlineCategory =
|
||||
| "certificat"
|
||||
| "avize"
|
||||
| "completari"
|
||||
| "analiza"
|
||||
|
||||
Reference in New Issue
Block a user