Files
tickettracker/frontend/src/views/TodosPage.tsx
T

103 lines
3.9 KiB
TypeScript

import { ClipboardList } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { HelpLink } from "@/components/HelpLink";
import { TodoPanel } from "@/components/TodoPanel";
import { getTodos } from "../api";
import type { TicketTodo, TodoStatus } from "../types";
type TodosPageProps = {
onNavigate: (to: string) => void;
};
export function TodosPage({ onNavigate }: TodosPageProps) {
const [todos, setTodos] = useState<TicketTodo[]>([]);
const [status, setStatus] = useState<TodoStatus | "all">("open");
const [loading, setLoading] = useState(true);
async function load() {
setLoading(true);
try {
const result = await getTodos(status);
setTodos(result.todos);
} catch (error) {
toast.error("ToDos konnten nicht geladen werden", {
description: error instanceof Error ? error.message : "Unbekannter Fehler"
});
} finally {
setLoading(false);
}
}
useEffect(() => {
void load();
}, [status]);
const counts = useMemo(
() => ({
open: todos.filter((todo) => todo.status === "open").length,
done: todos.filter((todo) => todo.status === "done").length,
urgent: todos.filter((todo) => todo.status === "open" && todo.priority === "urgent").length
}),
[todos]
);
return (
<div className="space-y-4">
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
<div>
<h2 className="text-xl font-semibold tracking-normal sm:text-2xl">ToDos</h2>
<p className="text-sm text-muted-foreground">Offene Aufgaben aus Projekten und normalen Tickets an einem Ort.</p>
</div>
<div className="grid gap-2 text-sm sm:grid-cols-3">
<div className="rounded-md border bg-muted/20 px-3 py-2">
<span className="block text-muted-foreground">Offen</span>
<span className="font-semibold">{counts.open}</span>
</div>
<div className="rounded-md border bg-muted/20 px-3 py-2">
<span className="block text-muted-foreground">Dringend</span>
<span className="font-semibold">{counts.urgent}</span>
</div>
<div className="rounded-md border bg-muted/20 px-3 py-2">
<span className="block text-muted-foreground">Erledigt</span>
<span className="font-semibold">{counts.done}</span>
</div>
</div>
</div>
<Card>
<CardHeader className="p-4">
<div className="flex items-start justify-between gap-3">
<div>
<CardTitle className="flex items-center gap-2">
<ClipboardList className="size-5 text-muted-foreground" />
Aufgabenliste
</CardTitle>
<CardDescription>ToDos werden im jeweiligen Ticket angelegt und hier gesammelt angezeigt.</CardDescription>
</div>
<HelpLink anchor="todos" label="Hilfe zu ToDos" />
</div>
<div className="flex flex-wrap gap-2 pt-2">
{(["open", "all", "done"] as const).map((value) => (
<button
key={value}
type="button"
className={`rounded-md border px-3 py-1.5 text-sm ${status === value ? "bg-primary text-primary-foreground" : "bg-background hover:bg-muted"}`}
onClick={() => setStatus(value)}
>
{value === "open" ? "Offen" : value === "done" ? "Erledigt" : "Alle"}
</button>
))}
</div>
</CardHeader>
<CardContent className="px-4 pb-4">
<TodoPanel todos={todos} showCreate={false} showTicketMeta onNavigate={onNavigate} onChanged={load} />
{!loading && todos.length === 0 ? null : null}
</CardContent>
</Card>
</div>
);
}