Files
ApiCore/ApiCore-Frontend/src/components/active-history-tabs.tsx
T
Nick.RosenloecherandClaude Opus 5.5 427969d8b9
Backend Image / build-and-push (push) Successful in 15m27s
Frontend Image / build-and-push (push) Successful in 23m9s
feat(t5): Wächter vollständig, Hinweis Sonnenmodus, Nacharbeiten (T5a A10)
- Baukasten-Wächter: jede Regel prüft eigene Beispiele (muss fangen, muss
  ignorieren), veraltete Ausnahmen und Ordner-Ausschlüsse schlagen fehl;
  neue Regeln für text-primary ohne -ink, dark:, Schrift unter 13 px
  (Tailwind, SVG, CSS, style, *_SIZE-Konstanten) und Verlaufs-Hintergründe.
  Aufgeteilt in baukasten-guard, touch-target-guard und einen Scan-Helfer.
- Hinweis „Neu: Sonnenmodus für draußen“ auf dem Dashboard, einmal je Gerät,
  „Ausprobieren“ schaltet live um; Fokus geht danach zur Überschrift.
- Varroa-Diagramm: Beschriftungen als HTML mit fester Größe statt SVG-Text.
- Medikamente zeigen die Tabs auch leer; Zahlen in Tabs nicht beim Laden
  oder Fehler; inaktive Tabs mit Hover für die Maus; TabPicker (Auswahl
  unter 640 px) für Automatisierung und Statistik; Rollen-Dialoge breit
  genug für die Rechte-Matrix.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-19 13:47:59 +02:00

78 lines
2.7 KiB
TypeScript

import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export type EntityLifecycleTab = "active" | "history";
export interface ActiveHistoryTabsProps {
value: EntityLifecycleTab;
onValueChange: (value: EntityLifecycleTab) => void;
activeCount?: number;
historyCount?: number;
/** A section heading that opens the row; the tabs then sit on its right. */
heading?: ReactNode;
/** Controls next to the tabs (a filter, a create button), outside the panels. */
toolbar?: ReactNode;
/** `end` pushes the toolbar to the far right of the row (a create button). */
toolbarAlign?: "start" | "end";
/** The list of each panel; only the selected one is mounted. */
children: { active: ReactNode; history: ReactNode };
}
/**
* The count for a tab: unknown (no number) until the list has loaded, so a failed or pending
* first load never reads „Aktiv (0)“. Cached data still counts while a refetch fails.
*/
export function countOf(data: readonly unknown[] | undefined, items: readonly unknown[]): number | undefined {
return data === undefined ? undefined : items.length;
}
const withCount = (label: string, count: number | undefined) =>
count === undefined ? label : `${label} (${count})`;
const isLifecycleTab = (value: unknown): value is EntityLifecycleTab =>
value === "active" || value === "history";
/**
* Template on Tabs (spec 10.4): „Aktiv“ and „Historie“, each panel holds its list. Both panels
* filter data the page has already loaded, so the arrow keys switch at once (activateOnFocus).
*/
export function ActiveHistoryTabs({
value,
onValueChange,
activeCount,
historyCount,
heading,
toolbar,
toolbarAlign = "start",
children,
}: ActiveHistoryTabsProps) {
return (
<Tabs
value={value}
onValueChange={(next) => {
if (isLifecycleTab(next)) onValueChange(next);
}}
>
<div className="flex flex-wrap items-center gap-3">
{heading}
<TabsList
aria-label="Aktiv oder Historie"
activateOnFocus
className={cn(heading !== undefined && "ml-auto")}
>
<TabsTrigger value="active">{withCount("Aktiv", activeCount)}</TabsTrigger>
<TabsTrigger value="history">{withCount("Historie", historyCount)}</TabsTrigger>
</TabsList>
{toolbarAlign === "end" && toolbar !== undefined ? (
<div className="ml-auto">{toolbar}</div>
) : (
toolbar
)}
</div>
<TabsContent value="active">{children.active}</TabsContent>
<TabsContent value="history">{children.history}</TabsContent>
</Tabs>
);
}