feat(t5): Bereiche mit eigenem Symbol und Ton, BrandMark, Verläufe raus (T5a A3)

AREAS (37 Bereiche, je Symbol genau einmal; neu compliance, medicines,
voice, hive-systems, system) als einzige Quelle für Kopfzeilen, Menü
und Schnellanlage; PageHeader, DetailHeader und EntityCard nehmen
area statt icon/gradient (Codemod über 72 Dateien). Doppelte Symbole
aufgelöst. BrandMark (Wabe) ersetzt das Verlaufs-Sechseck;
Honig-Banner auf der Startseite durch Überschrift mit Datum ersetzt;
Verläufe aus 33 Verbrauchern entfernt (Token folgen in A4).

Refs #26

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick.RosenloecherandClaude Opus 5.5 committed 2026-10-16 16:47:56 +02:00
1 parent cf53cf35ac
commit 258e90828f
97 files changed
+732 -395

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import { AREAS, type AreaKey } from "./areas";
import { ADMIN_GROUP, NAV_GROUPS } from "@/app/(app)/nav-config";
import { QUICK_ACTIONS } from "@/app/(app)/quick-actions";
const STATUS_TONES = ["success", "warning", "destructive", "condition", "ok", "error"];
describe("AREAS", () => {
it("every NAV_GROUPS entry and QUICK_ACTION maps to an AREAS key with the same icon", () => {
const navItems = [...NAV_GROUPS, ADMIN_GROUP].flatMap((group) => group.items);
expect(navItems.length).toBeGreaterThan(0);
for (const item of navItems) {
expect(AREAS[item.area], item.href).toBeDefined();
expect(item.icon, item.href).toBe(AREAS[item.area].icon);
}
for (const action of QUICK_ACTIONS) {
expect(AREAS[action.area], action.href).toBeDefined();
expect(action.icon, action.href).toBe(AREAS[action.area].icon);
}
});
it("contains the areas added after the spec: compliance, medicines, voice, hive-systems, system", () => {
const keys: AreaKey[] = ["compliance", "medicines", "voice", "hive-systems", "system"];
for (const key of keys) {
expect(AREAS[key].key).toBe(key);
}
expect(AREAS.compliance.tone).toBe("betrieb");
expect(AREAS.medicines.tone).toBe("erfassung");
expect(AREAS.voice.tone).toBe("erfassung");
expect(AREAS["hive-systems"].tone).toBe("neutral");
expect(AREAS.system.tone).toBe("neutral");
});
it("gives every icon to exactly one area", () => {
const seen = new Map<unknown, string>();
for (const area of Object.values(AREAS)) {
expect(seen.get(area.icon), `${area.key} clashes with ${seen.get(area.icon)}`).toBeUndefined();
seen.set(area.icon, area.key);
}
});
it("no area tone is a status colour", () => {
for (const area of Object.values(AREAS)) {
expect(STATUS_TONES).not.toContain(area.tone);
expect(["betrieb", "erfassung", "arbeit", "ernte", "neutral"]).toContain(area.tone);
}
});
it("keeps the key and label of each entry", () => {
for (const [key, area] of Object.entries(AREAS)) {
expect(area.key).toBe(key);
expect(area.label.length).toBeGreaterThan(0);
}
});
});
+149
View File
@@ -0,0 +1,149 @@
import {
Activity,
BarChart3,
Bell,
Box,
Boxes,
Bug,
CalendarDays,
ClipboardCheck,
Crown,
Database,
Droplet,
Factory,
FlaskConical,
Flower2,
History,
Layers,
LayoutDashboard,
Lightbulb,
ListTodo,
type LucideIcon,
MapPin,
Mic,
Package,
Pill,
Scissors,
Search,
ShieldCheck,
ShoppingBag,
Snowflake,
Sprout,
Syringe,
Tag,
Users,
UserRound,
Utensils,
Warehouse,
Wrench,
Zap,
} from "lucide-react";
export type AreaKey =
| "dashboard"
| "search"
| "apiaries"
| "colonies"
| "queens"
| "compliance"
| "winter"
| "phenology"
| "inspections"
| "varroa"
| "swarm-control"
| "treatments"
| "feedings"
| "medicines"
| "voice"
| "tasks"
| "calendar"
| "suggestions"
| "notifications"
| "harvests"
| "extractions"
| "honey-batches"
| "bottling"
| "products"
| "raw-materials"
| "production-batches"
| "storage"
| "equipment"
| "statistics"
| "users-roles"
| "automation"
| "hive-systems"
| "label-templates"
| "backup"
| "audit-log"
| "system"
| "profile";
/**
* A tone groups areas visually. It is never a status colour: red, green and
* yellow belong to states (traffic light, warning, destructive).
*/
export type AreaTone = "betrieb" | "erfassung" | "arbeit" | "ernte" | "neutral";
export interface Area {
key: AreaKey;
label: string;
icon: LucideIcon;
tone: AreaTone;
href?: string;
}
function area(
key: AreaKey,
label: string,
icon: LucideIcon,
tone: AreaTone,
href?: string,
): readonly [AreaKey, Area] {
return [key, { key, label, icon, tone, href }];
}
/**
* One icon means one area, everywhere (spec 11). Nav, quick actions, page
* headers and cards all read from here.
*/
export const AREAS: Readonly<Record<AreaKey, Area>> = Object.freeze(
Object.fromEntries([
area("dashboard", "Start", LayoutDashboard, "neutral", "/dashboard"),
area("search", "Suche", Search, "neutral", "/search"),
area("apiaries", "Bienenstände", MapPin, "betrieb", "/apiaries"),
area("colonies", "Völker", Layers, "betrieb", "/colonies"),
area("queens", "Königinnen", Crown, "betrieb", "/queens"),
area("compliance", "Prüfbereitschaft", ShieldCheck, "betrieb", "/compliance"),
area("winter", "Überwinterung", Snowflake, "betrieb", "/winter"),
area("phenology", "Pflanzenentwicklung", Flower2, "betrieb"),
area("inspections", "Durchsicht", ClipboardCheck, "erfassung", "/inspections"),
area("varroa", "Varroa", Bug, "erfassung", "/varroa"),
area("swarm-control", "Schwarmkontrolle", Sprout, "erfassung", "/swarm-control"),
area("treatments", "Behandlung", Syringe, "erfassung", "/treatments"),
area("feedings", "Fütterung", Utensils, "erfassung", "/feedings"),
area("medicines", "Arzneimittel", Pill, "erfassung", "/medicines"),
area("voice", "Sprachnotizen", Mic, "erfassung", "/voice"),
area("tasks", "Aufgaben", ListTodo, "arbeit", "/tasks"),
area("calendar", "Kalender", CalendarDays, "arbeit", "/calendar"),
area("suggestions", "Vorschläge", Lightbulb, "arbeit", "/suggestions"),
area("notifications", "Meldungen", Bell, "arbeit"),
area("harvests", "Ernten", Scissors, "ernte", "/harvests"),
area("extractions", "Schleuderungen", FlaskConical, "ernte", "/extractions"),
area("honey-batches", "Honigchargen", Droplet, "ernte", "/honey-batches"),
area("bottling", "Abfüllungen", Package, "ernte", "/bottling"),
area("products", "Produkte", ShoppingBag, "ernte", "/products"),
area("raw-materials", "Rohstoffe", Boxes, "ernte", "/raw-materials"),
area("production-batches", "Herstellungschargen", Factory, "ernte", "/production-batches"),
area("storage", "Lager", Warehouse, "ernte", "/storage"),
area("equipment", "Inventar", Wrench, "ernte", "/equipment"),
area("statistics", "Statistik", BarChart3, "neutral", "/statistics"),
area("users-roles", "Benutzer & Rollen", Users, "neutral", "/settings/users-roles"),
area("automation", "Automatisierung", Zap, "neutral", "/settings/automation"),
area("hive-systems", "Beutensysteme", Box, "neutral", "/settings/hive-systems"),
area("label-templates", "Etikettenvorlagen", Tag, "neutral", "/settings/label-templates"),
area("backup", "Backup", Database, "neutral", "/settings/backup"),
area("audit-log", "Audit-Log", History, "neutral", "/settings/audit-log"),
area("system", "System", Activity, "neutral", "/settings/system"),
area("profile", "Profil", UserRound, "neutral", "/profile"),
]) as Record<AreaKey, Area>,
);