feat: timeline milestones for deadlines, auto-close reply entries, cleanup

- Entry created via "Inchide" flow now gets status "inchis" with closureInfo
- New DeadlineTimeline component: main deadlines as cards with progress bar,
  auto-tracked sub-deadlines as milestone dots on horizontal timeline
- Auto-tracked deadlines hidden from dashboard when user deadlines exist
- Verification milestone shows "Expirat — nu se mai pot solicita clarificari"
- Parent closureInfo now includes linkedEntryId/Number of the closing act
- Removed orphaned deadline-table.tsx and use-deadline-filters.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-11 23:51:27 +02:00
parent 5b18cce5a3
commit c5112dbb3d
6 changed files with 552 additions and 210 deletions
@@ -1,127 +0,0 @@
'use client';
import { CheckCircle2 } from 'lucide-react';
import { Badge } from '@/shared/components/ui/badge';
import { Button } from '@/shared/components/ui/button';
import type { TrackedDeadline, RegistryEntry } from '../types';
import type { DeadlineDisplayStatus } from '../services/deadline-service';
import { getDeadlineType, CATEGORY_LABELS } from '../services/deadline-catalog';
import { cn } from '@/shared/lib/utils';
interface DeadlineRow {
deadline: TrackedDeadline;
entry: RegistryEntry;
status: DeadlineDisplayStatus;
}
interface DeadlineTableProps {
rows: DeadlineRow[];
onResolve: (entryId: string, deadline: TrackedDeadline) => void;
}
const BADGE_CLASSES: Record<string, string> = {
green: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 border-0',
yellow: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 border-0',
red: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 border-0',
blue: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 border-0',
gray: 'bg-muted text-muted-foreground border-0',
};
const ROW_CLASSES: Record<string, string> = {
red: 'bg-destructive/5',
yellow: 'bg-yellow-50/50 dark:bg-yellow-950/10',
blue: 'bg-blue-50/50 dark:bg-blue-950/10',
};
export function DeadlineTable({ rows, onResolve }: DeadlineTableProps) {
if (rows.length === 0) {
return (
<p className="py-8 text-center text-sm text-muted-foreground">
Niciun termen legal urmărit.
</p>
);
}
return (
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/40">
<th className="px-3 py-2 text-left font-medium">Nr. înreg.</th>
<th className="px-3 py-2 text-left font-medium">Categorie</th>
<th className="px-3 py-2 text-left font-medium">Tip termen</th>
<th className="px-3 py-2 text-left font-medium">Data start</th>
<th className="px-3 py-2 text-left font-medium">Termen limită</th>
<th className="px-3 py-2 text-left font-medium">Zile</th>
<th className="px-3 py-2 text-left font-medium">Status</th>
<th className="px-3 py-2 text-right font-medium">Acțiuni</th>
</tr>
</thead>
<tbody>
{rows.map((row) => {
const def = getDeadlineType(row.deadline.typeId);
return (
<tr
key={row.deadline.id}
className={cn(
'border-b transition-colors hover:bg-muted/20',
ROW_CLASSES[row.status.variant] ?? '',
)}
>
<td className="px-3 py-2 font-mono text-xs whitespace-nowrap">{row.entry.number}</td>
<td className="px-3 py-2 text-xs">
{def ? CATEGORY_LABELS[def.category] : '—'}
</td>
<td className="px-3 py-2 text-xs">
<span className="font-medium">{def?.label ?? row.deadline.typeId}</span>
{def?.dayType === 'working' && (
<span className="ml-1 text-muted-foreground">(lucr.)</span>
)}
</td>
<td className="px-3 py-2 text-xs whitespace-nowrap">{formatDate(row.deadline.startDate)}</td>
<td className="px-3 py-2 text-xs whitespace-nowrap font-medium">{formatDate(row.deadline.dueDate)}</td>
<td className="px-3 py-2 text-xs whitespace-nowrap">
{row.status.daysRemaining !== null ? (
<span className={cn(row.status.daysRemaining < 0 && 'text-destructive font-medium')}>
{row.status.daysRemaining < 0
? `${Math.abs(row.status.daysRemaining)}z depășit`
: `${row.status.daysRemaining}z`}
</span>
) : (
'—'
)}
</td>
<td className="px-3 py-2">
<Badge className={cn('text-[10px]', BADGE_CLASSES[row.status.variant] ?? '')}>
{row.status.label}
</Badge>
</td>
<td className="px-3 py-2 text-right">
{row.deadline.resolution === 'pending' && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-green-600"
onClick={() => onResolve(row.entry.id, row.deadline)}
title="Rezolvă"
>
<CheckCircle2 className="h-3.5 w-3.5" />
</Button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</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;
}
}
@@ -0,0 +1,515 @@
"use client";
import { useMemo } from "react";
import { Badge } from "@/shared/components/ui/badge";
import type { TrackedDeadline } from "../types";
import { getDeadlineType } from "../services/deadline-catalog";
import { getDeadlineDisplayStatus } from "../services/deadline-service";
import { cn } from "@/shared/lib/utils";
// ── Types ──
interface TimelineMilestone {
deadline: TrackedDeadline;
label: string;
description: string;
dueDate: string;
/** Position on the timeline (0..1) */
position: number;
/** Status of this milestone */
status: "completed" | "active" | "expired" | "upcoming" | "background";
/** Human-readable status text */
statusText: string;
/** Whether this is the main (user-created) deadline or an auto-tracked milestone */
isMain: boolean;
/** Days remaining (negative = overdue) */
daysRemaining: number;
}
interface DeadlineTimelineProps {
/** All tracked deadlines on this entry */
deadlines: TrackedDeadline[];
}
// ── Build timeline from deadlines ──
function buildTimeline(deadlines: TrackedDeadline[]): {
mainDeadlines: TimelineMilestone[];
groups: Map<string, TimelineMilestone[]>;
} {
const mainDeadlines: TimelineMilestone[] = [];
// Group auto-tracked by their chain parent (or by category proximity to a main deadline)
const groups = new Map<string, TimelineMilestone[]>();
const now = new Date();
now.setHours(0, 0, 0, 0);
// Separate main vs auto-tracked deadlines
const mainDls: TrackedDeadline[] = [];
const autoDls: TrackedDeadline[] = [];
for (const dl of deadlines) {
const def = getDeadlineType(dl.typeId);
if (def?.backgroundOnly) continue; // Skip background-only entirely
if (def?.autoTrack) {
autoDls.push(dl);
} else {
mainDls.push(dl);
}
}
// For each main deadline, find its associated milestones
for (const main of mainDls) {
const def = getDeadlineType(main.typeId);
const mainStart = new Date(main.startDate);
mainStart.setHours(0, 0, 0, 0);
const mainDue = new Date(main.dueDate);
mainDue.setHours(0, 0, 0, 0);
const totalSpan = Math.max(1, mainDue.getTime() - mainStart.getTime());
const mainDaysRemaining = Math.ceil(
(mainDue.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
);
const mainStatus = getMainStatus(main, mainDaysRemaining);
mainDeadlines.push({
deadline: main,
label: def?.label ?? main.typeId,
description: def?.description ?? "",
dueDate: main.dueDate,
position: 1,
status: mainStatus,
statusText: getStatusText(mainStatus, mainDaysRemaining),
isMain: true,
daysRemaining: mainDaysRemaining,
});
// Find auto-tracked milestones that fall within this main deadline's range
const milestones: TimelineMilestone[] = [];
for (const auto of autoDls) {
const autoDef = getDeadlineType(auto.typeId);
if (!autoDef) continue;
// Check if this auto-tracked deadline is related (same start date or chain parent in this group)
const autoStart = new Date(auto.startDate);
autoStart.setHours(0, 0, 0, 0);
const autoDue = new Date(auto.dueDate);
autoDue.setHours(0, 0, 0, 0);
// Only include if it starts from the same point or is within the main's time range
const startsNearMain =
Math.abs(autoStart.getTime() - mainStart.getTime()) <
2 * 24 * 60 * 60 * 1000;
const isChainRelated = auto.chainParentId
? deadlines.some(
(d) =>
d.id === auto.chainParentId &&
(d.typeId === main.typeId ||
d.chainParentId === main.id),
)
: false;
if (!startsNearMain && !isChainRelated) continue;
const pos = Math.min(
0.95,
Math.max(
0.05,
(autoDue.getTime() - mainStart.getTime()) / totalSpan,
),
);
const autoDaysRemaining = Math.ceil(
(autoDue.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
);
const autoStatus = getAutoStatus(auto, autoDaysRemaining, autoDef.id);
milestones.push({
deadline: auto,
label: autoDef.label,
description: getExpiredDescription(autoDef.id, autoStatus),
dueDate: auto.dueDate,
position: pos,
status: autoStatus,
statusText: getAutoStatusText(autoDef.id, autoStatus, autoDaysRemaining),
isMain: false,
daysRemaining: autoDaysRemaining,
});
}
// Sort milestones by position
milestones.sort((a, b) => a.position - b.position);
if (milestones.length > 0) {
groups.set(main.id, milestones);
}
}
// Handle auto-tracked deadlines that aren't associated with any main deadline
const associatedAutoIds = new Set<string>();
for (const ms of groups.values()) {
for (const m of ms) {
associatedAutoIds.add(m.deadline.id);
}
}
const orphanedAuto = autoDls.filter(
(dl) => !associatedAutoIds.has(dl.id),
);
if (orphanedAuto.length > 0 && mainDls.length === 0) {
// If there are ONLY auto-tracked deadlines (no main), show them as standalone milestones
for (const auto of orphanedAuto) {
const autoDef = getDeadlineType(auto.typeId);
if (!autoDef) continue;
const autoDue = new Date(auto.dueDate);
autoDue.setHours(0, 0, 0, 0);
const daysRemaining = Math.ceil(
(autoDue.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
);
const status = getMainStatus(auto, daysRemaining);
mainDeadlines.push({
deadline: auto,
label: autoDef.label,
description: autoDef.description,
dueDate: auto.dueDate,
position: 1,
status,
statusText: getStatusText(status, daysRemaining),
isMain: true,
daysRemaining,
});
}
}
return { mainDeadlines, groups };
}
function getMainStatus(
dl: TrackedDeadline,
daysRemaining: number,
): TimelineMilestone["status"] {
if (dl.resolution === "completed") return "completed";
if (dl.resolution !== "pending") return "completed";
if (daysRemaining < 0) return "expired";
if (daysRemaining <= 5) return "active";
return "upcoming";
}
function getAutoStatus(
dl: TrackedDeadline,
daysRemaining: number,
typeId: string,
): TimelineMilestone["status"] {
if (dl.resolution !== "pending") return "completed";
if (daysRemaining < 0) {
// For verification-type deadlines, "expired" means the window has passed
if (typeId.includes("verificare")) return "expired";
return "expired";
}
if (daysRemaining <= 2) return "active";
return "upcoming";
}
function getStatusText(
status: TimelineMilestone["status"],
daysRemaining: number,
): string {
switch (status) {
case "completed":
return "Finalizat";
case "expired":
return daysRemaining < 0
? `${Math.abs(daysRemaining)}z depasit`
: "Depasit";
case "active":
return daysRemaining === 0
? "Astazi"
: `${daysRemaining}z ramase`;
case "upcoming":
return `${daysRemaining}z ramase`;
default:
return "";
}
}
function getAutoStatusText(
typeId: string,
status: TimelineMilestone["status"],
daysRemaining: number,
): string {
if (status === "completed") return "Finalizat";
if (status === "expired") {
if (typeId.includes("verificare")) {
return "Expirat — nu se mai pot solicita clarificari";
}
return `Depasit cu ${Math.abs(daysRemaining)}z`;
}
if (daysRemaining === 0) return "Astazi";
return `${daysRemaining}z`;
}
function getExpiredDescription(
typeId: string,
status: TimelineMilestone["status"],
): string {
if (status === "expired" && typeId.includes("verificare")) {
return "Posibilitatea de a solicita clarificari sau de a returna documentatia a expirat.";
}
return "";
}
function formatShortDate(iso: string): string {
try {
const d = new Date(iso);
return `${String(d.getDate()).padStart(2, "0")}.${String(d.getMonth() + 1).padStart(2, "0")}`;
} catch {
return iso;
}
}
// ── Component ──
export function DeadlineTimeline({ deadlines }: DeadlineTimelineProps) {
const { mainDeadlines, groups } = useMemo(
() => buildTimeline(deadlines),
[deadlines],
);
if (mainDeadlines.length === 0) return null;
return (
<div className="space-y-4">
{mainDeadlines.map((main) => {
const milestones = groups.get(main.deadline.id) ?? [];
const displayStatus = getDeadlineDisplayStatus(main.deadline);
return (
<div key={main.deadline.id} className="space-y-2">
{/* Main deadline header */}
<div
className={cn(
"rounded-lg border px-3 py-2",
main.status === "expired"
? "border-destructive/40 bg-destructive/5"
: main.status === "active"
? "border-amber-300 bg-amber-50/30 dark:border-amber-800 dark:bg-amber-950/20"
: main.status === "completed"
? "border-muted bg-muted/20"
: "border-border",
)}
>
<div className="flex items-center justify-between">
<span className="text-xs font-medium">{main.label}</span>
<Badge
variant="outline"
className={cn(
"text-[10px]",
displayStatus.variant === "red" &&
"border-red-400 text-red-700 dark:text-red-400",
displayStatus.variant === "yellow" &&
"border-amber-400 text-amber-700 dark:text-amber-400",
displayStatus.variant === "green" &&
"border-green-400 text-green-700 dark:text-green-400",
displayStatus.variant === "blue" &&
"border-blue-400 text-blue-700 dark:text-blue-400",
displayStatus.variant === "gray" &&
"border-muted text-muted-foreground",
)}
>
{displayStatus.label}
</Badge>
</div>
<div className="mt-1 flex items-center gap-3 text-[10px] text-muted-foreground">
<span>Start: {formatShortDate(main.deadline.startDate)}</span>
<span>Scadent: {formatShortDate(main.dueDate)}</span>
{main.daysRemaining > 0 && main.status !== "completed" && (
<span
className={cn(
"font-medium",
main.daysRemaining <= 5
? "text-amber-600 dark:text-amber-400"
: "text-foreground",
)}
>
{main.daysRemaining}z ramase
</span>
)}
{main.daysRemaining < 0 && main.status === "expired" && (
<span className="font-medium text-destructive">
{Math.abs(main.daysRemaining)}z depasit
</span>
)}
</div>
{main.deadline.resolutionNote && (
<p className="mt-1 text-[10px] text-muted-foreground italic">
{main.deadline.resolutionNote}
</p>
)}
{/* Progress bar */}
{main.status !== "completed" && (
<ProgressBar deadline={main.deadline} status={main.status} />
)}
{/* Milestones timeline */}
{milestones.length > 0 && (
<MilestoneTimeline
milestones={milestones}
mainDeadline={main.deadline}
/>
)}
</div>
</div>
);
})}
</div>
);
}
// ── Progress bar ──
function ProgressBar({
deadline,
status,
}: {
deadline: TrackedDeadline;
status: TimelineMilestone["status"];
}) {
const now = new Date();
now.setHours(0, 0, 0, 0);
const start = new Date(deadline.startDate);
start.setHours(0, 0, 0, 0);
const due = new Date(deadline.dueDate);
due.setHours(0, 0, 0, 0);
const total = Math.max(1, due.getTime() - start.getTime());
const elapsed = now.getTime() - start.getTime();
const pct = Math.min(100, Math.max(0, (elapsed / total) * 100));
return (
<div className="mt-2 h-1.5 rounded-full bg-muted">
<div
className={cn(
"h-1.5 rounded-full transition-all",
status === "expired"
? "bg-red-500"
: status === "active"
? "bg-amber-500"
: "bg-green-500",
)}
style={{ width: `${pct}%` }}
/>
</div>
);
}
// ── Milestone timeline (horizontal dots along the progress bar) ──
function MilestoneTimeline({
milestones,
mainDeadline,
}: {
milestones: TimelineMilestone[];
mainDeadline: TrackedDeadline;
}) {
return (
<div className="mt-3 space-y-0">
{/* Timeline track */}
<div className="relative h-6">
{/* Base line */}
<div className="absolute top-[10px] left-0 right-0 h-px bg-border" />
{/* Start marker */}
<div className="absolute top-[7px] left-0 h-1.5 w-1.5 rounded-full bg-muted-foreground" />
{/* End marker (main deadline) */}
<div className="absolute top-[7px] right-0 h-1.5 w-1.5 rounded-full bg-muted-foreground" />
{/* Milestone dots */}
{milestones.map((ms) => (
<div
key={ms.deadline.id}
className="absolute top-0"
style={{ left: `${ms.position * 100}%` }}
title={`${ms.label}: ${ms.statusText}`}
>
<div
className={cn(
"h-5 w-5 -ml-2.5 rounded-full border-2 flex items-center justify-center",
ms.status === "completed"
? "border-green-500 bg-green-100 dark:bg-green-900/40"
: ms.status === "expired"
? "border-muted-foreground/40 bg-muted"
: ms.status === "active"
? "border-amber-500 bg-amber-100 dark:bg-amber-900/40"
: "border-border bg-background",
)}
>
{ms.status === "completed" && (
<span className="text-[8px] text-green-700 dark:text-green-300">
</span>
)}
{ms.status === "expired" && (
<span className="text-[8px] text-muted-foreground"></span>
)}
</div>
</div>
))}
</div>
{/* Milestone labels below */}
<div className="space-y-1.5 pt-1">
{milestones.map((ms) => (
<div key={ms.deadline.id} className="flex items-start gap-2 text-[10px]">
<span
className={cn(
"mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full",
ms.status === "completed"
? "bg-green-500"
: ms.status === "expired"
? "bg-muted-foreground/40"
: ms.status === "active"
? "bg-amber-500"
: "bg-border",
)}
/>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span
className={cn(
"font-medium",
ms.status === "expired" && "text-muted-foreground line-through",
ms.status === "completed" && "text-muted-foreground",
)}
>
{ms.label}
</span>
<span className="text-muted-foreground">
{formatShortDate(ms.dueDate)}
</span>
<span
className={cn(
ms.status === "expired"
? "text-muted-foreground italic"
: ms.status === "active"
? "text-amber-600 dark:text-amber-400 font-medium"
: ms.status === "completed"
? "text-green-600 dark:text-green-400"
: "text-muted-foreground",
)}
>
{ms.statusText}
</span>
</div>
{ms.description && (
<p className="text-muted-foreground italic">{ms.description}</p>
)}
</div>
</div>
))}
</div>
</div>
);
}
@@ -168,18 +168,37 @@ export function RegistraturaModule() {
);
}
const closureInfo: ClosureInfo = {
const parentClosureInfo: ClosureInfo = {
resolution: "finalizat",
reason: "Inchis prin inregistrare conex",
closedBy: "",
closedAt: new Date().toISOString(),
hadActiveDeadlines: resolvable.length > 0,
linkedEntryId: newEntry?.id,
linkedEntryNumber: newEntry?.number,
};
await updateEntry(closesEntryId, {
closureInfo,
closureInfo: parentClosureInfo,
trackedDeadlines: updatedDeadlines,
});
await closeEntry(closesEntryId, false);
// Also close the new entry (act administrativ) — it was created to close the parent
if (newEntry) {
const replyClosureInfo: ClosureInfo = {
resolution: "finalizat",
reason: `Act administrativ emis — inchide ${parentEntry.number}`,
closedBy: "",
closedAt: new Date().toISOString(),
hadActiveDeadlines: false,
linkedEntryId: parentEntry.id,
linkedEntryNumber: parentEntry.number,
};
await updateEntry(newEntry.id, {
status: "inchis",
closureInfo: replyClosureInfo,
});
}
}
setClosesEntryId(null);
}
@@ -52,6 +52,7 @@ import { findAuthorityForContact } from "../services/authority-catalog";
import { computeTransmissionStatus } from "../services/deadline-service";
import { StatusMonitorConfig } from "./status-monitor-config";
import { FlowDiagram } from "./flow-diagram";
import { DeadlineTimeline } from "./deadline-timeline";
interface RegistryEntryDetailProps {
entry: RegistryEntry | null;
@@ -584,58 +585,10 @@ export function RegistryEntryDetail({
</DetailSection>
)}
{/* ── Legal deadlines ── */}
{/* ── Legal deadlines (timeline view) ── */}
{(entry.trackedDeadlines ?? []).length > 0 && (
<DetailSection
title={`Termene legale (${entry.trackedDeadlines!.length})`}
>
<div className="space-y-2">
{entry.trackedDeadlines!.map((dl) => (
<div
key={dl.id}
className={cn(
"rounded-md border px-3 py-2 text-xs",
dl.resolution === "pending" &&
new Date(dl.dueDate) < new Date()
? "border-destructive/50 bg-destructive/5"
: dl.resolution === "pending"
? "border-amber-300 dark:border-amber-700 bg-amber-50/50 dark:bg-amber-950/20"
: "border-muted",
)}
>
<div className="flex items-center justify-between">
<span className="font-medium">{dl.typeId}</span>
<Badge
variant="outline"
className={cn(
"text-[10px]",
dl.resolution === "pending" &&
"border-amber-400 text-amber-700 dark:text-amber-300",
dl.resolution === "completed" &&
"border-green-400 text-green-700 dark:text-green-300",
)}
>
{DEADLINE_RES_LABELS[dl.resolution] ?? dl.resolution}
</Badge>
</div>
<div className="flex gap-4 mt-1 text-muted-foreground">
<span>
<Calendar className="mr-0.5 inline h-2.5 w-2.5" />
Start: {formatDate(dl.startDate)}
</span>
<span>
<Clock className="mr-0.5 inline h-2.5 w-2.5" />
Scadent: {formatDate(dl.dueDate)}
</span>
</div>
{dl.resolutionNote && (
<p className="mt-1 text-muted-foreground">
{dl.resolutionNote}
</p>
)}
</div>
))}
</div>
<DetailSection title="Termene legale">
<DeadlineTimeline deadlines={entry.trackedDeadlines!} />
</DetailSection>
)}
@@ -1,28 +0,0 @@
'use client';
import { useState, useCallback } from 'react';
import type { DeadlineCategory, DeadlineResolution } from '../types';
export interface DeadlineFilters {
category: DeadlineCategory | 'all';
resolution: DeadlineResolution | 'all';
urgentOnly: boolean;
}
export function useDeadlineFilters() {
const [filters, setFilters] = useState<DeadlineFilters>({
category: 'all',
resolution: 'all',
urgentOnly: false,
});
const updateFilter = useCallback(<K extends keyof DeadlineFilters>(key: K, value: DeadlineFilters[K]) => {
setFilters((prev) => ({ ...prev, [key]: value }));
}, []);
const resetFilters = useCallback(() => {
setFilters({ category: 'all', resolution: 'all', urgentOnly: false });
}, []);
return { filters, updateFilter, resetFilters };
}
@@ -275,14 +275,24 @@ export function groupDeadlinesByEntry(
}
}
// Check if there are any user-selectable (non-autoTrack) deadlines
const hasUserDeadlines = deadlines.some((dl) => {
const d = getDeadlineType(dl.typeId);
return !d?.autoTrack && !d?.backgroundOnly && !dl.chainParentId;
});
// Second pass: build main deadline items
for (const dl of deadlines) {
const def = getDeadlineType(dl.typeId);
if (def?.backgroundOnly) continue;
if (dl.chainParentId) continue; // chain children are nested
// Auto-tracked without chain parent: treat as main if pending, skip if resolved
if (def?.autoTrack && dl.resolution !== "pending") continue;
// Auto-tracked: hide when there are user-selectable deadlines (they appear as milestones)
// Only show auto-tracked as standalone when there are NO user deadlines on the entry
if (def?.autoTrack) {
if (hasUserDeadlines) continue;
if (dl.resolution !== "pending") continue;
}
mainItems.push({
deadline: dl,