feat(registratura): add legal deadline tracking system (Termene Legale)

Full deadline tracking engine for Romanian construction permitting:
- 16 deadline types across 5 categories (Avize, Completări, Analiză, Autorizare, Publicitate)
- Working days vs calendar days with Romanian public holidays (Orthodox Easter via Meeus)
- Backward deadlines (AC extension: 45 working days BEFORE expiry)
- Chain deadlines (resolving one prompts adding the next)
- Tacit approval auto-detection (overdue + applicable type)
- Tabbed UI: Registru + Termene legale dashboard with stats/filters/table
- Inline deadline cards in entry form with add/resolve/remove
- Clock icon + count badge on registry table for entries with deadlines

Also adds CLAUDE.md with full project context for AI assistant handoff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Marius Tarau
2026-02-18 11:27:34 +02:00
parent f0b878cf00
commit bb01268bcb
16 changed files with 1818 additions and 96 deletions

View File

@@ -3,8 +3,10 @@
import { useState, useEffect, useCallback } from 'react';
import { useStorage } from '@/core/storage';
import { v4 as uuid } from 'uuid';
import type { RegistryEntry, RegistryDirection, RegistryStatus, DocumentType } from '../types';
import type { RegistryEntry, RegistryDirection, RegistryStatus, DocumentType, TrackedDeadline, DeadlineResolution } from '../types';
import { getAllEntries, saveEntry, deleteEntry, generateRegistryNumber } from '../services/registry-service';
import { createTrackedDeadline, resolveDeadline as resolveDeadlineFn } from '../services/deadline-service';
import { getDeadlineType } from '../services/deadline-catalog';
export interface RegistryFilters {
search: string;
@@ -97,6 +99,71 @@ export function useRegistry() {
setFilters((prev) => ({ ...prev, [key]: value }));
}, []);
// ── Deadline operations ──
const addDeadline = useCallback(async (entryId: string, typeId: string, startDate: string, chainParentId?: string) => {
const entry = entries.find((e) => e.id === entryId);
if (!entry) return null;
const tracked = createTrackedDeadline(typeId, startDate, chainParentId);
if (!tracked) return null;
const existing = entry.trackedDeadlines ?? [];
const updated: RegistryEntry = {
...entry,
trackedDeadlines: [...existing, tracked],
updatedAt: new Date().toISOString(),
};
await saveEntry(storage, updated);
await refresh();
return tracked;
}, [entries, storage, refresh]);
const resolveDeadline = useCallback(async (
entryId: string,
deadlineId: string,
resolution: DeadlineResolution,
note?: string,
): Promise<TrackedDeadline | null> => {
const entry = entries.find((e) => e.id === entryId);
if (!entry) return null;
const deadlines = entry.trackedDeadlines ?? [];
const idx = deadlines.findIndex((d) => d.id === deadlineId);
if (idx === -1) return null;
const dl = deadlines[idx];
if (!dl) return null;
const resolved = resolveDeadlineFn(dl, resolution, note);
const updatedDeadlines = [...deadlines];
updatedDeadlines[idx] = resolved;
const updated: RegistryEntry = {
...entry,
trackedDeadlines: updatedDeadlines,
updatedAt: new Date().toISOString(),
};
await saveEntry(storage, updated);
// If the resolved deadline has a chain, automatically check for the next type
const def = getDeadlineType(dl.typeId);
await refresh();
if (def?.chainNextTypeId && (resolution === 'completed' || resolution === 'aprobat-tacit')) {
return resolved;
}
return resolved;
}, [entries, storage, refresh]);
const removeDeadline = useCallback(async (entryId: string, deadlineId: string) => {
const entry = entries.find((e) => e.id === entryId);
if (!entry) return;
const deadlines = entry.trackedDeadlines ?? [];
const updated: RegistryEntry = {
...entry,
trackedDeadlines: deadlines.filter((d) => d.id !== deadlineId),
updatedAt: new Date().toISOString(),
};
await saveEntry(storage, updated);
await refresh();
}, [entries, storage, refresh]);
const filteredEntries = entries.filter((entry) => {
if (filters.direction !== 'all' && entry.direction !== filters.direction) return false;
if (filters.status !== 'all' && entry.status !== filters.status) return false;
@@ -124,6 +191,9 @@ export function useRegistry() {
updateEntry,
removeEntry,
closeEntry,
addDeadline,
resolveDeadline,
removeDeadline,
refresh,
};
}