feat(tasks): stato "In revisione", con gli stati finalmente in un posto solo
Mancava il modo di dire "finito, ma da controllare prima di consegnarlo". Il nuovo stato sta fra "In corso" e "Fatto" ed e' visibile anche al cliente: il lavoro c'e' ed e' in controllo qualita', non e' fermo. Il costo non era la logica ma la dispersione: tre letterali ricopiati a mano in otto file, in tre forme diverse (allow-list a runtime, union TS, colonne kanban, opzioni della select) e nessun CHECK in DB a tenerli insieme. Invece di modificarne quattordici occorrenze, tutto deriva da TASK_STATUSES in src/lib/task-status.ts: la prossima aggiunta costa una riga. Due punti perdevano dati in silenzio, ed erano il vero motivo per centralizzare: - recomputePhaseStatus considerava "iniziato" solo in_progress|done, come lista. Una fase con tutti i task in revisione non rientrava ne' in allDone ne' in anyActive e retrocedeva a "upcoming": si leggeva "non iniziata" quando era quasi finita. Ora e' la negazione di "todo", e regge anche il prossimo stato. - ClientKanban ripartiva i task con un oggetto a tre chiavi fisse, non derivato dalle colonne: un task fuori da quelle spariva da ogni colonna e da ogni contatore, e il cliente ne vedeva meno di quanti ce n'erano, senza errore. Chiuso anche il cast non verificato al confine del portale (page.tsx), che era la causa a monte di entrambi: ora ci passa normalizeTaskStatus. Nessuna migration: tasks.status e' text senza CHECK, le righe esistenti valgono. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,16 @@
|
||||
|
||||
import type { ClientView } from "@/lib/client-view";
|
||||
import { ApproveButton } from "@/components/client/ApproveButton";
|
||||
import { TASK_STATUS_LABELS, TASK_STATUSES, type TaskStatus } from "@/lib/task-status";
|
||||
|
||||
type Task = ClientView["phases"][number]["tasks"][number] & {
|
||||
phaseTitle: string;
|
||||
};
|
||||
|
||||
const COLUMNS: { id: "todo" | "in_progress" | "done"; label: string }[] = [
|
||||
{ id: "todo", label: "Da fare" },
|
||||
{ id: "in_progress", label: "In corso" },
|
||||
{ id: "done", label: "Fatto" },
|
||||
];
|
||||
const COLUMNS: { id: TaskStatus; label: string }[] = TASK_STATUSES.map((id) => ({
|
||||
id,
|
||||
label: TASK_STATUS_LABELS[id],
|
||||
}));
|
||||
|
||||
function TaskCard({ task, token }: { task: Task; token: string }) {
|
||||
return (
|
||||
@@ -46,14 +46,19 @@ export function ClientKanban({ phases, token }: { phases: ClientView["phases"];
|
||||
phase.tasks.map((task) => ({ ...task, phaseTitle: phase.title }))
|
||||
);
|
||||
|
||||
const tasksByStatus = {
|
||||
todo: allTasks.filter((t) => t.status === "todo"),
|
||||
in_progress: allTasks.filter((t) => t.status === "in_progress"),
|
||||
done: allTasks.filter((t) => t.status === "done"),
|
||||
};
|
||||
// Derived from the statuses, not a hand-written object: as three fixed keys it
|
||||
// dropped any task outside them from every column AND every counter, so the
|
||||
// client silently saw fewer tasks than the project had.
|
||||
const tasksByStatus = COLUMNS.reduce(
|
||||
(acc, col) => {
|
||||
acc[col.id] = allTasks.filter((t) => t.status === col.id);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<TaskStatus, Task[]>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{COLUMNS.map((col) => (
|
||||
<div key={col.id} className="flex flex-col gap-4 rounded-xl border border-border-light bg-muted/60 p-4 min-h-[300px]">
|
||||
<div className="flex items-center justify-between border-b border-border-light pb-2">
|
||||
|
||||
Reference in New Issue
Block a user