fix: N/A-Aggregationsbug behoben, FeedbackDraftPage migriert, toten Code entfernt
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
f47353dbe1
commit
911dcace72
|
|
@ -116,17 +116,13 @@ MeetingView liest: `const { id, meetingId: meetingIdParam } = useParams()`
|
||||||
|
|
||||||
| Prio | Aufgabe | Datei |
|
| 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 | **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` |
|
| 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 | `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` |
|
| 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` |
|
| 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,20 +109,16 @@ MeetingView liest den Param als: `const { id, meetingId: meetingIdParam } = useP
|
||||||
|
|
||||||
## Bekannte offene Punkte (Priorität nach Dringlichkeit)
|
## Bekannte offene Punkte (Priorität nach Dringlichkeit)
|
||||||
|
|
||||||
### Sofort-Fixes (einfach)
|
|
||||||
1. **Dashboard zeigt keine Assignment-Nummer** — `Dashboard.tsx` Zeile ~57: `{a.assignmentNumber && <span>...{a.assignmentNumber}</span>}` 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
|
### 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
|
1. **`db/queries/`-Layer** — DB-Zugriffe aus Komponenten in domänenspezifische Query-Funktionen extrahieren, bereitet SQLite-Migration vor
|
||||||
5. **`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.
|
||||||
6. **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)
|
### Große Aufgaben (eigene Design-Session nötig)
|
||||||
7. **SQLite WASM + OPFS** — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus.
|
3. **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.
|
4. **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.
|
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.
|
**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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ Stand: 2026-07-03
|
||||||
| Assignment-Nummer ist Pflichtfeld beim Anlegen | `AssignmentCreate.tsx` |
|
| 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` |
|
| 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` |
|
| 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) |
|
| 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 |
|
| 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` | ? |
|
| 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 |
|
| 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 |
|
| # | 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 |
|
| 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` | ? |
|
| 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 |
|
| # | 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 |
|
| 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 |
|
| 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 |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ src/
|
||||||
AssessmentTab.tsx ← Gesamtbewertungs-UI (Accordion je Kategorie), Notation-Vorschlag
|
AssessmentTab.tsx ← Gesamtbewertungs-UI (Accordion je Kategorie), Notation-Vorschlag
|
||||||
CriterionTagPicker.tsx ← Chip+Popover mit Suche, Kriterium-Zuordnung pro Protokoll-Zeile
|
CriterionTagPicker.tsx ← Chip+Popover mit Suche, Kriterium-Zuordnung pro Protokoll-Zeile
|
||||||
AssignmentFeedbackPage.tsx ← Strukturiertes Abschluss-Feedback + KI-Generierung + Trend-Vorschlag
|
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
|
Evaluation.tsx ← Auswertung — zeitgewichteter Bewertungs-Vorschlag + Trend, gruppiert nach Assignment
|
||||||
Configuration.tsx ← Tab-Container für alle Konfigurationsseiten
|
Configuration.tsx ← Tab-Container für alle Konfigurationsseiten
|
||||||
FeedbackStructureConfig.tsx ← Dimensionen/Kategorien/Kriterien CRUD + Gewichtung
|
FeedbackStructureConfig.tsx ← Dimensionen/Kategorien/Kriterien CRUD + Gewichtung
|
||||||
|
|
@ -66,7 +66,7 @@ src/
|
||||||
### DB-Version: 6
|
### DB-Version: 6
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Konfiguration (Legacy, nur noch für FeedbackDraftPage.tsx + Seeds)
|
// Konfiguration (Legacy, kein aktiver Seiten-Konsument mehr)
|
||||||
categories: '++id, order'
|
categories: '++id, order'
|
||||||
criteria: '++id, categoryId, order'
|
criteria: '++id, categoryId, order'
|
||||||
|
|
||||||
|
|
@ -286,7 +286,7 @@ const buildFilename = (ext: string) => {
|
||||||
Die App legt beim ersten Start automatisch an:
|
Die App legt beim ersten Start automatisch an:
|
||||||
|
|
||||||
1. **3 Assignment-Typen** mit je vollständigem Phasen-Flow (Generisch, Case Interview, Stakeholder Meeting / Pitch)
|
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)
|
3. **Feedback-Dimensionsstruktur** mit 5 Dimensionen, 14 Kategorien, ~70 Kriterium-Items (Capgemini-Kompetenzmodell)
|
||||||
4. **Criterion-Mappings** (Legacy, nicht mehr aktiv genutzt)
|
4. **Criterion-Mappings** (Legacy, nicht mehr aktiv genutzt)
|
||||||
|
|
||||||
|
|
@ -296,13 +296,9 @@ Die App legt beim ersten Start automatisch an:
|
||||||
|
|
||||||
| Bereich | Problem | Priorität |
|
| 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 |
|
| `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 |
|
| 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) |
|
| 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) |
|
| 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 |
|
| 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 |
|
| `db/queries/`-Layer | Fehlt — DB-Zugriffe direkt aus Komponenten, erschwert SQLite-Migration | Mittel |
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ async function buildAiPrompt(
|
||||||
const crits = items.filter(i => critIds.includes(i.id!))
|
const crits = items.filter(i => critIds.includes(i.id!))
|
||||||
|
|
||||||
const scoreLines = crits.map(c => {
|
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
|
const avg = scores.length > 0
|
||||||
? (scores.reduce((s, a) => s + ratingToNum(a.score), 0) / scores.length).toFixed(1)
|
? (scores.reduce((s, a) => s + ratingToNum(a.score), 0) / scores.length).toFixed(1)
|
||||||
: '—'
|
: '—'
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,30 @@ import {
|
||||||
db,
|
db,
|
||||||
type Assignment,
|
type Assignment,
|
||||||
type Consultant,
|
type Consultant,
|
||||||
type Category,
|
type FeedbackCategory,
|
||||||
type Criterion,
|
type FeedbackCriterionItem,
|
||||||
type FeedbackDraft,
|
type FeedbackDraft,
|
||||||
type FeedbackRating,
|
type FeedbackRating,
|
||||||
} from '../db'
|
} from '../db'
|
||||||
|
import { RATING_OPTIONS, RATING_NUM_MAP } from '../config/constants'
|
||||||
|
import { numberToRating } from '../utils/ratingTrend'
|
||||||
|
|
||||||
const ratingToNum = (r: FeedbackRating | null | undefined): number => {
|
const ratingToNum = (r: FeedbackRating | null | undefined): number =>
|
||||||
const m: Record<FeedbackRating, number> = { na: 0, not_client_ready: 1, partially_client_ready: 2, nearly_client_ready: 3, client_ready: 4 }
|
r ? (RATING_NUM_MAP[r] ?? 0) : 0
|
||||||
return r ? (m[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 {
|
interface CriterionSummary {
|
||||||
criterion: Criterion
|
criterion: FeedbackCriterionItem
|
||||||
avg: number | null
|
avg: number | null
|
||||||
notes: string[]
|
notes: string[]
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +40,7 @@ export default function FeedbackDraftPage() {
|
||||||
|
|
||||||
const [assignment, setAssignment] = useState<Assignment | null>(null)
|
const [assignment, setAssignment] = useState<Assignment | null>(null)
|
||||||
const [institutee, setInstitutee] = useState<Consultant | null>(null)
|
const [institutee, setInstitutee] = useState<Consultant | null>(null)
|
||||||
const [categories, setCategories] = useState<Category[]>([])
|
const [categories, setCategories] = useState<FeedbackCategory[]>([])
|
||||||
const [summary, setSummary] = useState<CriterionSummary[]>([])
|
const [summary, setSummary] = useState<CriterionSummary[]>([])
|
||||||
const [draft, setDraft] = useState<FeedbackDraft | null>(null)
|
const [draft, setDraft] = useState<FeedbackDraft | null>(null)
|
||||||
const [content, setContent] = useState('')
|
const [content, setContent] = useState('')
|
||||||
|
|
@ -40,7 +51,7 @@ export default function FeedbackDraftPage() {
|
||||||
const [a, cons, cats] = await Promise.all([
|
const [a, cons, cats] = await Promise.all([
|
||||||
db.assignments.get(assignmentId),
|
db.assignments.get(assignmentId),
|
||||||
db.consultants.get(instId),
|
db.consultants.get(instId),
|
||||||
db.categories.orderBy('order').toArray(),
|
db.feedbackCategories.orderBy('order').toArray(),
|
||||||
])
|
])
|
||||||
setAssignment(a ?? null)
|
setAssignment(a ?? null)
|
||||||
setInstitutee(cons ?? null)
|
setInstitutee(cons ?? null)
|
||||||
|
|
@ -56,21 +67,25 @@ export default function FeedbackDraftPage() {
|
||||||
|
|
||||||
const instAssessments = allAssessments.filter(a => a.instituteeId === instId)
|
const instAssessments = allAssessments.filter(a => a.instituteeId === instId)
|
||||||
const instEntries = allEntries.filter(e => e.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
|
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 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
|
const avg = relevant.length > 0
|
||||||
? relevant.reduce((s, a) => s + ratingToNum(a.score), 0) / relevant.length
|
? relevant.reduce((s, a) => s + ratingToNum(a.score), 0) / relevant.length
|
||||||
: null
|
: null
|
||||||
const notes = [
|
const notes = [
|
||||||
...instAssessments.filter(a => a.criteriaId === crit.id && a.note).map(a => a.note),
|
...instAssessments.filter(a => a.criteriaId === crit.id && a.note).map(a => a.note),
|
||||||
...instEntries.filter(e => e.note).map(e => e.note),
|
...allLineTags.filter(t => t.criterionItemId === crit.id).map(t => t.text),
|
||||||
].filter(Boolean)
|
]
|
||||||
return { criterion: crit, avg, notes }
|
return { criterion: crit, avg, notes }
|
||||||
})
|
})
|
||||||
setSummary(sum)
|
setSummary(sum)
|
||||||
|
|
@ -104,8 +119,7 @@ export default function FeedbackDraftPage() {
|
||||||
for (const [catId, items] of grouped) {
|
for (const [catId, items] of grouped) {
|
||||||
text += `## ${catMap.get(catId) ?? 'Sonstige'}\n`
|
text += `## ${catMap.get(catId) ?? 'Sonstige'}\n`
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const avgStr = item.avg !== null ? item.avg.toFixed(1) : '—'
|
text += `${item.criterion.name}: ${ratingLabel(item.avg)}\n`
|
||||||
text += `${item.criterion.name}: ${avgStr}/5\n`
|
|
||||||
if (item.notes.length > 0) {
|
if (item.notes.length > 0) {
|
||||||
const uniqueNotes = [...new Set(item.notes)]
|
const uniqueNotes = [...new Set(item.notes)]
|
||||||
text += uniqueNotes.map(n => ` • ${n}`).join('\n') + '\n'
|
text += uniqueNotes.map(n => ` • ${n}`).join('\n') + '\n'
|
||||||
|
|
@ -146,13 +160,6 @@ export default function FeedbackDraftPage() {
|
||||||
}, 600)
|
}, 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 <div className="text-center py-10 text-gray-400">Lädt…</div>
|
if (!assignment || !institutee) return <div className="text-center py-10 text-gray-400">Lädt…</div>
|
||||||
|
|
||||||
return (
|
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">
|
className="flex items-center justify-between py-1.5 border-b border-gray-50 last:border-0">
|
||||||
<span className="text-sm text-gray-700">{s.criterion.name}</span>
|
<span className="text-sm text-gray-700">{s.criterion.name}</span>
|
||||||
<span className={`text-sm font-semibold px-2 py-0.5 rounded min-w-[40px] text-center ${scoreColor(s.avg)}`}>
|
<span className={`text-sm font-semibold px-2 py-0.5 rounded min-w-[40px] text-center ${scoreColor(s.avg)}`}>
|
||||||
{s.avg !== null ? s.avg.toFixed(1) : '—'}
|
{ratingLabel(s.avg)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,12 @@
|
||||||
import { useEffect, useState } from 'react'
|
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() {
|
export default function FeedbackStructureConfig() {
|
||||||
const [dimensions, setDimensions] = useState<FeedbackDimension[]>([])
|
const [dimensions, setDimensions] = useState<FeedbackDimension[]>([])
|
||||||
const [categories, setCategories] = useState<FeedbackCategory[]>([])
|
const [categories, setCategories] = useState<FeedbackCategory[]>([])
|
||||||
const [items, setItems] = useState<FeedbackCriterionItem[]>([])
|
const [items, setItems] = useState<FeedbackCriterionItem[]>([])
|
||||||
const [assignmentTypes, setAssignmentTypes] = useState<AssignmentType[]>([])
|
|
||||||
const [expanded, setExpanded] = useState<Set<number>>(new Set())
|
const [expanded, setExpanded] = useState<Set<number>>(new Set())
|
||||||
const [expandedCat, setExpandedCat] = useState<Set<number>>(new Set())
|
const [expandedCat, setExpandedCat] = useState<Set<number>>(new Set())
|
||||||
const [showCatalogs, setShowCatalogs] = useState(false)
|
|
||||||
const [phaseTemplates, setPhaseTemplates] = useState<PhaseTemplate[]>([])
|
|
||||||
const [showTypeConfig, setShowTypeConfig] = useState(false)
|
|
||||||
const [expandedType, setExpandedType] = useState<number | null>(null)
|
|
||||||
const [newTypeName, setNewTypeName] = useState('')
|
|
||||||
const [newPhase, setNewPhase] = useState<Record<number, { label: string; withWhom: 'lead'|'client'; isRepeatable: boolean; hasConversation: boolean; hasAssessment: boolean }>>({})
|
|
||||||
const [typeError, setTypeError] = useState<Record<number, string>>({})
|
|
||||||
|
|
||||||
// Neue Einträge
|
// Neue Einträge
|
||||||
const [newDimName, setNewDimName] = useState('')
|
const [newDimName, setNewDimName] = useState('')
|
||||||
|
|
@ -22,18 +14,14 @@ export default function FeedbackStructureConfig() {
|
||||||
const [newItemName, setNewItemName] = useState<Record<number, string>>({})
|
const [newItemName, setNewItemName] = useState<Record<number, string>>({})
|
||||||
|
|
||||||
const load = async () => {
|
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.feedbackDimensions.orderBy('order').toArray(),
|
||||||
db.feedbackCategories.orderBy('order').toArray(),
|
db.feedbackCategories.orderBy('order').toArray(),
|
||||||
db.feedbackCriterionItems.orderBy('order').toArray(),
|
db.feedbackCriterionItems.orderBy('order').toArray(),
|
||||||
db.assignmentTypes.toArray(),
|
|
||||||
db.phaseTemplates.toArray(),
|
|
||||||
])
|
])
|
||||||
setDimensions(dims)
|
setDimensions(dims)
|
||||||
setCategories(cats)
|
setCategories(cats)
|
||||||
setItems(itms)
|
setItems(itms)
|
||||||
setAssignmentTypes(types)
|
|
||||||
setPhaseTemplates(phases)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { load() }, [])
|
useEffect(() => { load() }, [])
|
||||||
|
|
@ -125,70 +113,6 @@ export default function FeedbackStructureConfig() {
|
||||||
setItems(prev => prev.map(i => i.id === id ? { ...i, weight } : i))
|
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<PhaseTemplate>) => {
|
|
||||||
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)
|
const sorted = [...dimensions].sort((a, b) => a.order - b.order)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user