Co-Authored-By: claude-flow <ruv@ruv.net>
17 KiB
Refactor v2 — Code Optimization, De-duplication & Maintainability
Anforderungsanalyse
Vollstaendiger Optimierungsdurchlauf des Planarchy-Monorepos. Ziel: Code-Duplikate eliminieren,
Performance verbessern, Wartbarkeit erhoehen. Betroffen sind alle Pakete: apps/web, packages/api,
packages/db, packages/shared.
Die Analyse identifiziert 7 unabhaengige Arbeitsstroeme (Waves), die von Agenten parallel bearbeitet werden koennen, plus eine abschliessende Schema-Migration.
Betroffene Pakete & Dateien
| Paket | Dateien | Art |
|---|---|---|
apps/web |
src/lib/format.ts, 10+ Consumer-Dateien |
edit |
apps/web |
src/lib/status-styles.ts, src/components/timeline/timelineConstants.ts, 6 Consumer-Dateien |
edit |
apps/web |
src/hooks/useInvalidatePlanningViews.ts, 14 Consumer-Dateien |
edit |
apps/web |
src/components/timeline/renderHelpers.ts |
create |
apps/web |
src/components/timeline/TimelineResourcePanel.tsx, TimelineProjectPanel.tsx |
edit |
apps/web |
src/components/timeline/TimelineView.tsx |
edit |
apps/web |
src/hooks/useTimelineDrag.ts |
edit |
packages/api |
src/db/helpers.ts, 7+ Router-Dateien |
edit |
packages/api |
src/db/selects.ts, 6+ Router-Dateien |
edit |
packages/db |
prisma/schema.prisma |
edit |
Wave 1 — Centralize formatMoney() / formatCents() (Agent: format-consolidator)
Problem: Zentralisierte Funktionen existieren in apps/web/src/lib/format.ts, aber 10+
Stellen nutzen Inline-(x / 100).toLocaleString("de-DE", ...) statt der zentralen Imports.
Zusaetzlich gibt es 2 lokale fmtEur() Helfer in API-Routern.
Dateien die keine Aenderung brauchen:
ShiftPreviewTooltip.tsx— spezialisierter Delta-Formatter mit +/- Prefix (bleibt)
Tasks
- 1.1 Inline-Formatierung in
FillOpenDemandModal.tsx(5 Stellen, Lines ~214/417/434/444/452) durchformatCents()Import ersetzen →apps/web/src/components/allocations/FillOpenDemandModal.tsx - 1.2 Inline-Formatierung in
DemandPopover.tsx(Lines ~146/152) durchformatCents()Import ersetzen →apps/web/src/components/timeline/DemandPopover.tsx - 1.3 Inline-Formatierung in
ResourceHoverCard.tsx(Lines ~123/130) durchformatCents()Import ersetzen →apps/web/src/components/timeline/ResourceHoverCard.tsx - 1.4 Inline-Formatierung in
ProjectWizard.tsx(Lines ~509-511) ersetzen →apps/web/src/components/projects/ProjectWizard.tsx - 1.5 Inline-Formatierung in
ProjectAssignmentsTable.tsx(Line ~130) ersetzen →apps/web/src/components/projects/ProjectAssignmentsTable.tsx - 1.6 Inline-Formatierung in
ProjectDemandsTable.tsx(Lines ~135/138-139) ersetzen →apps/web/src/components/projects/ProjectDemandsTable.tsx - 1.7 Inline-Formatierung in
ProjectsClient.tsx(Line ~403) ersetzen →apps/web/src/app/(app)/projects/ProjectsClient.tsx - 1.8 Inline-Formatierung in
ProjectTableWidget.tsx(Lines ~274/283) ersetzen →apps/web/src/components/dashboard/widgets/ProjectTableWidget.tsx - 1.9 Inline-
.toFixed(0)inResourcesClient.tsx(Line ~1178) undResourceDetail.tsx(Lines ~279/286) durchformatMoney()ersetzen → 2 Dateien - 1.10 Duplizierte
fmtEur()inassistant-tools.ts(Line ~44-46) undcomputation-graph.ts(Line ~53-55) entfernen — gemeinsamen Helfer inpackages/api/src/lib/format-utils.tserstellen (da API-Router keinen Zugriff aufapps/web/src/lib/format.tshaben) →packages/api/src/lib/format-utils.ts(create), 2 Router editieren
Akzeptanzkriterien
- Kein
/ 100).toLocaleString("de-DE"mehr in Component-Dateien (ausser ShiftPreviewTooltip) - Kein lokales
fmtEur()mehr in API-Routern
Wave 2 — Adopt findUniqueOrThrow() Helper (Agent: db-helper-consolidator)
Problem: packages/api/src/db/helpers.ts existiert mit findUniqueOrThrow<T>(), aber 7 Router-Dateien nutzen ihn nicht und haben manuelle findUnique + if (!x) throw Bloecke.
Tasks
- 2.1
entitlement.ts— 7 manuelle Stellen durchfindUniqueOrThrow()ersetzen →packages/api/src/router/entitlement.ts - 2.2
calculation-rules.ts— 3 manuelle if+throw Stellen (Lines ~24-26, 62-64, 89-91) ersetzen →packages/api/src/router/calculation-rules.ts - 2.3
notification.ts— 3 Stellen migrieren →packages/api/src/router/notification.ts - 2.4
settings.ts— 3 Stellen migrieren →packages/api/src/router/settings.ts - 2.5
user.ts— verbleibende manuelle Stellen migrieren →packages/api/src/router/user.ts - 2.6
resource.ts— 8 verbleibende manuelle Stellen migrieren →packages/api/src/router/resource.ts - 2.7
vacation.ts— 12 verbleibende manuelle Stellen migrieren →packages/api/src/router/vacation.ts - 2.8
timeline.ts— 4 verbleibende Stellen migrieren →packages/api/src/router/timeline.ts - 2.9
assistant.ts— 1 Stelle migrieren →packages/api/src/router/assistant.ts
Hinweis: assistant-tools.ts nutzt { error: "..." } Return-Pattern statt throw — NICHT migrieren (anderes Error-Handling).
Akzeptanzkriterien
- Alle Router (ausser assistant-tools.ts) nutzen
findUniqueOrThrow()fuer NOT_FOUND Checks pnpm --filter @planarchy/api exec tsc --noEmit— gruen
Wave 3 — Prisma Select Constants adoptieren (Agent: select-consolidator)
Problem: packages/api/src/db/selects.ts definiert ROLE_BRIEF_SELECT, PROJECT_BRIEF_SELECT, RESOURCE_BRIEF_SELECT, aber Adoption ist gering (nur allocation.ts nutzt alle drei).
Tasks
- 3.1
vacation.ts— 5 Inline-Resource-Selects (Lines ~102/123/213/542/579) durchRESOURCE_BRIEF_SELECTersetzen →packages/api/src/router/vacation.ts - 3.2
role.ts— 1 Inline-Resource-Select (Line ~92) ersetzen →packages/api/src/router/role.ts - 3.3
project-planning-read-model.ts— 1 Inline-Role-Select (Line ~34) durchROLE_BRIEF_SELECTersetzen →packages/api/src/router/project-planning-read-model.ts - 3.4
resource.ts— 1 Inline-Role-Select (Line ~313) durchROLE_BRIEF_SELECTersetzen →packages/api/src/router/resource.ts - 3.5
calculation-rules.ts— 2 Inline-Project-Selects (Lines ~13/22) durchPROJECT_BRIEF_SELECTersetzen →packages/api/src/router/calculation-rules.ts - 3.6
entitlement.ts— 1 Inline-Resource-Select (Line ~269) durchRESOURCE_BRIEF_SELECT(+ spreadchapter: true) ersetzen →packages/api/src/router/entitlement.ts
Akzeptanzkriterien
- Keine
{ id: true, name: true, color: true }Inline-Selects mehr in Routern (ausser dort wo erweitert) pnpm --filter @planarchy/api exec tsc --noEmit— gruen
Wave 4 — Status Badge & Vacation Constant Consolidation (Agent: style-consolidator)
Problem: Mehrere duplizierte Konstanten-Maps fuer Status-Badges und Vacation-Type-Farben:
VACATION_TYPE_LABELSdupliziert inVacationModal.tsx(Line ~13-18)TYPE_COLORS+TYPE_BORDER+TYPE_LABELS_SHORTidentisch inTimelineResourcePanel.tsx(Lines ~563-580) undTimelineProjectPanel.tsx(Lines ~1299-1316)TYPE_COLORfuer Kalender dupliziert inVacationCalendar.tsx(Line ~21) undTeamCalendar.tsx(Line ~8) — mit Inkonsistenz bei PUBLIC_HOLIDAY
Tasks
- 4.1 Vacation-Timeline-Konstanten (
VACATION_TIMELINE_COLORS,VACATION_TIMELINE_BORDER,VACATION_TYPE_LABELS_SHORT) instatus-styles.tsals Exports hinzufuegen →apps/web/src/lib/status-styles.ts - 4.2 Vacation-Kalender-Konstanten (
VACATION_CALENDAR_COLORS) instatus-styles.tshinzufuegen, PUBLIC_HOLIDAY-Inkonsistenz aufemerald-500vereinheitlichen →apps/web/src/lib/status-styles.ts - 4.3
TimelineResourcePanel.tsx— lokaleTYPE_COLORS/TYPE_BORDER/TYPE_LABELS_SHORT(Lines ~563-580) entfernen, Import ausstatus-styles.ts→apps/web/src/components/timeline/TimelineResourcePanel.tsx - 4.4
TimelineProjectPanel.tsx— lokaleTYPE_COLORS/TYPE_BORDER/TYPE_LABELS_SHORT(Lines ~1299-1316) entfernen, Import ausstatus-styles.ts→apps/web/src/components/timeline/TimelineProjectPanel.tsx - 4.5
VacationModal.tsx— lokalesVACATION_TYPE_LABELS(Lines ~13-18) entfernen, Import ausstatus-styles.ts→apps/web/src/components/vacations/VacationModal.tsx - 4.6
VacationCalendar.tsx— lokalesTYPE_COLOR(Line ~21) ersetzen durch Import →apps/web/src/components/vacations/VacationCalendar.tsx - 4.7
TeamCalendar.tsx— lokalesTYPE_COLOR(Line ~8) ersetzen durch Import →apps/web/src/components/vacations/TeamCalendar.tsx
Akzeptanzkriterien
- Keine duplizierten Vacation/Status-Konstanten mehr in Komponenten
VacationCalendarundTeamCalendarnutzen identische Farben
Wave 5 — Adopt useInvalidatePlanningViews() Hook (Agent: invalidation-consolidator)
Problem: Hook existiert in apps/web/src/hooks/useInvalidatePlanningViews.ts mit 8 Queries,
wird aber von KEINER Mutation genutzt. 14+ Stellen kopieren die 4-Query-Timeline-Invalidierung manuell.
Ausserdem fehlt getProjectContext in useTimelineDrag.ts (Line ~238).
Tasks
- 5.1
useInvalidatePlanningViewsin eineuseInvalidateTimeline()(4 Timeline-Queries) unduseInvalidateAllAllocViews()(alle 8) aufspalten, da manche Stellen nur Timeline invalidieren →apps/web/src/hooks/useInvalidatePlanningViews.ts(edit) - 5.2
TimelineView.tsx— 2 manuelle Invalidierungsbloecke (Lines ~73-76, ~333-336) durch Hook ersetzen →apps/web/src/components/timeline/TimelineView.tsx - 5.3
AllocationPopover.tsx— Invalidierungsblock (Lines ~58-62) durch Hook ersetzen →apps/web/src/components/timeline/AllocationPopover.tsx - 5.4
NewAllocationPopover.tsx— Invalidierungsblock (Lines ~63-66) ersetzen →apps/web/src/components/timeline/NewAllocationPopover.tsx - 5.5
BatchAssignPopover.tsx— Invalidierungsblock (Lines ~54-57) ersetzen →apps/web/src/components/timeline/BatchAssignPopover.tsx - 5.6
ProjectPanel.tsx— 3 Invalidierungsbloecke (Lines ~106-109, 115-118, 124-127) ersetzen →apps/web/src/components/timeline/ProjectPanel.tsx - 5.7
useAllocationHistory.ts— 3 Invalidierungsbloecke (Lines ~31-34, 40-43, 62-65) ersetzen →apps/web/src/hooks/useAllocationHistory.ts - 5.8
useTimelineDrag.ts— 2 Bloecke (Lines ~238-241, 254-257) ersetzen + fehlendegetProjectContextInvalidierung fixen →apps/web/src/hooks/useTimelineDrag.ts - 5.9
FillOpenDemandModal.tsx— Invalidierungsblock (Lines ~76-79) ersetzen →apps/web/src/components/allocations/FillOpenDemandModal.tsx
Akzeptanzkriterien
- Keine manuellen 4-Query-Timeline-Invalidierungsbloecke mehr
useTimelineDrag.tsinvalidiert alle 4 Timeline-Queries (inkl.getProjectContext)
Wave 6 — Timeline Render-Helpers & React.memo (Agent: timeline-optimizer)
Problem: 3 identische Render-Funktionen in beiden Panel-Komponenten (Vacation-Blocks, Range-Overlay, Overbooking-Blink). Keine React.memo auf den grossen Panel-Komponenten. useTimelineDrag.ts ist 883 Zeilen Monolith.
Tasks
- 6.1
renderHelpers.tserstellen mit extrahierten Shared-Funktionen:renderVacationBlocks(),renderRangeOverlay(),renderOverbookingBlink()→apps/web/src/components/timeline/renderHelpers.ts(create) - 6.2
TimelineResourcePanel.tsx— lokalerenderVacationBlocksForRow,renderRangeOverlay,renderOverbookingBlink(Lines ~582-636, 895-930) durch Imports ausrenderHelpers.tsersetzen → edit - 6.3
TimelineProjectPanel.tsx— lokalerenderVacationBlocksForProjectRow,renderRangeOverlayProject,renderOverbookingBlinkProjectdurch Imports ersetzen → edit - 6.4
React.memo()aufTimelineResourcePanelwrappen →TimelineResourcePanel.tsx - 6.5
React.memo()aufTimelineProjectPanelwrappen →TimelineProjectPanel.tsx - 6.6 Multi-Select-Intersection-Logic (Lines ~573-634 in
TimelineView.tsx) in eigenen HookuseMultiSelectIntersection.tsextrahieren →apps/web/src/hooks/useMultiSelectIntersection.ts(create),TimelineView.tsx(edit) - 6.7
useTimelineDrag.ts— Drag-Math-Utilities (pixelsToDays,constrainToGrid,clampDate) indragMath.tsextrahieren →apps/web/src/components/timeline/dragMath.ts(create),useTimelineDrag.ts(edit)
Akzeptanzkriterien
- Keine duplizierten Render-Funktionen zwischen den Panel-Komponenten
- Beide Panels als
React.memo()exportiert useTimelineDrag.tsunter 800 Zeilen- Timeline rendert korrekt in beiden Views + Overbooking-Blink funktioniert
Wave 7 — Composite Database Indexes (Agent: db-index-optimizer)
Problem: Mehrere haeufig abgefragte Modelle haben keine optimalen Composite-Indexes fuer die kombinierten WHERE-Bedingungen der tRPC-Router.
Tasks
- 7.1
DemandRequirement—@@index([projectId, status, startDate, endDate])hinzufuegen (ersetzt/ergaenzt bestehenden 3-Feld-Index) →packages/db/prisma/schema.prisma - 7.2
Resource—@@index([isActive, orgUnitId])hinzufuegen fuer "aktive Ressourcen pro OrgUnit" Queries →packages/db/prisma/schema.prisma - 7.3
Project—@@index([status, startDate, endDate])hinzufuegen fuer Timeline-Filterung →packages/db/prisma/schema.prisma - 7.4
Estimate—@@index([projectId, status])hinzufuegen →packages/db/prisma/schema.prisma - 7.5
pnpm db:pushausfuehren,.next/Cache loeschen, Dev-Server neu starten
Bereits optimal:
Vacation— hat@@index([resourceId, status, startDate, endDate])✅Assignment— hat@@index([resourceId, status, startDate])+@@index([projectId, startDate, endDate])✅
Akzeptanzkriterien
- Neue Indexes in Schema sichtbar
pnpm db:pusherfolgreich- Dev-Server startet sauber
Abhaengigkeiten & Parallelisierung
Wave 1 (format) ─┐
Wave 2 (findUniqueOrThrow) ─┤
Wave 3 (selects) ─┤── Alle parallel ausfuehrbar (verschiedene Dateien/Domains)
Wave 4 (status-styles) ─┤
Wave 5 (invalidation) ─┤
Wave 6 (timeline render) ─┘
Wave 7 (DB indexes) ── Sequentiell NACH Wave 1-6 (erfordert db:push + Restart)
Innerhalb der Waves:
- Wave 1: Task 1.10 (API format-utils) kann parallel zu Tasks 1.1-1.9 (web components)
- Wave 2: Alle Tasks unabhaengig (verschiedene Router-Dateien)
- Wave 4: Task 4.1-4.2 ZUERST (erstellt Exports), dann Tasks 4.3-4.7 parallel
- Wave 5: Task 5.1 ZUERST (Hook-Refactoring), dann Tasks 5.2-5.9 parallel
- Wave 6: Task 6.1 ZUERST (erstellt renderHelpers.ts), dann Tasks 6.2-6.7 parallel
Dateikonflikte vermeiden:
- Wave 2 und Wave 3 editieren teilweise gleiche Router-Dateien (
vacation.ts,entitlement.ts,calculation-rules.ts) — diese Tasks SEQUENTIELL innerhalb eines Agents ausfuehren - Wave 4 und Wave 6 editieren beide Panel-Dateien — unterschiedliche Abschnitte, koennen aber sicherer sequentiell sein
Akzeptanzkriterien (Gesamt)
pnpm --filter @planarchy/web exec tsc --noEmit— null Errorspnpm --filter @planarchy/api exec tsc --noEmit— null Errorspnpm --filter @planarchy/engine exec vitest run— alle Tests gruenpnpm --filter @planarchy/staffing exec vitest run— alle Tests gruen- Dev-Server startet, Timeline rendert in beiden Views
- Overbooking-Blink funktioniert
- Demand-Popover und Resource-Hover-Card funktionieren
- Keine duplizierten
formatMoney/formatCents/fmtEurFunktionsdefinitionen - Keine manuellen
findUnique+ throw Bloecke in Routern (ausser assistant-tools.ts) - Keine duplizierten Vacation-Type/Status-Konstanten
- Keine manuellen 4-Query Timeline-Invalidierungsbloecke
Risiken & Offene Fragen
- Wave 2+3 Dateikonflikt:
vacation.ts,entitlement.ts,calculation-rules.tswerden in Wave 2 UND Wave 3 editiert. Loesung: Ein Agent bearbeitet beide Waves sequentiell fuer diese Dateien. - Wave 5 Type-Cast:
useInvalidatePlanningViewshat einen TypeScript-Cast fuerallocation.listView. Wenn der Cast nach Refactoring bricht, muss der tRPC-Output-Type geprueft werden. - Wave 6 memo-Props:
React.memo()auf Panels erfordert stabile Prop-Referenzen. Wenn Inline-Callbacks als Props uebergeben werden, muss der ParentuseCallbacknutzen — pruefeTimelineViewContent. - Wave 7 DB-Migration:
db:pushauf Produktions-DB erfordert Maintenance-Window fuer Index-Erstellung. Auf Dev-DB unproblematisch. - assistant-tools.ts: 45
findUniqueStellen mit{ error: "..." }Return-Pattern — NICHT auffindUniqueOrThrowmigrieren, da das Error-Handling grundlegend anders ist (Return vs Throw).
Metriken (erwartet)
| Metrik | Vorher | Nachher |
|---|---|---|
| Duplizierte Format-Funktionen | 12+ Inline | 0 (1 zentrale Lib + 1 API-Helfer) |
| Manuelle findUnique+throw | ~35 Stellen | 0 (alle via Helper) |
| Inline Prisma-Selects | ~20 Duplikate | 0 (via Shared Constants) |
| Duplizierte Status-Konstanten | 7 Stellen | 0 (1 zentrale Datei) |
| Manuelle Invalidierungsbloecke | 14+ Stellen | 0 (via Hooks) |
| Duplizierte Render-Funktionen | 3 Paare (6 total) | 3 Shared (renderHelpers.ts) |
| useTimelineDrag.ts Zeilen | 883 | ~800 |
| Fehlende DB-Composite-Indexes | 4 | 0 |