Apply linen design system across app

This commit is contained in:
t.indorf
2026-05-20 22:47:52 +02:00
parent 99ff978116
commit 1c5d2f10ac
56 changed files with 4693 additions and 2154 deletions
+83 -70
View File
@@ -1,7 +1,7 @@
import Link from "next/link";
import { addWeeks, format, getISOWeek, startOfDay, startOfWeek } from "date-fns";
import { de } from "date-fns/locale";
import { CalendarRange, ClipboardList, Plus, Trash2 } from "lucide-react";
import { CalendarRange, Plus, Trash2 } from "lucide-react";
import { UserRole } from "@prisma/client";
import { Button } from "@/components/ui/button";
@@ -34,15 +34,16 @@ const MAX_WEEK_COUNT = 104;
export default async function DienstePage({
searchParams,
}: {
searchParams: { start?: string; weeks?: string };
searchParams: Promise<{ start?: string; weeks?: string }>;
}) {
const session = await requireRole([UserRole.ADMIN, UserRole.SUPERADMIN]);
if (!session.user.kitaId) {
throw new Error("Kein Mandant zugeordnet.");
}
const rangeStart = getRangeStart(searchParams.start);
const weekCount = getWeekCount(searchParams.weeks);
const resolvedSearchParams = await searchParams;
const rangeStart = getRangeStart(resolvedSearchParams.start);
const weekCount = getWeekCount(resolvedSearchParams.weeks);
const weeks = buildWeeks(rangeStart, weekCount);
const rangeEnd = startOfDay(new Date(weeks[weeks.length - 1].endDate));
@@ -83,77 +84,88 @@ export default async function DienstePage({
const dutyTypeDtos: DutyPlanDutyTypeDto[] = dutyTypes;
const familyDtos: DutyPlanFamilyDto[] = families;
const assignmentDtos: DutyPlanAssignmentDto[] = assignments.map((assignment) => ({
id: assignment.id,
dutyTypeId: assignment.dutyTypeId,
familyId: assignment.familyId,
startDate: toDateKey(assignment.startDate),
}));
const assignmentDtos: DutyPlanAssignmentDto[] = assignments.map(
(assignment) => ({
id: assignment.id,
dutyTypeId: assignment.dutyTypeId,
familyId: assignment.familyId,
startDate: toDateKey(assignment.startDate),
}),
);
return (
<div className="space-y-6 p-6">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="mx-auto max-w-[1280px] px-11 py-9 pb-16 max-[760px]:px-5">
<header className="mb-7 flex items-end justify-between gap-8 max-[900px]:flex-col max-[900px]:items-start">
<div>
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<ClipboardList className="h-4 w-4" />
Admin
</div>
<h1 className="mt-2 text-2xl font-bold tracking-tight">
Dienstplan-Manager
<p className="text-[11px] font-semibold uppercase text-[var(--accent-deep)] [letter-spacing:0.18em]">
ADMIN · DIENSTPLAN
</p>
<h1 className="mt-2 text-[36px] font-normal leading-tight tracking-[-0.025em] text-[var(--ink)]">
Dienste{" "}
<em className="font-normal italic text-[var(--accent-deep)]">
planen
</em>
</h1>
<p className="mt-1 max-w-2xl text-sm text-muted-foreground">
<p className="mt-2 max-w-2xl text-[15px] leading-6 text-[var(--ink-soft)]">
Plane Dienste pro Kalenderwoche und Haushalt. Jede Änderung wird
direkt gespeichert.
</p>
</div>
<div className="flex shrink-0 flex-wrap gap-2">
<span className="inline-flex h-9 items-center rounded-full border border-[var(--hairline)] bg-[var(--surface)] px-3 text-[13px] font-semibold text-[var(--ink-soft)]">
{dutyTypes.length} Diensttypen
</span>
<span className="inline-flex h-9 items-center rounded-full border border-[var(--hairline)] bg-[var(--surface)] px-3 text-[13px] font-semibold text-[var(--ink-soft)]">
{families.length} Familien
</span>
</div>
</header>
<Card className="w-full lg:max-w-xl">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-base">
<CalendarRange className="h-4 w-4" />
Zeitraum
</CardTitle>
<CardDescription>
Du kannst beliebig weit voraus planen, jeweils in handlichen
Ausschnitten.
</CardDescription>
</CardHeader>
<CardContent>
<form className="flex flex-col gap-3 sm:flex-row sm:items-end">
<div className="grid gap-1.5">
<Label htmlFor="start">Startwoche</Label>
<Input
id="start"
name="start"
type="date"
defaultValue={toDateInputValue(rangeStart)}
/>
<Card className="mb-5">
<CardContent className="pt-[22px]">
<form className="flex flex-wrap items-end gap-3">
<div className="mr-auto min-w-64">
<div className="flex items-center gap-2 text-base font-medium tracking-[-0.02em] text-[var(--ink)]">
<CalendarRange className="h-4 w-4 text-[var(--accent-deep)] [stroke-width:1.7]" />
Zeitraum
</div>
<div className="grid gap-1.5">
<Label htmlFor="weeks">Wochen</Label>
<Input
id="weeks"
name="weeks"
type="number"
min={1}
max={MAX_WEEK_COUNT}
defaultValue={weekCount}
/>
</div>
<Button type="submit">Anzeigen</Button>
<Button asChild type="button" variant="outline">
<Link
href={`/dashboard/admin/dienste?start=${toDateInputValue(
rangeStart,
)}&weeks=${Math.min(weekCount + 12, MAX_WEEK_COUNT)}`}
>
Weitere Wochen laden
</Link>
</Button>
</form>
</CardContent>
</Card>
</div>
<p className="mt-1 text-[13.5px] text-[var(--ink-soft)]">
{weeks[0].label} bis {weeks[weeks.length - 1].label}
</p>
</div>
<div className="grid gap-1.5">
<Label htmlFor="start">Startwoche</Label>
<Input
id="start"
name="start"
type="date"
defaultValue={toDateInputValue(rangeStart)}
/>
</div>
<div className="grid w-28 gap-1.5">
<Label htmlFor="weeks">Wochen</Label>
<Input
id="weeks"
name="weeks"
type="number"
min={1}
max={MAX_WEEK_COUNT}
defaultValue={weekCount}
/>
</div>
<Button type="submit">Anzeigen</Button>
<Button asChild type="button" variant="ghost">
<Link
href={`/dashboard/admin/dienste?start=${toDateInputValue(
rangeStart,
)}&weeks=${Math.min(weekCount + 12, MAX_WEEK_COUNT)}`}
>
Weitere Wochen laden
</Link>
</Button>
</form>
</CardContent>
</Card>
<div className="grid gap-6 xl:grid-cols-[340px_1fr]">
<Card>
@@ -186,19 +198,19 @@ export default async function DienstePage({
<div className="space-y-2">
{dutyTypes.length === 0 ? (
<p className="rounded-md bg-muted p-3 text-sm text-muted-foreground">
<p className="rounded-lg border border-dashed border-[var(--hairline)] bg-[var(--surface-2)] p-3 text-sm italic text-[var(--ink-faint)]">
Noch keine Diensttypen angelegt.
</p>
) : (
dutyTypes.map((dutyType) => (
<div
key={dutyType.id}
className="flex items-start justify-between gap-3 rounded-md border p-3"
className="flex items-start justify-between gap-3 rounded-lg border border-[var(--hairline)] bg-[var(--surface-2)] p-3"
>
<div className="min-w-0">
<p className="font-medium">{dutyType.name}</p>
<p className="font-medium text-[var(--ink)]">{dutyType.name}</p>
{dutyType.description && (
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
<p className="mt-1 line-clamp-2 text-xs text-[var(--ink-soft)]">
{dutyType.description}
</p>
)}
@@ -213,9 +225,10 @@ export default async function DienstePage({
type="submit"
variant="ghost"
size="icon"
className="text-[var(--danger)] hover:bg-[var(--danger-soft)] hover:text-[var(--danger)]"
aria-label={`${dutyType.name} löschen`}
>
<Trash2 className="h-4 w-4" />
<Trash2 className="h-4 w-4 [stroke-width:1.7]" />
</Button>
</form>
</div>