Files
Lageplan/plans/phase-1-symbol-architecture.md
2026-05-20 21:33:53 +02:00

9.5 KiB
Raw Blame History

Phase 1 — Symbol-Architektur Redesign: Detaillierter Plan

Basierend auf: docs/roadmap-feedback-fabian.md Ziel: Mandantenspezifische Symbol-Bibliothek mit Template-Import, eigener Kategorisierung und vollständiger Entkopplung von globalen Icons. Status: ABGESCHLOSSEN (Sprints AD implementiert, getestet und gepusht)


1. Ziel & Konzept

Problem heute:

  • TenantSymbol verweist auf IconAsset (global). Mandant kann zwar customName setzen, aber nicht die Kategorie ändern, das SVG bearbeiten oder Symbole aus verschiedenen Paketen frei mischen.
  • Kategorien (IconCategory) sind global mit tenantId Override — 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 bestehenden public/signaturen/ und IconAsset generiert.
  • TenantCategory = pro Mandant, frei anlegbar/umbenennbar/sortierbar.
  • TenantSymbol = pro Mandant, vollständig eigenständig (name, svgPath, categoryId). Kein Verweis mehr auf globale IconAsset.
  • 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, IconCategory und hiddenIconIds bleiben 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 — erstellt symbol_templates, tenant_categories, neue Spalten in tenant_symbols
  • prisma/seed-symbol-templates.ts — füllt SymbolTemplate aus public/signaturen/*.svg
  • prisma/migrate-tenant-symbols.ts — migriert bestehende TenantSymbol-Einträge
  • prisma/migrate.js — erweitert um Migrationsschritte für Produktion
  • Alle Seed-Skripte sind idempotent (upsert statt deleteMany)

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 mit groupBy Aggregation und Vorschau
  • src/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.ts
  • src/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:

  1. Meine Symbole — nach TenantCategory gruppierte Symbol-Liste
    • Expandable Kategorien
    • SymbolCard: Bild, Name (inline-edit), Kategorie-Select, Löschen
    • Suche über alle Symbole
  2. Kategorien — CRUD für TenantCategory
    • Neue Kategorie erstellen
    • Umbenennen (inline)
    • Löschen nur wenn leer
  3. 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 tenantSymbolGroups aus /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-Liste
  • tenantSymbolGroups — 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/*.svgSymbolTemplate
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