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:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user