Files
ArchiTools/src/modules/registratura/components/deadline-card.tsx
T
AI Assistant 442a1565fd feat: avize deadline restructure with interruption mechanism + comisie toggle
- Add "Necesita analiza in comisie" toggle for avize category (mirrors CJ toggle)
  - When OFF: auto-creates 5-day working limit for completari requests
  - When ON: no limit (institution can request completions anytime)
- Add interruption mechanism: resolve aviz as "intrerupt" when institution
  requests completions → auto-creates new 15-day deadline from completions date
- New resolution type "intrerupt" with yellow badge + chain support
- Restructure avize catalog entries:
  - aviz-ac-15 (L50) and aviz-urbanism-30 (L350) now have chain to
    aviz-emitere-dupa-completari for interruption flow
  - aviz-mediu: updated hints about procedure closure prerequisite
  - aviz-cultura-comisie: 2-phase with auto-track depunere la comisie (30 days)
  - aeronautica, ISU, transport-eu: all get interruption chain
- 3 new auto-track entries: aviz-completari-limit (5zl), aviz-emitere-dupa-completari
  (15zc), aviz-cultura-depunere-comisie (30zc)
- New document type: "Convocare sedinta"
- Info boxes in dialog explaining auto-track behavior + interruption mechanism

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 22:41:14 +02:00

216 lines
7.4 KiB
TypeScript

"use client";
import { useState } from "react";
import { Clock, CheckCircle2, X, History, ShieldCheck, Building2 } from "lucide-react";
import { Badge } from "@/shared/components/ui/badge";
import { Button } from "@/shared/components/ui/button";
import type { TrackedDeadline } from "../types";
import { getDeadlineType } from "../services/deadline-catalog";
import { getDeadlineDisplayStatus } from "../services/deadline-service";
import { cn } from "@/shared/lib/utils";
interface DeadlineCardProps {
deadline: TrackedDeadline;
onResolve: (deadline: TrackedDeadline) => void;
onRemove: (deadlineId: string) => void;
}
const VARIANT_CLASSES: Record<string, string> = {
green: "border-green-500/30 bg-green-50 dark:bg-green-950/20",
yellow: "border-yellow-500/30 bg-yellow-50 dark:bg-yellow-950/20",
red: "border-red-500/30 bg-red-50 dark:bg-red-950/20",
blue: "border-blue-500/30 bg-blue-50 dark:bg-blue-950/20",
gray: "border-muted bg-muted/30",
};
const BADGE_CLASSES: Record<string, string> = {
green: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
yellow:
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
red: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
blue: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
gray: "bg-muted text-muted-foreground",
};
export function DeadlineCard({
deadline,
onResolve,
onRemove,
}: DeadlineCardProps) {
const def = getDeadlineType(deadline.typeId);
const status = getDeadlineDisplayStatus(deadline);
const [showAudit, setShowAudit] = useState(false);
const auditLog = deadline.auditLog ?? [];
const isAutoTrack = def?.autoTrack === true;
const isVerificationDeadline = deadline.typeId === "cu-verificare";
const isCJDeadline =
deadline.typeId === "cu-cj-solicitare-aviz" ||
deadline.typeId === "cu-cj-aviz-primar";
// For verification deadlines, check if the 10-day period has passed
const verificationExpired =
isVerificationDeadline &&
deadline.resolution === "pending" &&
status.daysRemaining !== null &&
status.daysRemaining < 0;
return (
<div
className={cn(
"rounded-lg border p-3",
VARIANT_CLASSES[status.variant] ?? "",
)}
>
<div className="flex items-center gap-3">
{isAutoTrack ? (
isCJDeadline ? (
<Building2 className="h-4 w-4 shrink-0 text-amber-500" />
) : (
<ShieldCheck className="h-4 w-4 shrink-0 text-blue-500" />
)
) : (
<Clock className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-medium truncate">
{def?.label ?? deadline.typeId}
</span>
{isAutoTrack && (
<Badge variant="outline" className="text-[10px] text-purple-600 border-purple-300">
auto
</Badge>
)}
<Badge
className={cn(
"text-[10px] border-0",
BADGE_CLASSES[status.variant] ?? "",
)}
>
{status.label}
{status.daysRemaining !== null && status.variant !== "blue" && (
<span className="ml-1">
(
{status.daysRemaining < 0
? `${Math.abs(status.daysRemaining)}z depasit`
: `${status.daysRemaining}z`}
)
</span>
)}
</Badge>
</div>
{/* Verification badge: institution lost right to return docs */}
{verificationExpired && (
<div className="flex items-center gap-1 mt-1">
<Badge className="text-[10px] border-0 bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200">
<ShieldCheck className="h-3 w-3 mr-0.5" />
Nu mai pot returna documentatia
</Badge>
</div>
)}
{/* Interruption badge */}
{deadline.resolution === "intrerupt" && (
<div className="flex items-center gap-1 mt-1">
<Badge className="text-[10px] border-0 bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200">
Intrerupt completari solicitate
</Badge>
</div>
)}
<div className="text-xs text-muted-foreground mt-0.5">
{def?.isBackwardDeadline ? "Termen limita" : "Start"}:{" "}
{formatDate(deadline.startDate)}
{" \u2192 "}
{def?.isBackwardDeadline ? "Depunere pana la" : "Termen"}:{" "}
{formatDate(deadline.dueDate)}
{def?.dayType === "working" && (
<span className="ml-1">(zile lucratoare)</span>
)}
</div>
</div>
<div className="flex gap-1 shrink-0">
{auditLog.length > 0 && (
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7 text-muted-foreground"
onClick={() => setShowAudit(!showAudit)}
title="Istoric modificări"
>
<History className="h-3.5 w-3.5" />
</Button>
)}
{deadline.resolution === "pending" && (
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7 text-green-600"
onClick={() => onResolve(deadline)}
title="Rezolvă"
>
<CheckCircle2 className="h-3.5 w-3.5" />
</Button>
)}
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7 text-destructive"
onClick={() => onRemove(deadline.id)}
title="Șterge"
>
<X className="h-3.5 w-3.5" />
</Button>
</div>
</div>
{/* Audit log */}
{showAudit && auditLog.length > 0 && (
<div className="mt-2 border-t pt-2 space-y-1">
<p className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
Istoric modificări
</p>
{auditLog.map((entry, i) => (
<div key={i} className="flex items-start gap-2 text-[11px]">
<span className="text-muted-foreground whitespace-nowrap">
{formatDateTime(entry.timestamp)}
</span>
{entry.actor && (
<span className="font-medium">{entry.actor}</span>
)}
<span className="text-muted-foreground">{entry.detail}</span>
</div>
))}
</div>
)}
</div>
);
}
function formatDate(iso: string): string {
try {
return new Date(iso).toLocaleDateString("ro-RO", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
} catch {
return iso;
}
}
function formatDateTime(iso: string): string {
try {
return new Date(iso).toLocaleString("ro-RO", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} catch {
return iso;
}
}