feat: "Heute" start page with one next step, clear task states and motion levels
Start page (AuDHD-oriented, per the agreed "Heute zuerst" layout): - One highlighted next step: started work first (easy re-entry after an interruption), then overdue, due today, then urgent undated tasks. Primary action "Erledigt", secondary "Beginnen", link to the related colony/apiary/queen. - "Außerdem heute": at most five further tasks with a large honey-cell checkbox; undated automatic suggestions are included so a queenless colony does not sit unseen in the full list. - Completing is the one authored motion moment: the cell fills, the tick draws, the row folds away and the next step arrives with a short focus pull. Every completion can be undone from the toast (10 s), restoring the previous status. - Status is shown as icon plus word (never colour alone); tasks created by automation carry a "Vorschlag" badge, recurring and inspection tasks their own - a suggestion never looks like work already done. - Friendly empty, loading and error states; key figures moved into a compact overview below; overdue tasks are no longer listed twice. Motion: per-device setting Voll / Dezent / Aus in the profile (default Voll); the OS reduce-motion setting always lowers Voll to Dezent. New 44 px "touch" button size for gloves at the apiary; themed text selection and caret. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
5c2ea7bf1e
commit
f9374a135a
14 files changed
+1104
-112
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import { CheckCircle2, Circle, ClipboardList, PlayCircle, Repeat, Sparkles, XCircle, type LucideIcon } from "lucide-react";
|
||||
import { Task, TaskStatus } from "@/lib/tasks/types";
|
||||
import { TASK_STATUS_LABELS } from "@/lib/tasks/labels";
|
||||
import { DueState } from "@/lib/tasks/today";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const STATUS_META: Record<TaskStatus, { icon: LucideIcon; className: string }> = {
|
||||
OPEN: { icon: Circle, className: "bg-muted text-foreground/80" },
|
||||
IN_PROGRESS: {
|
||||
icon: PlayCircle,
|
||||
className: "bg-[color-mix(in_oklch,var(--chart-5)_16%,var(--card))] text-foreground",
|
||||
},
|
||||
DONE: { icon: CheckCircle2, className: "bg-success/15 text-foreground" },
|
||||
CANCELLED: { icon: XCircle, className: "bg-muted text-muted-foreground line-through" },
|
||||
};
|
||||
|
||||
/** Status as icon AND word - never colour alone. */
|
||||
export function TaskStatusPill({ status, className }: { status: TaskStatus; className?: string }) {
|
||||
const meta = STATUS_META[status];
|
||||
const Icon = meta.icon;
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium",
|
||||
meta.className,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Icon className="size-3.5" aria-hidden />
|
||||
{TASK_STATUS_LABELS[status]}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a task came from. An automatic task is a suggestion to look at
|
||||
* something - it says nothing about work having been done.
|
||||
*/
|
||||
export function TaskSourceBadge({
|
||||
task,
|
||||
}: {
|
||||
task: Pick<Task, "sourceAutomationTrigger" | "sourceTemplateId" | "sourceInspectionId">;
|
||||
}) {
|
||||
const source = task.sourceAutomationTrigger
|
||||
? { icon: Sparkles, label: "Vorschlag", title: "Automatisch vorgeschlagen – noch nichts erledigt" }
|
||||
: task.sourceTemplateId
|
||||
? { icon: Repeat, label: "Wiederkehrend", title: "Aus einer wiederkehrenden Vorlage" }
|
||||
: task.sourceInspectionId
|
||||
? { icon: ClipboardList, label: "Aus Durchsicht", title: "Bei einer Durchsicht angelegt" }
|
||||
: null;
|
||||
if (!source) return null;
|
||||
const Icon = source.icon;
|
||||
return (
|
||||
<span
|
||||
title={source.title}
|
||||
className="inline-flex items-center gap-1 rounded-full border border-dashed border-primary/45 px-2 py-0.5 text-xs text-foreground/80"
|
||||
>
|
||||
<Icon className="size-3.5 text-primary" aria-hidden />
|
||||
{source.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const DUE_LABEL: Record<Exclude<DueState, "none">, string> = {
|
||||
overdue: "Überfällig",
|
||||
today: "Heute",
|
||||
upcoming: "Geplant",
|
||||
};
|
||||
|
||||
export function DueChip({ state, dueDate }: { state: DueState; dueDate: string | null }) {
|
||||
if (state === "none" || !dueDate) {
|
||||
return <span className="text-xs text-muted-foreground">ohne Termin</span>;
|
||||
}
|
||||
const date = new Date(dueDate).toLocaleDateString("de-DE", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium",
|
||||
state === "overdue" && "bg-warning/20 text-foreground",
|
||||
state === "today" && "bg-primary/12 text-foreground",
|
||||
state === "upcoming" && "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{DUE_LABEL[state]}
|
||||
{state !== "today" ? <span className="font-normal">· {date}</span> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user