9.5 KiB
Phase 1 — Symbol-Architektur Redesign: Detaillierter Plan
Basierend auf:
docs/roadmap-feedback-fabian.mdZiel: Mandantenspezifische Symbol-Bibliothek mit Template-Import, eigener Kategorisierung und vollständiger Entkopplung von globalen Icons. Status: ABGESCHLOSSEN ✅ (Sprints A–D implementiert, getestet und gepusht)
1. Ziel & Konzept
Problem heute:
TenantSymbolverweist aufIconAsset(global). Mandant kann zwarcustomNamesetzen, aber nicht die Kategorie ändern, das SVG bearbeiten oder Symbole aus verschiedenen Paketen frei mischen.- Kategorien (
IconCategory) sind global mittenantIdOverride — uneinheitlich. - Keine Möglichkeit, Vorlagen-Pakete (Feuerwehr CH, THW, Sanität) als Unit zu importieren.
Lösung:
SymbolTemplate= globale, read-only Vorlagen (je Paket). Wird einmalig aus bestehendenpublic/signaturen/undIconAssetgeneriert.TenantCategory= pro Mandant, frei anlegbar/umbenennbar/sortierbar.TenantSymbol= pro Mandant, vollständig eigenständig (name,svgPath,categoryId). Kein Verweis mehr auf globaleIconAsset.- Mandant startet mit leerer Bibliothek und importiert Pakete nach Bedarf.
2. Datenmodell-Änderungen
2.1 Neue Modelle
model SymbolTemplate {
id String @id @default(uuid())
packageId String // z.B. "feuerwehr-ch"
packageName String // z.B. "Feuerwehr Schweiz"
categoryName String // z.B. "Fahrzeuge"
name String
svgPath String // Relativer Pfad in public/ oder SVG-Inhalt
tags String[] @default([])
sortOrder Int @default(0)
@@index([packageId])
@@map("symbol_templates")
}
model TenantCategory {
id String @id @default(uuid())
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
name String
description String?
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
symbols TenantSymbol[]
@@unique([tenantId, name])
@@index([tenantId])
@@map("tenant_categories")
}
2.2 Bestehendes TenantSymbol umbaut
Vorher:
model TenantSymbol {
id String @id @default(uuid())
customName String?
sortOrder Int @default(0)
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
iconId String
icon IconAsset @relation(fields: [iconId], references: [id], onDelete: Cascade)
}
Nachher:
model TenantSymbol {
id String @id @default(uuid())
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
categoryId String?
category TenantCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
name String // endgültiger Anzeigename (kann aus Template importiert oder custom sein)
svgPath String? // z.B. "signaturen/TLF.svg" oder tenant-spezifischer MinIO-Key
sortOrder Int @default(0)
isUploaded Boolean @default(false) // true = eigener Upload, false = aus Template importiert
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Legacy-Feld für Migration (kann später entfernt werden)
migratedFromIconId String?
@@index([tenantId])
@@index([categoryId])
@@map("tenant_symbols")
}
Hinweis:
IconAsset,IconCategoryundhiddenIconIdsbleiben vorerst bestehen (read-only Legacy), werden aber nicht mehr für neue Features verwendet. In einer späteren Phase können sie entfernt werden.
3. Migration & Seed (Sprint A) ✅
prisma/migrations/20260520_symbol_architecture/migration.sql— erstelltsymbol_templates,tenant_categories, neue Spalten intenant_symbolsprisma/seed-symbol-templates.ts— fülltSymbolTemplateauspublic/signaturen/*.svgprisma/migrate-tenant-symbols.ts— migriert bestehendeTenantSymbol-Einträgeprisma/migrate.js— erweitert um Migrationsschritte für Produktion- Alle Seed-Skripte sind idempotent (
upsertstattdeleteMany)
4. API-Design (Sprint B) ✅
4.1 Templates
GET /api/templates
→ { packages: [{ packageId, packageName, categoryCount, symbolCount, previewSymbols }] }
POST /api/templates/import
Body: { packageId }
→ importiert alle Symbole des Pakets als TenantSymbols
→ antwortet mit { imported, skipped, message }
Implementiert:
src/app/api/templates/route.ts— GET mitgroupByAggregation und Vorschausrc/app/api/templates/import/route.ts— POST mit Auto-Kategorie-Erstellung und Deduplikation
4.2 Tenant Categories
GET /api/tenant/categories → [{ id, name, sortOrder, description }]
POST /api/tenant/categories Body: { name } → neue Kategorie
PATCH /api/tenant/categories Body: { id, name } → umbenennen
DELETE /api/tenant/categories?id=... → 204 oder 409 wenn nicht leer
Implementiert: src/app/api/tenant/categories/route.ts
4.3 Tenant Symbols
GET /api/tenant/symbols?grouped=true|false
→ grouped=true: { groups: [{ category, symbols }], symbols: [...] }
→ grouped=false: { symbols: [...] }
POST /api/tenant/symbols
- Multipart: { file, categoryId? } → Upload nach MinIO
- JSON: { templateId, categoryId? } → aus SymbolTemplate
- JSON: { iconId, customName?, categoryId? } → aus IconAsset (Legacy)
PATCH /api/tenant/symbols Body: { id, name?, customName?, categoryId?, sortOrder? }
DELETE /api/tenant/symbols Body: { id } → löscht TenantSymbol
Implementiert: src/app/api/tenant/symbols/route.ts + src/app/api/tenant/symbols/[id]/image/route.ts
4.4 Icon-Serving (TenantSymbol-First Lookup)
GET /api/icons/:id/image
→ 1. TenantSymbol (svgPath aus public/ oder MinIO)
→ 2. Fallback: IconAsset (legacy)
GET /api/tenant/symbols/:id/image
→ TenantSymbol-Bild aus MinIO oder public/
Implementiert:
src/app/api/icons/[id]/image/route.tssrc/app/api/tenant/symbols/[id]/image/route.ts
5. Frontend-Änderungen (Sprints C + D) ✅
5.1 Admin — Symbol-Manager (src/components/admin/symbol-manager.tsx)
Tabs:
- Meine Symbole — nach TenantCategory gruppierte Symbol-Liste
- Expandable Kategorien
- SymbolCard: Bild, Name (inline-edit), Kategorie-Select, Löschen
- Suche über alle Symbole
- Kategorien — CRUD für TenantCategory
- Neue Kategorie erstellen
- Umbenennen (inline)
- Löschen nur wenn leer
- Vorlagen importieren — Template-Pakete als Cards
- Vorschau der ersten 4 Symbole
- 1-Klick Import
Actions:
- Upload-Button → Dialog mit Drag & Drop + Kategorie-Auswahl
- Import-Button → Dialog mit allen verfügbaren Paketen
5.2 Sidebar — RightSidebar (src/components/layout/right-sidebar.tsx)
- Verwendet neue
tenantSymbolGroupsaus/api/icons - Meine Symbole Sektion: nach TenantCategory gruppiert, expandable
- Bibliothek Sektion: globale IconAsset-Kategorien (Legacy, read-only)
- Drag & Drop funktioniert mit neuen TenantSymbol-IDs
5.3 API /icons — Kompatibilität
src/app/api/icons/route.ts liefert:
categories— Legacy IconAsset-Kategorien (für alte Clients)mySymbols— Legacy TenantSymbol-Liste (für alte Clients)tenantSymbols— Neue flache TenantSymbol-ListetenantSymbolGroups— Neue gruppierte Liste für Sidebar
6. Dateien: Create / Modify
Neue Dateien ✅
| Pfad | Beschreibung |
|---|---|
prisma/migrations/20260520_symbol_architecture/migration.sql |
Migration neue Tabellen + Spalten |
prisma/seed-symbol-templates.ts |
Seed: public/signaturen/*.svg → SymbolTemplate |
src/app/api/templates/route.ts |
GET /api/templates |
src/app/api/templates/import/route.ts |
POST /api/templates/import |
src/app/api/tenant/categories/route.ts |
CRUD TenantCategory |
src/app/api/tenant/symbols/[id]/image/route.ts |
TenantSymbol Bild-Serving |
Modifizierte Dateien ✅
| Pfad | Änderung |
|---|---|
prisma/schema.prisma |
Neue Modelle + TenantSymbol Refactor |
src/app/api/tenant/symbols/route.ts |
Refactor: Gruppierung, Upload, CRUD |
src/app/api/icons/route.ts |
Liefert tenantSymbolGroups |
src/app/api/icons/[id]/image/route.ts |
TenantSymbol-First Lookup |
src/components/admin/symbol-manager.tsx |
Vollständiger Umbau mit Kategorien, Import, Upload |
src/components/layout/right-sidebar.tsx |
Gruppierte TenantSymbol-Anzeige |
7. Ausführungsreihenfolge (Tatsächlich)
| Sprint | Status | Inhalt |
|---|---|---|
| A | ✅ Fertig | Schema, Migration, Seed, Daten-Migration |
| B | ✅ Fertig | Templates API, Categories API, Symbols API erweitert |
| C | ✅ Fertig | Admin UI: Symbol-Manager mit Kategorien, Import, Upload |
| D | ✅ Fertig | Frontend Sidebar gruppiert nach TenantCategory |
8. Risiken & Entscheidungen (Umgesetzt)
| Thema | Entscheidung |
|---|---|
| SVG-Speicherort | Templates: public/signaturen/ Pfade in DB (read-only, kein MinIO-Overhead). Uploads: MinIO tenant-{id}/symbols/. |
| Migration alter Tenants | Vollautomatisch via prisma/migrate.js + prisma/migrate-tenant-symbols.ts. |
| Sidebar vs SymbolPalette | RightSidebar direkt angepasst, keine separate SymbolPalette-Komponente nötig. |
| instanceLabel / Stockwerke | Nicht in Sprint D enthalten (laut Roadmap Phase 1.4 — kann separat geplant werden). |
Plan erstellt: 2026-05-20 Phase 1 abgeschlossen: 2026-05-20