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:
1 parent
cf53cf35ac
commit
258e90828f
97 files changed
+732
-395
No files matched your search
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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>,
|
||||
);
|
||||
Reference in new issue
Block a user