Files
ApiCore/ApiCore-Frontend/src/components/task-status.tsx
T
Nick SzaunigandClaude Opus 5.5 f9374a135a 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]>
2026-09-27 13:00:44 +02:00

93 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}