diff --git a/src/modules/registratura/components/deadline-timeline.tsx b/src/modules/registratura/components/deadline-timeline.tsx index 020127f..86ad5d8 100644 --- a/src/modules/registratura/components/deadline-timeline.tsx +++ b/src/modules/registratura/components/deadline-timeline.tsx @@ -1,7 +1,9 @@ "use client"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { Badge } from "@/shared/components/ui/badge"; +import { Button } from "@/shared/components/ui/button"; +import { CheckCircle2 } from "lucide-react"; import type { TrackedDeadline } from "../types"; import { getDeadlineType } from "../services/deadline-catalog"; import { getDeadlineDisplayStatus } from "../services/deadline-service"; @@ -29,6 +31,8 @@ interface TimelineMilestone { interface DeadlineTimelineProps { /** All tracked deadlines on this entry */ deadlines: TrackedDeadline[]; + /** Callback to resolve a sub-deadline inline (id, note) */ + onResolveInline?: (deadlineId: string, note: string) => void; } // ── Build timeline from deadlines ── @@ -286,7 +290,7 @@ function formatFullDate(iso: string): string { // ── Component ── -export function DeadlineTimeline({ deadlines }: DeadlineTimelineProps) { +export function DeadlineTimeline({ deadlines, onResolveInline }: DeadlineTimelineProps) { const { mainDeadlines, groups } = useMemo( () => buildTimeline(deadlines), [deadlines], @@ -395,6 +399,7 @@ export function DeadlineTimeline({ deadlines }: DeadlineTimelineProps) { )} @@ -449,10 +454,14 @@ function ProgressBar({ function MilestoneTimeline({ milestones, mainDeadline, + onResolveInline, }: { milestones: TimelineMilestone[]; mainDeadline: TrackedDeadline; + onResolveInline?: (deadlineId: string, note: string) => void; }) { + const [resolvingId, setResolvingId] = useState(null); + const [resolveNote, setResolveNote] = useState(""); return ( {/* Timeline track */} @@ -501,54 +510,125 @@ function MilestoneTimeline({ {/* Milestone labels below */} - {milestones.map((ms) => ( - - - - + {milestones.map((ms) => { + const canResolve = + onResolveInline && + ms.deadline.resolution === "pending" && + ms.status !== "expired"; + const isResolving = resolvingId === ms.deadline.id; + + return ( + + - {ms.label} - - - {formatShortDate(ms.dueDate)} - - + + + + {ms.label} + + + {formatShortDate(ms.dueDate)} + + + {ms.statusText} + + {canResolve && !isResolving && ( + { + setResolvingId(ms.deadline.id); + setResolveNote(""); + }} + className="ml-1 flex items-center gap-0.5 text-green-600 hover:text-green-500 dark:text-green-400" + title="Rezolva rapid" + > + + + )} + + {ms.description && ( + {ms.description} )} - > - {ms.statusText} - + - {ms.description && ( - {ms.description} + + {/* Inline resolve form */} + {isResolving && ( + + setResolveNote(e.target.value)} + className="flex-1 bg-transparent text-[10px] outline-none placeholder:text-muted-foreground/60" + autoFocus + onKeyDown={(e) => { + if (e.key === "Enter" && resolveNote.trim()) { + onResolveInline?.(ms.deadline.id, resolveNote.trim()); + setResolvingId(null); + setResolveNote(""); + } + if (e.key === "Escape") { + setResolvingId(null); + setResolveNote(""); + } + }} + /> + { + onResolveInline?.(ms.deadline.id, resolveNote.trim()); + setResolvingId(null); + setResolveNote(""); + }} + > + OK + + { + setResolvingId(null); + setResolveNote(""); + }} + > + ✕ + + )} - - ))} + ); + })} ); diff --git a/src/modules/registratura/components/registratura-module.tsx b/src/modules/registratura/components/registratura-module.tsx index af1192b..81ab94e 100644 --- a/src/modules/registratura/components/registratura-module.tsx +++ b/src/modules/registratura/components/registratura-module.tsx @@ -617,6 +617,12 @@ export function RegistraturaModule() { onClose={handleCloseViaReply} onDelete={handleDelete} onReply={handleConex} + onResolveDeadline={async (entryId, deadlineId, resolution, note) => { + await resolveDeadline(entryId, deadlineId, resolution as DeadlineResolution, note); + // Refresh the viewing entry + const refreshed = await loadFullEntry(entryId); + if (refreshed) setViewingEntry(refreshed); + }} allEntries={allEntries} /> diff --git a/src/modules/registratura/components/registry-entry-detail.tsx b/src/modules/registratura/components/registry-entry-detail.tsx index 2233c27..60577c5 100644 --- a/src/modules/registratura/components/registry-entry-detail.tsx +++ b/src/modules/registratura/components/registry-entry-detail.tsx @@ -63,6 +63,8 @@ interface RegistryEntryDetailProps { onDelete: (id: string) => void; /** Create a new entry linked as reply (conex) to this entry */ onReply?: (entry: RegistryEntry) => void; + /** Resolve a tracked deadline inline */ + onResolveDeadline?: (entryId: string, deadlineId: string, resolution: string, note: string) => void; allEntries: RegistryEntry[]; } @@ -152,6 +154,7 @@ export function RegistryEntryDetail({ onClose, onDelete, onReply, + onResolveDeadline, allEntries, }: RegistryEntryDetailProps) { const [previewIndex, setPreviewIndex] = useState(null); @@ -595,7 +598,12 @@ export function RegistryEntryDetail({ {/* ── Legal deadlines (timeline view) ── */} {(entry.trackedDeadlines ?? []).length > 0 && ( - + { + onResolveDeadline(entry.id, deadlineId, "completed", note); + } : undefined} + /> )}
{ms.description}