From 911dcace7263466932ff37ad00ade0c778119332 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 3 Jul 2026 13:54:19 +0200 Subject: [PATCH] fix: N/A-Aggregationsbug behoben, FeedbackDraftPage migriert, toten Code entfernt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buildAiPrompt (AssignmentFeedbackPage.tsx): 'na' wird jetzt korrekt aus dem Score-Mittelwert ausgeschlossen (war bislang als 0 mitgerechnet) - FeedbackDraftPage.tsx auf feedbackCriterionItems/feedbackCategories migriert, letzter Konsument der Legacy-Tabellen außer den Seeds. Notizen hängen jetzt korrekt pro Kriterium über lineTags, statt ungefiltert an jedes Kriterium. Score-Anzeige nutzt RATING_OPTIONS-Labels statt rohem /5-Wert. - FeedbackStructureConfig.tsx: verwaisten AssignmentType/PhaseTemplate-CRUD-Code entfernt (Funktionalität war bereits nach AssignmentTypeConfig.tsx verschoben) - Doku aktualisiert: CLAUDE.md, docs/HANDOVER.md, docs/STATUS_UND_OFFENE_PUNKTE.md, docs/TECHNISCHE_ARCHITEKTUR.md Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 6 +- docs/HANDOVER.md | 18 +++--- docs/STATUS_UND_OFFENE_PUNKTE.md | 7 +-- docs/TECHNISCHE_ARCHITEKTUR.md | 10 +--- src/pages/AssignmentFeedbackPage.tsx | 2 +- src/pages/FeedbackDraftPage.tsx | 53 ++++++++++-------- src/pages/FeedbackStructureConfig.tsx | 80 +-------------------------- 7 files changed, 47 insertions(+), 129 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 65ea664..066de99 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -116,17 +116,13 @@ MeetingView liest: `const { id, meetingId: meetingIdParam } = useParams()` | Prio | Aufgabe | Datei | |---|---|---| -| H | N/A-Aggregationsbug fixen (siehe Bug-Klasse oben) | `src/pages/AssignmentFeedbackPage.tsx`, `src/pages/FeedbackDraftPage.tsx` | | H | **KI-Generierung derzeit funktionsunfähig**: `generateWithAi()` schlägt beim Ausführen fehl, vermutlich blockiert das Capgemini-Netzwerk/Firewall den Call zu `openrouter.ai`. Root Cause noch nicht untersucht — ggf. mit internem IT-Support klären. | `src/pages/AssignmentFeedbackPage.tsx` | | H | **Größeres Vorhaben (noch nicht gescoped):** Mehrstufiges, pro Dimension konfigurierbares Prompt-System mit Spezialwissen je Dimension + Platzhaltersystem — Ablösung des aktuellen Einzel-Prompts in `buildAiPrompt`. Braucht eigene Design-Session. | `src/pages/AssignmentFeedbackPage.tsx` | -| M | `FeedbackDraftPage.tsx` auf neue Datenstruktur migrieren (nutzt noch `categories`/`criteria`) | `src/pages/FeedbackDraftPage.tsx` | | M | `db/queries/`-Layer einführen (Vorbereitung SQLite-Migration, DB-Calls aus Komponenten extrahieren) | `src/db/queries/` (neu) | | M | **Selbstlernendes Tagging (Idee, noch nicht gescoped):** Zeile-zu-Kriterium-Zuordnungen sammeln und daraus beim Tippen automatische Tag-Vorschläge ableiten (ähnliche Formulierungen → gleiches Kriterium). Braucht eigene Design-Session (Ähnlichkeits-/Lernmechanik, Datenbasis, UI). | `src/pages/meeting/CriterionTagPicker.tsx` | -| N | Dashboard: Assignment-Nummer anzeigen | `src/pages/Dashboard.tsx` | -| N | Toter Code in FeedbackStructureConfig entfernen | `src/pages/FeedbackStructureConfig.tsx` | | L | SQLite WASM + OPFS Migration (kein festes Datum) | `src/db/schema.ts` | -**Erledigt (2026-07-02):** Konfiguration zentralisiert (`config/constants.ts`), `db/index.ts` in types/schema/seeds aufgeteilt, `MeetingView.tsx` in 3 Dateien aufgeteilt (`pages/meeting/`), `Evaluation.tsx` auf `feedbackCriterionItems` migriert + nach Assignment gruppiert + Papierkorb/Status-Filter + N/A-Bug gefixt, `zustand` entfernt (war ungenutzt). Zeitgewichteter Bewertungs-Vorschlag mit Trend-Erkennung umgesetzt (`src/utils/ratingTrend.ts`, neue Tabelle `appSettings`, neuer Config-Tab „Gewichtung“, Vorschlags-Badge + Übernehmen/Trend-in-Text auf `AssignmentFeedbackPage`, `buildAiPrompt` um Trend-Abschnitt erweitert und Legacy-`db.criteria`-Bug dabei gefixt). Kriterium-Zuordnung für Freitext-Notizen umgesetzt: `ConversationEntry.lineTags` (Zeilentext → Kriterium, nach einem verworfenen Zwischenstand mit "ein Entry pro Zeile" — siehe Sonstige Fallstricke), `CriterionTagPicker.tsx` (Chip+Popover mit Suche, unverändert seit erster Version) wird pro Zeile direkt an der Zeile via `NotationText`s neuer optionaler `renderLineAddon`-Prop gerendert; Zuordnung passiert bewusst nur nachträglich (nicht live während des Tippens), um den Schreibfluss nicht zu stören. `buildAiPrompt` führt Assessment-Notizen und getaggte Zeilen jetzt pro Kriterium zusammen, unzugeordnete Zeilen bleiben im allgemeinen Protokoll-Block. **(2026-07-03)** Vier Folge-Fixes: `meetingExport.ts` löste Kriterien über die Legacy-Tabelle `db.criteria` auf (identischer Bug wie der frühere `buildAiPrompt`-Fix) — dadurch war die Gesamtbewertung im Export faktisch leer; jetzt auf `feedbackCriterionItems` umgestellt, Markdown-Export zeigt zusätzlich das Kriterium pro getaggter Protokollzeile. `dbBackup.ts`s `TABLE_NAMES` enthielt `appSettings` nicht — Voll-Backups verloren die Gewichtungs-Config stillschweigend; jetzt ergänzt samt Kommentar, dass neue Tabellen dort manuell nachgezogen werden müssen. Notation → Bewertungs-Vorschlag: `detectNotationRating()` in `notationParser.tsx` (`!`→Not, `(!)`→Partially, `(+)`→Nearly, `+`→Fully) kombiniert mit `lineTags` liefert in `AssessmentTab.tsx` einen "Vorschlag aus Protokoll"-Badge pro Kriterium-Item, unabhängig vom bestehenden "Aus Protokoll"-Button (andere Datenquelle). `AssignmentCreate.tsx`: Assignment-Nummer ist jetzt Pflichtfeld (nur auf Formular-Ebene, Typ bleibt optional wegen Altdaten). Kriterium-Gewichtung auf `0,0–2,0` in `0,1`-Schritten verfeinert (`FeedbackStructureConfig.tsx`, vorher `×1`–`×5`), dabei einen latenten Division-durch-Null-Bug gefixt, der durch das neu erlaubte Gewicht `0` real wurde (`catMode()` in `AssessmentTab.tsx`, `buildGroupMeetingScores()` in `ratingTrend.ts` — siehe zweite Bug-Klasse oben). Sprecher eines bereits erfassten Protokoll-Beitrags kann nachträglich geändert werden (`ConversationTab.tsx`, im Bearbeiten-Modus neben Füllwörtern — ändert sofort `ConversationEntry.instituteeId`, kein separater Speichern-Schritt nötig). +**Erledigt (2026-07-02):** Konfiguration zentralisiert (`config/constants.ts`), `db/index.ts` in types/schema/seeds aufgeteilt, `MeetingView.tsx` in 3 Dateien aufgeteilt (`pages/meeting/`), `Evaluation.tsx` auf `feedbackCriterionItems` migriert + nach Assignment gruppiert + Papierkorb/Status-Filter + N/A-Bug gefixt, `zustand` entfernt (war ungenutzt). Zeitgewichteter Bewertungs-Vorschlag mit Trend-Erkennung umgesetzt (`src/utils/ratingTrend.ts`, neue Tabelle `appSettings`, neuer Config-Tab „Gewichtung“, Vorschlags-Badge + Übernehmen/Trend-in-Text auf `AssignmentFeedbackPage`, `buildAiPrompt` um Trend-Abschnitt erweitert und Legacy-`db.criteria`-Bug dabei gefixt). Kriterium-Zuordnung für Freitext-Notizen umgesetzt: `ConversationEntry.lineTags` (Zeilentext → Kriterium, nach einem verworfenen Zwischenstand mit "ein Entry pro Zeile" — siehe Sonstige Fallstricke), `CriterionTagPicker.tsx` (Chip+Popover mit Suche, unverändert seit erster Version) wird pro Zeile direkt an der Zeile via `NotationText`s neuer optionaler `renderLineAddon`-Prop gerendert; Zuordnung passiert bewusst nur nachträglich (nicht live während des Tippens), um den Schreibfluss nicht zu stören. `buildAiPrompt` führt Assessment-Notizen und getaggte Zeilen jetzt pro Kriterium zusammen, unzugeordnete Zeilen bleiben im allgemeinen Protokoll-Block. **(2026-07-03)** Vier Folge-Fixes: `meetingExport.ts` löste Kriterien über die Legacy-Tabelle `db.criteria` auf (identischer Bug wie der frühere `buildAiPrompt`-Fix) — dadurch war die Gesamtbewertung im Export faktisch leer; jetzt auf `feedbackCriterionItems` umgestellt, Markdown-Export zeigt zusätzlich das Kriterium pro getaggter Protokollzeile. `dbBackup.ts`s `TABLE_NAMES` enthielt `appSettings` nicht — Voll-Backups verloren die Gewichtungs-Config stillschweigend; jetzt ergänzt samt Kommentar, dass neue Tabellen dort manuell nachgezogen werden müssen. Notation → Bewertungs-Vorschlag: `detectNotationRating()` in `notationParser.tsx` (`!`→Not, `(!)`→Partially, `(+)`→Nearly, `+`→Fully) kombiniert mit `lineTags` liefert in `AssessmentTab.tsx` einen "Vorschlag aus Protokoll"-Badge pro Kriterium-Item, unabhängig vom bestehenden "Aus Protokoll"-Button (andere Datenquelle). `AssignmentCreate.tsx`: Assignment-Nummer ist jetzt Pflichtfeld (nur auf Formular-Ebene, Typ bleibt optional wegen Altdaten). Kriterium-Gewichtung auf `0,0–2,0` in `0,1`-Schritten verfeinert (`FeedbackStructureConfig.tsx`, vorher `×1`–`×5`), dabei einen latenten Division-durch-Null-Bug gefixt, der durch das neu erlaubte Gewicht `0` real wurde (`catMode()` in `AssessmentTab.tsx`, `buildGroupMeetingScores()` in `ratingTrend.ts` — siehe zweite Bug-Klasse oben). Sprecher eines bereits erfassten Protokoll-Beitrags kann nachträglich geändert werden (`ConversationTab.tsx`, im Bearbeiten-Modus neben Füllwörtern — ändert sofort `ConversationEntry.instituteeId`, kein separater Speichern-Schritt nötig). N/A-Aggregationsbug in `buildAiPrompt` (`AssignmentFeedbackPage.tsx`) gefixt — war die letzte offene Stelle dieser Bug-Klasse außer der jetzt migrierten `FeedbackDraftPage.tsx`. `FeedbackDraftPage.tsx` vollständig auf `feedbackCriterionItems`/`feedbackCategories` migriert (letzter Konsument der Legacy-Tabellen ist jetzt nur noch der Seed-Code), Notizen jetzt korrekt pro Kriterium über `lineTags` statt aller Protokoll-Notizen ungefiltert an jedes Kriterium gehängt, Score-Anzeige auf `RATING_OPTIONS`-Labels umgestellt (statt `/5`-Rohwert). Toter Code (verwaiste AssignmentType/PhaseTemplate-CRUD-Reste) aus `FeedbackStructureConfig.tsx` entfernt. „Dashboard zeigt keine Assignment-Nummer" war bereits gelöst, nur die Doku war veraltet — richtiggestellt. --- diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md index 40f150c..b0d5e84 100644 --- a/docs/HANDOVER.md +++ b/docs/HANDOVER.md @@ -109,20 +109,16 @@ MeetingView liest den Param als: `const { id, meetingId: meetingIdParam } = useP ## Bekannte offene Punkte (Priorität nach Dringlichkeit) -### Sofort-Fixes (einfach) -1. **Dashboard zeigt keine Assignment-Nummer** — `Dashboard.tsx` Zeile ~57: `{a.assignmentNumber && ...{a.assignmentNumber}}` hinzufügen -2. **Toter Code in FeedbackStructureConfig** — State-Variablen `assignmentTypes`, `phaseTemplates`, `showCatalogs`, `showTypeConfig`, `expandedType`, `newTypeName`, `newPhase`, `typeError` + alle Handler `toggleCatalogCategory`, `addAssignmentType`, `updateAssignmentTypeName`, `deleteAssignmentType`, `addPhaseTemplate`, `updatePhaseTemplate`, `deletePhaseTemplate` entfernen -3. **N/A-Aggregationsbug**: Filter der Form `a.score !== null` schließen `'na'` nicht aus (N/A ist ein String, kein `null`!) → verfälscht Mittelwerte. Bereits gefixt in `Evaluation.tsx`. Noch offen in `AssignmentFeedbackPage.tsx:63` (buildAiPrompt) und `FeedbackDraftPage.tsx:66`. - ### Mittlere Aufgaben -4. **FeedbackDraftPage.tsx** — nutzt noch Legacy `categories`/`criteria`, sollte auf `feedbackCriterionItems` migriert werden (analog zu `Evaluation.tsx`); letzter Konsument der Legacy-Tabellen außer den Seeds -5. **`db/queries/`-Layer** — DB-Zugriffe aus Komponenten in domänenspezifische Query-Funktionen extrahieren, bereitet SQLite-Migration vor -6. **KI-Generierung funktionsunfähig** — `generateWithAi()` schlägt fehl, vermutlich Capgemini-Netzwerk/Firewall blockiert `openrouter.ai`. Root Cause nicht untersucht. +1. **`db/queries/`-Layer** — DB-Zugriffe aus Komponenten in domänenspezifische Query-Funktionen extrahieren, bereitet SQLite-Migration vor +2. **KI-Generierung funktionsunfähig** — `generateWithAi()` schlägt fehl, vermutlich Capgemini-Netzwerk/Firewall blockiert `openrouter.ai`. Root Cause nicht untersucht. ### Große Aufgaben (eigene Design-Session nötig) -7. **SQLite WASM + OPFS** — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus. -8. **Mehrstufiges, pro Dimension konfigurierbares Prompt-System** — löst den aktuellen Einzel-Prompt in `buildAiPrompt` ab, braucht Spezialwissen je Dimension + Platzhaltersystem. -9. **Selbstlernendes Tagging** — aus bisherigen Zeile-zu-Kriterium-Zuordnungen automatische Tag-Vorschläge beim Tippen ableiten. +3. **SQLite WASM + OPFS** — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus. +4. **Mehrstufiges, pro Dimension konfigurierbares Prompt-System** — löst den aktuellen Einzel-Prompt in `buildAiPrompt` ab, braucht Spezialwissen je Dimension + Platzhaltersystem. +5. **Selbstlernendes Tagging** — aus bisherigen Zeile-zu-Kriterium-Zuordnungen automatische Tag-Vorschläge beim Tippen ableiten. + +**Erledigt (2026-07-03, zweite Runde):** N/A-Aggregationsbug in `buildAiPrompt` gefixt. `FeedbackDraftPage.tsx` vollständig auf `feedbackCriterionItems`/`feedbackCategories` migriert (Notizen jetzt korrekt pro Kriterium über `lineTags`, Score-Anzeige über `RATING_OPTIONS`). Toter Code in `FeedbackStructureConfig.tsx` entfernt. "Dashboard zeigt keine Assignment-Nummer" war bereits gelöst — nur die Doku war veraltet. **Erledigt (2026-07-02):** Zeitgewichteter Bewertungs-Vorschlag mit Trend-Erkennung — `src/utils/ratingTrend.ts` berechnet pro Kategorie einen zeitgewichteten Vorschlag aus den Meeting-Assessments (Gewichtungsfaktor konfigurierbar unter Config → Gewichtung, neue Tabelle `appSettings`), erkennt Verbesserung/Verschlechterung über die Laufzeit und zeigt beides als Badge auf `AssignmentFeedbackPage` (mit „Übernehmen" + „Trend in Text übernehmen"). `buildAiPrompt` wurde um einen Trend-Abschnitt erweitert; dabei wurde auch der alte `db.criteria`-Bug mitgefixt. diff --git a/docs/STATUS_UND_OFFENE_PUNKTE.md b/docs/STATUS_UND_OFFENE_PUNKTE.md index ec8cb65..e5819f4 100644 --- a/docs/STATUS_UND_OFFENE_PUNKTE.md +++ b/docs/STATUS_UND_OFFENE_PUNKTE.md @@ -45,6 +45,9 @@ Stand: 2026-07-03 | Assignment-Nummer ist Pflichtfeld beim Anlegen | `AssignmentCreate.tsx` | | Kriterium-Gewichtung feiner (0,0–2,0 in 0,1-Schritten statt `×1`–`×5`), inkl. Division-durch-Null-Fix bei Gewicht 0 in allen gewichteten Mittelwerten | `FeedbackStructureConfig.tsx`, `AssessmentTab.tsx`, `ratingTrend.ts` | | Sprecher eines Protokoll-Beitrags nachträglich änderbar (im Bearbeiten-Modus) | `ConversationTab.tsx`, `MeetingView.tsx` | +| N/A-Aggregationsbug in `buildAiPrompt` gefixt (letzte offene Stelle außer Legacy-Code) | `AssignmentFeedbackPage.tsx` | +| `FeedbackDraftPage.tsx` auf `feedbackCriterionItems`/`feedbackCategories` migriert, Notizen korrekt pro Kriterium über `lineTags` (nicht mehr alle Protokoll-Notizen an jedes Kriterium), Score-Anzeige auf `RATING_OPTIONS`-Labels | `FeedbackDraftPage.tsx` | +| Toter Code (verwaiste AssignmentType/PhaseTemplate-CRUD-Reste) entfernt | `FeedbackStructureConfig.tsx` | --- @@ -56,7 +59,6 @@ Stand: 2026-07-03 |---|---|---|---| | H1 | **IndexedDB-Robustheit**: Browser kann IndexedDB unter Speicherdruck löschen. Migration zu SQLite WASM + OPFS geplant. Bis dahin: regelmäßige Backups als einzige Sicherheit. | `db/schema.ts` | L (Wochen) | | H2 | **Backup-Reminder fehlt**: Es gibt keine automatische Erinnerung / keinen Hinweis nach abgeschlossenem Meeting, dass ein Backup erstellt werden sollte. | `pages/meeting/MeetingView.tsx` | S | -| H3 | **N/A-Aggregationsbug**: Filter `a.score !== null` schließt den String `'na'` nicht aus, verfälscht Mittelwerte ("Fantasiewerte"). Gefixt in `Evaluation.tsx`, noch offen in `AssignmentFeedbackPage.tsx:63` und `FeedbackDraftPage.tsx:66`. | `AssignmentFeedbackPage.tsx`, `FeedbackDraftPage.tsx` | XS | | H6 | **KI-Generierung funktionsunfähig**: `generateWithAi()` schlägt beim Ausführen fehl, vermutlich Capgemini-Netzwerk/Firewall blockiert den Call zu `openrouter.ai`. Root Cause noch nicht untersucht. | `AssignmentFeedbackPage.tsx` | ? | | H7 | **Prompt-System-Neubau (noch nicht gescoped)**: Mehrstufiges, pro Dimension konfigurierbares Prompt-System mit Spezialwissen je Dimension + Platzhaltersystem, löst den aktuellen Einzel-Prompt in `buildAiPrompt` ab. Braucht eigene Design-Session. | `AssignmentFeedbackPage.tsx` | L | @@ -64,7 +66,6 @@ Stand: 2026-07-03 | # | Beschreibung | Datei | Aufwand | |---|---|---|---| -| M4 | **FeedbackDraftPage.tsx veraltet**: Nutzt noch Legacy-`Category`/`Criterion`-Tabellen, analog zum ehemaligen Zustand von `Evaluation.tsx`. Letzter verbleibender Konsument der Legacy-Tabellen außer den Seeds. | `FeedbackDraftPage.tsx` | M | | M5 | **`db/queries/`-Layer fehlt**: DB-Zugriffe direkt aus Komponenten (`db.*`), keine Abstraktionsschicht. Erschwert die geplante SQLite-Migration (H1). | `src/db/queries/` (neu) | M | | M6 | **Selbstlernendes Tagging (Idee, noch nicht gescoped)**: Zeile-zu-Kriterium-Zuordnungen sammeln und daraus beim Tippen automatische Tag-Vorschläge ableiten. Braucht eigene Design-Session. | `CriterionTagPicker.tsx` | ? | @@ -72,8 +73,6 @@ Stand: 2026-07-03 | # | Beschreibung | Datei | Aufwand | |---|---|---|---| -| N1 | **Dashboard**: Zeigt keine Assignment-Nummer. Nur Titel + Kunde + Typ. | `Dashboard.tsx` | XS | -| N3 | **FeedbackStructureConfig**: Toter Code — Funktionen für AssignmentType-CRUD wurden verschoben, Deklarationen blieben (`assignmentTypes`, `phaseTemplates` State + alle zugehörigen Handler). | `FeedbackStructureConfig.tsx` | XS | | N5 | **AssignmentCreate**: Erstellt keine `instituteeEnrollments`. Von/Bis-Daten können nur über Edit gepflegt werden. | `AssignmentCreate.tsx` | S | | N6 | **Meeting-Route**: URL ist `/assignment/:id/meeting/:meetingId` — `:id` ist die Assignment-ID, aber die Assignment-Nummer (z.B. CGI-2024-001) ist nicht in der URL. Akzeptiert als nice-to-have. | `App.tsx` | M | diff --git a/docs/TECHNISCHE_ARCHITEKTUR.md b/docs/TECHNISCHE_ARCHITEKTUR.md index 4a907df..061bd9a 100644 --- a/docs/TECHNISCHE_ARCHITEKTUR.md +++ b/docs/TECHNISCHE_ARCHITEKTUR.md @@ -43,7 +43,7 @@ src/ AssessmentTab.tsx ← Gesamtbewertungs-UI (Accordion je Kategorie), Notation-Vorschlag CriterionTagPicker.tsx ← Chip+Popover mit Suche, Kriterium-Zuordnung pro Protokoll-Zeile AssignmentFeedbackPage.tsx ← Strukturiertes Abschluss-Feedback + KI-Generierung + Trend-Vorschlag - FeedbackDraftPage.tsx ← Einfacher Feedback-Entwurf (Legacy, nutzt noch alte Criterion-Tabelle) + FeedbackDraftPage.tsx ← Einfacher Feedback-Entwurf (Legacy-UI, Datenzugriff auf feedbackCriterionItems migriert) Evaluation.tsx ← Auswertung — zeitgewichteter Bewertungs-Vorschlag + Trend, gruppiert nach Assignment Configuration.tsx ← Tab-Container für alle Konfigurationsseiten FeedbackStructureConfig.tsx ← Dimensionen/Kategorien/Kriterien CRUD + Gewichtung @@ -66,7 +66,7 @@ src/ ### DB-Version: 6 ```typescript -// Konfiguration (Legacy, nur noch für FeedbackDraftPage.tsx + Seeds) +// Konfiguration (Legacy, kein aktiver Seiten-Konsument mehr) categories: '++id, order' criteria: '++id, categoryId, order' @@ -286,7 +286,7 @@ const buildFilename = (ext: string) => { Die App legt beim ersten Start automatisch an: 1. **3 Assignment-Typen** mit je vollständigem Phasen-Flow (Generisch, Case Interview, Stakeholder Meeting / Pitch) -2. **Legacy-Kriterien** in `categories` + `criteria` (nur noch von `FeedbackDraftPage.tsx` genutzt, `Evaluation.tsx` ist längst auf `feedbackCriterionItems` migriert) +2. **Legacy-Kriterien** in `categories` + `criteria` (kein aktiver Seiten-Konsument mehr — `Evaluation.tsx` und `FeedbackDraftPage.tsx` sind beide auf `feedbackCriterionItems` migriert; die Tabellen bleiben aus historischen Gründen im Backup/Import) 3. **Feedback-Dimensionsstruktur** mit 5 Dimensionen, 14 Kategorien, ~70 Kriterium-Items (Capgemini-Kompetenzmodell) 4. **Criterion-Mappings** (Legacy, nicht mehr aktiv genutzt) @@ -296,13 +296,9 @@ Die App legt beim ersten Start automatisch an: | Bereich | Problem | Priorität | |---|---|---| -| `FeedbackDraftPage.tsx` | Nutzt alte `Category`/`Criterion`-Tabellen statt neue `feedbackCriterionItems`; letzter Konsument der Legacy-Tabellen außer den Seeds | Mittel | | `meetingExport.ts` | Bewertungs-Labels im Markdown-Export zeigen `FeedbackRating`-Keys (z.B. `client_ready`) statt sprechender Labels | Niedrig | -| `FeedbackStructureConfig.tsx` | Enthält toten Code (Funktionen für gelöschte AssignmentType-CRUD) | Niedrig | -| AI-Prompt & `FeedbackDraftPage` | N/A-Aggregationsbug: `score !== null` schließt `'na'` nicht aus, verfälscht Mittelwerte | Hoch | | KI-Generierung | `generateWithAi()` schlägt fehl (vermutlich Netzwerk/Firewall), Root Cause offen | Hoch | | Prompt-System | Aktueller Einzel-Prompt soll mehrstufig/pro Dimension konfigurierbar werden — eigene Design-Session nötig | Hoch (nicht gescoped) | | Selbstlernendes Tagging | Idee, automatische Tag-Vorschläge aus bisherigen Zuordnungen abzuleiten — eigene Design-Session nötig | Mittel (nicht gescoped) | -| Dashboard | Zeigt keine Assignment-Nummer | Niedrig | | IndexedDB | Keine robuste Persistenz-Garantie (Browser kann löschen) | Hoch → SQLite WASM geplant | | `db/queries/`-Layer | Fehlt — DB-Zugriffe direkt aus Komponenten, erschwert SQLite-Migration | Mittel | diff --git a/src/pages/AssignmentFeedbackPage.tsx b/src/pages/AssignmentFeedbackPage.tsx index 773d4ea..8fe0405 100644 --- a/src/pages/AssignmentFeedbackPage.tsx +++ b/src/pages/AssignmentFeedbackPage.tsx @@ -71,7 +71,7 @@ async function buildAiPrompt( const crits = items.filter(i => critIds.includes(i.id!)) const scoreLines = crits.map(c => { - const scores = instAssessments.filter(a => a.criteriaId === c.id && a.score !== null) + const scores = instAssessments.filter(a => a.criteriaId === c.id && a.score !== null && a.score !== 'na') const avg = scores.length > 0 ? (scores.reduce((s, a) => s + ratingToNum(a.score), 0) / scores.length).toFixed(1) : '—' diff --git a/src/pages/FeedbackDraftPage.tsx b/src/pages/FeedbackDraftPage.tsx index e15e8bb..d283ada 100644 --- a/src/pages/FeedbackDraftPage.tsx +++ b/src/pages/FeedbackDraftPage.tsx @@ -4,19 +4,30 @@ import { db, type Assignment, type Consultant, - type Category, - type Criterion, + type FeedbackCategory, + type FeedbackCriterionItem, type FeedbackDraft, type FeedbackRating, } from '../db' +import { RATING_OPTIONS, RATING_NUM_MAP } from '../config/constants' +import { numberToRating } from '../utils/ratingTrend' -const ratingToNum = (r: FeedbackRating | null | undefined): number => { - const m: Record = { na: 0, not_client_ready: 1, partially_client_ready: 2, nearly_client_ready: 3, client_ready: 4 } - return r ? (m[r] ?? 0) : 0 +const ratingToNum = (r: FeedbackRating | null | undefined): number => + r ? (RATING_NUM_MAP[r] ?? 0) : 0 + +const ratingLabel = (avg: number | null): string => { + if (avg === null) return '—' + const r = RATING_OPTIONS.find(x => x.value === numberToRating(avg)) + return r ? `${r.short} (${avg.toFixed(1)})` : avg.toFixed(1) +} + +const scoreColor = (avg: number | null): string => { + if (avg === null) return 'bg-gray-100 text-gray-400' + return RATING_OPTIONS.find(r => r.value === numberToRating(avg))?.color ?? 'bg-gray-100 text-gray-400' } interface CriterionSummary { - criterion: Criterion + criterion: FeedbackCriterionItem avg: number | null notes: string[] } @@ -29,7 +40,7 @@ export default function FeedbackDraftPage() { const [assignment, setAssignment] = useState(null) const [institutee, setInstitutee] = useState(null) - const [categories, setCategories] = useState([]) + const [categories, setCategories] = useState([]) const [summary, setSummary] = useState([]) const [draft, setDraft] = useState(null) const [content, setContent] = useState('') @@ -40,7 +51,7 @@ export default function FeedbackDraftPage() { const [a, cons, cats] = await Promise.all([ db.assignments.get(assignmentId), db.consultants.get(instId), - db.categories.orderBy('order').toArray(), + db.feedbackCategories.orderBy('order').toArray(), ]) setAssignment(a ?? null) setInstitutee(cons ?? null) @@ -56,21 +67,25 @@ export default function FeedbackDraftPage() { const instAssessments = allAssessments.filter(a => a.instituteeId === instId) const instEntries = allEntries.filter(e => e.instituteeId === instId) + const allLineTags = instEntries.flatMap(e => e.lineTags ?? []) - const criteriaIds = [...new Set(instAssessments.map(a => a.criteriaId))] + const criteriaIds = [...new Set([ + ...instAssessments.map(a => a.criteriaId), + ...allLineTags.map(t => t.criterionItemId), + ])] const crits = criteriaIds.length > 0 - ? await db.criteria.where('id').anyOf(criteriaIds).toArray() + ? await db.feedbackCriterionItems.where('id').anyOf(criteriaIds).toArray() : [] const sum: CriterionSummary[] = crits.map(crit => { - const relevant = instAssessments.filter(a => a.criteriaId === crit.id && a.score !== null) + const relevant = instAssessments.filter(a => a.criteriaId === crit.id && a.score !== null && a.score !== 'na') const avg = relevant.length > 0 ? relevant.reduce((s, a) => s + ratingToNum(a.score), 0) / relevant.length : null const notes = [ ...instAssessments.filter(a => a.criteriaId === crit.id && a.note).map(a => a.note), - ...instEntries.filter(e => e.note).map(e => e.note), - ].filter(Boolean) + ...allLineTags.filter(t => t.criterionItemId === crit.id).map(t => t.text), + ] return { criterion: crit, avg, notes } }) setSummary(sum) @@ -104,8 +119,7 @@ export default function FeedbackDraftPage() { for (const [catId, items] of grouped) { text += `## ${catMap.get(catId) ?? 'Sonstige'}\n` for (const item of items) { - const avgStr = item.avg !== null ? item.avg.toFixed(1) : '—' - text += `${item.criterion.name}: ${avgStr}/5\n` + text += `${item.criterion.name}: ${ratingLabel(item.avg)}\n` if (item.notes.length > 0) { const uniqueNotes = [...new Set(item.notes)] text += uniqueNotes.map(n => ` • ${n}`).join('\n') + '\n' @@ -146,13 +160,6 @@ export default function FeedbackDraftPage() { }, 600) } - const scoreColor = (avg: number | null) => { - if (avg === null) return 'bg-gray-100 text-gray-400' - if (avg >= 4) return 'bg-green-100 text-green-700' - if (avg >= 3) return 'bg-yellow-100 text-yellow-700' - return 'bg-red-100 text-red-700' - } - if (!assignment || !institutee) return
Lädt…
return ( @@ -179,7 +186,7 @@ export default function FeedbackDraftPage() { className="flex items-center justify-between py-1.5 border-b border-gray-50 last:border-0"> {s.criterion.name} - {s.avg !== null ? s.avg.toFixed(1) : '—'} + {ratingLabel(s.avg)} ))} diff --git a/src/pages/FeedbackStructureConfig.tsx b/src/pages/FeedbackStructureConfig.tsx index ce3cd54..96f0167 100644 --- a/src/pages/FeedbackStructureConfig.tsx +++ b/src/pages/FeedbackStructureConfig.tsx @@ -1,20 +1,12 @@ import { useEffect, useState } from 'react' -import { db, type FeedbackDimension, type FeedbackCategory, type FeedbackCriterionItem, type AssignmentType, type PhaseTemplate } from '../db' +import { db, type FeedbackDimension, type FeedbackCategory, type FeedbackCriterionItem } from '../db' export default function FeedbackStructureConfig() { const [dimensions, setDimensions] = useState([]) const [categories, setCategories] = useState([]) const [items, setItems] = useState([]) - const [assignmentTypes, setAssignmentTypes] = useState([]) const [expanded, setExpanded] = useState>(new Set()) const [expandedCat, setExpandedCat] = useState>(new Set()) - const [showCatalogs, setShowCatalogs] = useState(false) - const [phaseTemplates, setPhaseTemplates] = useState([]) - const [showTypeConfig, setShowTypeConfig] = useState(false) - const [expandedType, setExpandedType] = useState(null) - const [newTypeName, setNewTypeName] = useState('') - const [newPhase, setNewPhase] = useState>({}) - const [typeError, setTypeError] = useState>({}) // Neue Einträge const [newDimName, setNewDimName] = useState('') @@ -22,18 +14,14 @@ export default function FeedbackStructureConfig() { const [newItemName, setNewItemName] = useState>({}) const load = async () => { - const [dims, cats, itms, types, phases] = await Promise.all([ + const [dims, cats, itms] = await Promise.all([ db.feedbackDimensions.orderBy('order').toArray(), db.feedbackCategories.orderBy('order').toArray(), db.feedbackCriterionItems.orderBy('order').toArray(), - db.assignmentTypes.toArray(), - db.phaseTemplates.toArray(), ]) setDimensions(dims) setCategories(cats) setItems(itms) - setAssignmentTypes(types) - setPhaseTemplates(phases) } useEffect(() => { load() }, []) @@ -125,70 +113,6 @@ export default function FeedbackStructureConfig() { setItems(prev => prev.map(i => i.id === id ? { ...i, weight } : i)) } - const toggleCatalogCategory = async (type: AssignmentType, catId: number) => { - const current = type.criteriaIds ?? [] - const next = current.includes(catId) ? current.filter(id => id !== catId) : [...current, catId] - await db.assignmentTypes.update(type.id!, { criteriaIds: next }) - setAssignmentTypes(prev => prev.map(t => t.id === type.id ? { ...t, criteriaIds: next } : t)) - } - - // ── AssignmentType CRUD ─────────────────────────────────────────────────── - - const addAssignmentType = async () => { - if (!newTypeName.trim()) return - await db.assignmentTypes.add({ name: newTypeName.trim(), defaultPhaseKeys: [] }) - setNewTypeName('') - await load() - } - - const updateAssignmentTypeName = async (id: number, name: string) => { - await db.assignmentTypes.update(id, { name }) - setAssignmentTypes(prev => prev.map(t => t.id === id ? { ...t, name } : t)) - } - - const deleteAssignmentType = async (id: number) => { - const count = await db.assignments.where('assignmentTypeId').equals(id).count() - if (count > 0) { - setTypeError(prev => ({ ...prev, [id]: `Kann nicht gelöscht werden – ${count} Assignment(s) verwenden diesen Typ.` })) - return - } - await db.phaseTemplates.where('assignmentTypeId').equals(id).delete() - await db.assignmentTypes.delete(id) - setTypeError(prev => { const n = { ...prev }; delete n[id]; return n }) - await load() - } - - // ── PhaseTemplate CRUD ──────────────────────────────────────────────────── - - const addPhaseTemplate = async (typeId: number) => { - const p = newPhase[typeId] - if (!p?.label.trim()) return - const existing = phaseTemplates.filter(t => t.assignmentTypeId === typeId) - await db.phaseTemplates.add({ - assignmentTypeId: typeId, - phaseKey: p.label.trim().toLowerCase().replace(/\s+/g, '_'), - label: p.label.trim(), - withWhom: p.withWhom, - isRepeatable: p.isRepeatable, - hasConversation: p.hasConversation, - hasAssessment: p.hasAssessment, - criteriaIds: [], - order: existing.length + 1, - }) - setNewPhase(prev => ({ ...prev, [typeId]: { label: '', withWhom: 'lead', isRepeatable: false, hasConversation: false, hasAssessment: false } })) - await load() - } - - const updatePhaseTemplate = async (id: number, fields: Partial) => { - await db.phaseTemplates.update(id, fields) - setPhaseTemplates(prev => prev.map(t => t.id === id ? { ...t, ...fields } : t)) - } - - const deletePhaseTemplate = async (id: number) => { - await db.phaseTemplates.delete(id) - setPhaseTemplates(prev => prev.filter(t => t.id !== id)) - } - const sorted = [...dimensions].sort((a, b) => a.order - b.order) return (