feat: deadline pause/resume on clarifications + enhanced timeline UX

- TrackedDeadline gains pausedAt + totalPausedDays fields
- pauseDeadline() / resumeDeadline() in deadline-service with audit log
- Auto-pause: when incoming conex linked to parent with active deadlines
- Auto-resume: when outgoing conex linked to parent with paused deadlines
  (shifts dueDate forward by paused days)
- Timeline shows "Suspendat" state with blue pulsing progress bar
- Milestone tooltips now show exact dates (hover: "Data maximă: ...")
- ISC warning text on expired emission deadlines
- Verification expired text matches PDF spec
- DeadlineAuditEntry gains "paused" | "resumed" action types
- getDeadlineDisplayStatus returns "Suspendat" (blue) for paused deadlines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-12 17:54:19 +02:00
parent 1361534c98
commit c892e8d820
4 changed files with 177 additions and 15 deletions
@@ -75,6 +75,70 @@ export function resolveDeadline(
};
}
/**
* Pause a deadline (e.g., when a clarification request is linked).
* Records the pause date and adds audit entry.
*/
export function pauseDeadline(deadline: TrackedDeadline): TrackedDeadline {
if (deadline.pausedAt) return deadline; // already paused
const now = new Date().toISOString();
return {
...deadline,
pausedAt: now,
auditLog: [
...(deadline.auditLog ?? []),
{
action: "paused" as DeadlineAuditEntry["action"],
timestamp: now,
detail: "Termen suspendat — solicitare clarificări primită",
},
],
};
}
/**
* Resume a paused deadline (e.g., when completions are submitted).
* Adds the paused days to totalPausedDays and shifts dueDate forward.
*/
export function resumeDeadline(
deadline: TrackedDeadline,
resumeDate?: string,
): TrackedDeadline {
if (!deadline.pausedAt) return deadline; // not paused
const now = resumeDate ? new Date(resumeDate) : new Date();
now.setHours(0, 0, 0, 0);
const pausedStart = new Date(deadline.pausedAt);
pausedStart.setHours(0, 0, 0, 0);
const pausedDays = Math.max(
0,
Math.round((now.getTime() - pausedStart.getTime()) / (1000 * 60 * 60 * 24)),
);
// Shift due date forward by the number of paused days
const oldDue = new Date(deadline.dueDate);
oldDue.setHours(0, 0, 0, 0);
oldDue.setDate(oldDue.getDate() + pausedDays);
const newDueDate = formatDate(oldDue);
const totalPaused = (deadline.totalPausedDays ?? 0) + pausedDays;
const ts = new Date().toISOString();
return {
...deadline,
pausedAt: undefined,
totalPausedDays: totalPaused,
dueDate: newDueDate,
auditLog: [
...(deadline.auditLog ?? []),
{
action: "resumed" as DeadlineAuditEntry["action"],
timestamp: ts,
detail: `Termen reluat — ${pausedDays} zile suspendate, nou termen: ${newDueDate}`,
},
],
};
}
/**
* Get the display status for a tracked deadline — color coding + label.
*/
@@ -83,6 +147,11 @@ export function getDeadlineDisplayStatus(
): DeadlineDisplayStatus {
const def = getDeadlineType(deadline.typeId);
// Paused — waiting for completions
if (deadline.pausedAt && deadline.resolution === "pending") {
return { label: "Suspendat", variant: "blue", daysRemaining: null };
}
// Already resolved
if (deadline.resolution !== "pending") {
if (deadline.resolution === "aprobat-tacit") {