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:
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user