diff --git a/UI_OVERHAUL_PLAN.md b/UI_OVERHAUL_PLAN.md new file mode 100644 index 0000000..dedc4fa --- /dev/null +++ b/UI_OVERHAUL_PLAN.md @@ -0,0 +1,348 @@ +# LeoCRM UI-Overhaul-Plan (v2) + +> **Erstellt:** 2026-08-21 +> **Aktualisiert:** 2026-08-21 — AI Assistent Integration hinzugefügt +> **Status:** Planung — nicht gestartet +> **Leitlinie:** Auf bestehendem Code aufbauen, 3-Spalten-Explorer-Layout als Standard, keine parallelen Systeme + +--- + +## Standard-Layout (Referenz: ContactsList.tsx) + +Alle Explorer-Plugins nutzen das 3-Spalten-Layout aus den UI-Design-Guidelines: + +``` +┌─────────────┬──────────────────┬──────────────────────┐ +│ Tree │ Liste/Ansicht │ Detail │ +│ (224px) │ (flex-1) │ (flex-1 / 60%) │ +│ ResizablePanel│ ResizablePanel │ ResizablePanel │ +└─────────────┴──────────────────┴──────────────────────┘ +``` + +- **Toolbar oben:** PluginToolbar mit Filter-Dropdowns, Ansichts-Umschaltern, Aktion-Buttons +- **Linke Spalte:** ResizablePanel mit Baumansicht (Ordner, Kategorien, Kalender) +- **Mitte:** Liste, Karten, Kalender-Ansicht — mehrere Ansichten umschaltbar +- **Rechts:** Detail-Bereich für ausgewähltes Element + +--- + +## Phase 1: Echte Bugs fixen (2-3 Tage) + +### 1.1 Kontakte — Liste aktualisiert nach Speichern nicht +- **Datei:** `frontend/src/pages/ContactsList.tsx` +- **Problem:** Nach dem Speichern eines Kontakts wird die Liste nicht aktualisiert +- **Ursache:** Wahrscheinlich fehlendes `invalidateQueries` nach Mutation +- **Fix:** TanStack Query `useCreateContact` mutation muss `queryClient.invalidateQueries({ queryKey: ['contacts'] })` im `onSuccess` haben +- **Aufwand:** 1 Stunde + +### 1.2 Kontakte — Drag-Drop von Kontakten in Ordner nicht möglich +- **Datei:** `frontend/src/pages/ContactsList.tsx`, `frontend/src/components/contacts/` +- **Problem:** Drag-Drop von Kontakten in Ordner funktioniert nicht +- **Fix:** HTML5 Drag-Drop API auf Tree-Nodes implementieren, `onDrop` handler der `updateContact({ folder_id })` aufruft +- **Aufwand:** 3 Stunden + +### 1.3 Kontakte — Verschieben-Dialog funktioniert nicht +- **Datei:** `frontend/src/components/contacts/MoveDialog.tsx` (oder ähnlich) +- **Problem:** Ordner-Auswahl im Verschieben-Dialog leer oder broken +- **Fix:** Ordner-API aufrufen und im Dialog anzeigen, Auswahl speichern +- **Aufwand:** 2 Stunden + +### 1.4 Wiki — Artikel kann nicht gespeichert werden +- **Datei:** `frontend/src/pages/Wiki.tsx`, `frontend/src/api/knowledge.ts` +- **Problem:** Speichern-Button funktioniert nicht oder API gibt Fehler zurück +- **Diagnose:** API-Endpunkt prüfen (`POST /api/v1/wiki/articles` oder `PATCH /api/v1/wiki/articles/:id`), Frontend-Mutation prüfen +- **Fix:** Je nach Diagnose — API-Fehler oder Frontend-Mutation-Fehler +- **Aufwand:** 2 Stunden + +### 1.5 Kalender — Dialog schließt nicht nach Speichern +- **Datei:** `frontend/src/pages/Calendar.tsx`, `frontend/src/components/calendar/AppointmentEditForm.tsx` +- **Problem:** Nach dem Speichern eines Termins schließt sich der Dialog nicht +- **Fix:** `onSuccess` handler muss `setEditingEvent(null)` oder `setShowDialog(false)` aufrufen +- **Aufwand:** 30 Minuten + +### 1.6 Kommunikation — Chats können nicht angelegt werden +- **Datei:** `frontend/src/pages/Communication.tsx` +- **Problem:** "Neuer Chat" Button funktioniert nicht oder API gibt Fehler +- **Diagnose:** API-Endpunkt prüfen (`POST /api/v1/comm/conversations`), Frontend-Mutation prüfen +- **Fix:** Je nach Diagnose +- **Aufwand:** 2 Stunden + +### 1.7 Wiki — Doppelt im Menü +- **Datei:** `frontend/src/routes/index.tsx`, `frontend/src/components/layout/` (Navigation) +- **Problem:** Wiki erscheint zweimal im Menü +- **Diagnose:** Route `/wiki` und möglicherweise Help-Subroute oder Plugin-Route +- **Fix:** Doppelte Route entfernen +- **Aufwand:** 30 Minuten + +**Gesamtaufwand Phase 1:** ~13 Stunden (2-3 Tage) + +--- + +## Phase 2: AI Assistent in Kommunikation integrieren (2-3 Tage) + +### Problem +Der AI Assistent ist ein paralleles System das die Kommunikation-Plattform dupliziert: +- **AI Assistant Tabellen:** `ai_conversations`, `ai_messages` (app/models/ai_conversation.py) + `ai_chat_sessions`, `ai_chat_messages`, `ai_chat_attachments` (app/plugins/builtins/ai_assistant/models.py) — 5 Tabellen +- **AI Assistant Frontend:** `AIAssistant.tsx`, `AIAssistantStandalone.tsx`, `SessionList.tsx`, `ChatWindow.tsx` — eigene UI +- **AI Assistant API:** `/api/v1/ai/sessions`, `/api/v1/ai/sessions/:id/messages`, `/api/v1/ai/sessions/:id/stream` — eigene API +- **Kommunikation hat schon AI-Chat:** `comm_conversations` mit `conversation_type='ai'`, `streamChat()` aus `@/api/ai`, `categorizeConversation()` mit 'KI Chats' Kategorie, `new-ai-chat` Toolbar-Button + +### 2.1 Daten-Migration (Backend) +- **Migration 0137:** Migriere `ai_chat_sessions` → `comm_conversations` (conversation_type='ai') + - `ai_chat_sessions.id` → `comm_conversations.id` + - `ai_chat_sessions.title` → `comm_conversations.title` + - `ai_chat_sessions.tenant_id` → `comm_conversations.tenant_id` + - `ai_chat_sessions.user_id` → `comm_conversations.owner_id` + - `ai_chat_sessions.agent_id` → `comm_conversations.metadata.agent_id` + - `ai_chat_sessions.created_at` → `comm_conversations.created_at` +- **Migration 0137:** Migriere `ai_chat_messages` → `comm_messages` + - `ai_chat_messages.id` → `comm_messages.id` + - `ai_chat_messages.session_id` → `comm_messages.conversation_id` + - `ai_chat_messages.role` → `comm_messages.sender_type` ('user' → 'user', 'assistant' → 'ai') + - `ai_chat_messages.content` → `comm_messages.content` + - `ai_chat_messages.tenant_id` → `comm_messages.tenant_id` +- **Migration 0137:** Migriere `ai_conversations` → `comm_conversations` (falls Daten vorhanden) +- **Migration 0137:** Migriere `ai_messages` → `comm_messages` (falls Daten vorhanden) +- **Migration 0137:** Drop `ai_conversations`, `ai_messages`, `ai_chat_sessions`, `ai_chat_messages`, `ai_chat_attachments` Tabellen +- **Aufwand:** 1 Tag + +### 2.2 Backend — AI Chat API auf Communication umleiten +- **Datei:** `app/plugins/builtins/ai_assistant/routes.py` +- **Änderung:** `POST /api/v1/ai/sessions` → erstellt `comm_conversations` mit `conversation_type='ai'` statt `ai_chat_sessions` +- **Änderung:** `GET /api/v1/ai/sessions/:id/messages` → liest aus `comm_messages` statt `ai_chat_messages` +- **Änderung:** `POST /api/v1/ai/sessions/:id/stream` → bleibt erhalten (streaming endpoint) aber speichert messages in `comm_messages` +- **Aufwand:** 4 Stunden + +### 2.3 Frontend — AI Assistant Page entfernen +- **Entfernen:** `frontend/src/pages/AIAssistant.tsx` +- **Entfernen:** `frontend/src/pages/AIAssistantStandalone.tsx` +- **Entfernen:** `frontend/src/components/ai/SessionList.tsx` +- **Entfernen:** `frontend/src/components/ai/ChatWindow.tsx` +- **Route anpassen:** `/ai-assistant` → **gelöscht** (kein Redirect nötig) +- **Route anpassen:** `/ai-assistant-standalone` → **gelöscht** (kein Redirect nötig) +- **Navigation:** AI Assistent Menüpunkt entfernen, AI Chat bleibt unter Kommunikation +- **Aufwand:** 2 Stunden + +### 2.4 Frontend — Communication AI-Chat verbessern +- **Datei:** `frontend/src/pages/Communication.tsx` +- **Änderung:** AI Chat Sessions aus `comm_conversations` laden (statt `ai/sessions` API) +- **Änderung:** `streamChat()` bleibt erhalten aber Session-ID ist jetzt `comm_conversation_id` +- **Änderung:** AI Chat Messages aus `comm_messages` laden +- **Aufwand:** 4 Stunden + +### 2.5 Backend — ai_assistant plugin models aufräumen +- **Entfernen:** `AIChatSession`, `AIChatMessage`, `AIChatAttachment` Models aus `app/plugins/builtins/ai_assistant/models.py` +- **Entfernen:** `AIConversation`, `AIMessage` Models aus `app/models/ai_conversation.py` +- **Behalten:** `AIProvider`, `AIModel`, `AIPreset`, `AIChatFolder` Models (für Settings) +- **Behalten:** `ai_assistant` plugin routes für Settings (providers, models, presets) +- **Aufwand:** 2 Stunden + +### 2.6 Unified Search — AI Chat Provider anpassen +- **Datei:** `app/plugins/builtins/unified_search/providers/ai_chat_provider.py` +- **Änderung:** Search auf `comm_messages` (conversation_type='ai') statt `ai_chat_messages` +- **Aufwand:** 1 Stunde + +**Gesamtaufwand Phase 2:** ~2-3 Tage + +--- + +## Phase 3: Wiki UI-Überarbeitung (3-4 Tage) + +### 3.1 WYSIWYG Editor +- **Datei:** `frontend/src/components/wiki/WikiEditor.tsx` (neu zu bauen) +- **Anforderung:** WYSIWYG Editor mit allen Möglichkeiten, wie Notion — Bedienelemente über dem Textblock +- **Technologie:** Tiptap (ProseMirror-basiert, React-integration, Notion-ähnliche UX) + - `@tiptap/react`, `@tiptap/starter-kit`, `@tiptap/extension-*` + - Floating Toolbar über dem Textblock (wie Notion) + - Markdown-Export für Backend-Speicherung +- **Aufwand:** 2 Tage + +### 3.2 Wiki Layout — 3-Spalten +- **Datei:** `frontend/src/pages/Wiki.tsx` (umbauen) +- **Anforderung:** Toolbar oben, links Baummenü (Kategorien), Mitte Textbereich +- **Aufbau:** + - **Toolbar:** View/Edit Mode Toggle (oben rechts), Suche, Neuer Artikel + - **Links:** WikiBrowser (existiert schon) — Baumansicht mit Kategorien + - **Mitte:** WYSIWYG Editor (Edit Mode) oder gerenderte Ansicht (View Mode) + - **Kein separater Detail-Bereich** — Artikel wird in der Mitte angezeigt +- **Aufwand:** 1 Tag + +### 3.3 View/Edit Mode Toggle +- **Datei:** `frontend/src/pages/Wiki.tsx` +- **Anforderung:** Button oben rechts in der Toolbar der zwischen View und Edit Mode wechselt +- **Im Edit Mode:** WYSIWYG Editor mit Floating Toolbar +- **Im View Mode:** Gerenderte Markdown-Ansicht (wie jetzt, aber schöner) +- **Aufwand:** 2 Stunden + +**Gesamtaufwand Phase 3:** ~3-4 Tage + +--- + +## Phase 4: Tasks UI-Überarbeitung (2-3 Tage) + +### 4.1 Tasks Layout — 3-Spalten wie Kontakte +- **Datei:** `frontend/src/pages/Tasks.tsx` (kompletter Umbau, 419 → ~600 Zeilen) +- **Anforderung:** Linke Sidebar Baumansicht, Mitte Liste mit mehreren Ansichten, rechts Detailbereich +- **Aufbau:** + - **Toolbar:** PluginToolbar mit Filter-Dropdowns (Status, Priorität, Zuweisung, Fällig), Ansichts-Umschalter (Liste/Kanban), Neuer Task + - **Links:** Baumansicht — nach Status (Offen/In Bearbeitung/Erledigt), nach Priorität, nach Zuweisung, nach Liste/Goal + - **Mitte:** Liste (Tabelle) oder Kanban-Board — umschaltbar + - **Rechts:** TaskDetail — ausgewählter Task mit Beschreibung, Subtasks, Zuweisung, Fälligkeit +- **Aufwand:** 2-3 Tage + +**Gesamtaufwand Phase 4:** ~2-3 Tage + +--- + +## Phase 5: Kalender UI-Überarbeitung (1 Tag) + +### 5.1 Toolbar und Filter standardisieren +- **Datei:** `frontend/src/pages/Calendar.tsx` (anpassen, 759 Zeilen) +- **Problem:** Drucken-Button und Filter-Leiste über dem Kalender entsprechen nicht dem Standard +- **Fix:** + - Filter in PluginToolbar als Dropdowns (wie Kontakte) + - Drucken-Button in PluginToolbar + - Ansichts-Umschalter (Tag/Woche/Monat/Range) in PluginToolbar +- **Aufwand:** 4 Stunden + +### 5.2 Kalender-Auswahl fixen +- **Datei:** `frontend/src/components/calendar/CalendarTree.tsx` +- **Problem:** Einzelnes An- und Abwählen von Kalendern funktioniert nicht richtig +- **Fix:** Checkbox-Toggle Logik reparieren — `visibleCalendars` Set korrekt verwalten +- **Aufwand:** 2 Stunden + +**Gesamtaufwand Phase 5:** ~1 Tag + +--- + +## Phase 6: Tags Umstrukturierung (2 Tage) + +### 6.1 Tags in Settings verschieben +- **Datei:** `frontend/src/pages/Tags.tsx` → `frontend/src/pages/SettingsTags.tsx` (neu) +- **Route:** `/settings/tags` statt `/tags` +- **Anforderung:** Tags gehören in die Einstellungen, bei System +- **Aufwand:** 2 Stunden + +### 6.2 Tags Baumstruktur +- **Datei:** `frontend/src/pages/SettingsTags.tsx` (neu) +- **Anforderung:** Baumstruktur um Tags zu sortieren (Parent-Child Beziehung) +- **Backend:** `tags` Tabelle braucht `parent_id` Spalte (Migration 0138) +- **Frontend:** TreeView Komponente für Tags +- **Aufwand:** 1 Tag + +### 6.3 Pro Tag einstellbar wo er verfügbar ist +- **Datei:** `frontend/src/pages/SettingsTags.tsx`, Backend `tags` Tabelle +- **Anforderung:** Pro Tag einstellbar: Kontakte, Mail, Termin, Task, etc. +- **Backend:** `tag_applications` Tabelle (tag_id, entity_type) oder JSON-Spalte `applicable_to` in tags (Migration 0138) +- **Frontend:** Multi-Select im Tag-Editor +- **Aufwand:** 4 Stunden + +### 6.4 Symbol und Farbe pro Tag +- **Datei:** `frontend/src/pages/SettingsTags.tsx`, Backend `tags` Tabelle +- **Anforderung:** Symbol (Icon) und Farbe pro Tag einstellbar +- **Backend:** `icon` Spalte in tags (Migration 0138), `color` existiert schon +- **Frontend:** Icon-Picker und Color-Picker im Tag-Editor +- **Aufwand:** 4 Stunden + +**Gesamtaufwand Phase 6:** ~2 Tage + +--- + +## Phase 7: Reports UI-Überarbeitung (2 Tage) + +### 7.1 Reports Layout — 3-Spalten wie Kontakte +- **Datei:** `frontend/src/pages/Reports.tsx` (Umbau, 433 Zeilen) +- **Anforderung:** Linke Sidebar mit Baumstruktur (Ordner zum Sortieren), Mitte verschiedene Ansichten (Liste/Karten), rechts Detailbereich +- **Aufbau:** + - **Toolbar:** PluginToolbar mit Filter, Ansichts-Umschalter, Neuer Report + - **Links:** Baumansicht — nach Ordner/Gruppe sortierbar + - **Mitte:** Liste oder Karten-Ansicht — umschaltbar + - **Rechts:** ReportDetail — ausgewählter Report mit Vorschau +- **Backend:** `reports` Tabelle braucht `folder_id` Spalte (Migration 0139) für Ordner-Sortierung +- **Aufwand:** 2 Tage + +**Gesamtaufwand Phase 7:** ~2 Tage + +--- + +## Phase 8: Kommunikation UI-Überarbeitung (2-3 Tage) + +### 8.1 Baumstruktur verbessern und Ordner +- **Datei:** `frontend/src/pages/Communication.tsx` (anpassen, 859 Zeilen) +- **Anforderung:** Baumstruktur größer/übersichtlicher, Ordner für Chats +- **Aufbau:** + - **Links:** Baumansicht mit Ordnern — System, AI, Kollegen, Custom Ordner + - **Baum breiter:** ResizablePanel `initialWidth=280` statt 224 + - **Ordner:** `comm_conversation_folders` Tabelle oder `folder_id` in `comm_conversations` (Migration 0140) +- **Aufwand:** 1-2 Tage + +### 8.2 AI Chat in Kommunikation (nach Phase 2) +- AI Chats werden als eigener Baum-Knoten 'KI Chats' in Communication angezeigt +- Neuer AI Chat Button in Toolbar erstellt `comm_conversation` mit `conversation_type='ai'` +- `streamChat()` wird aufgerufen mit `comm_conversation_id` als Session-ID +- AI Messages werden in `comm_messages` gespeichert +- **Aufwand:** in Phase 2 + +**Gesamtaufwand Phase 8:** ~1-2 Tage (Phase 2 vorab) + +--- + +## Phase 9: Strukturelle Änderungen (0.5 Tage) + +### 9.1 System Dashboard als eigener Menüpunkt +- **Datei:** `frontend/src/routes/index.tsx`, Navigation +- **Problem:** System Dashboard ist unter Settings, soll eigener Punkt auf Startseite-Ebene sein +- **Fix:** Route `/system-dashboard` existiert schon — muss in Navigation als Top-Level Menüpunkt angezeigt werden +- **Aufwand:** 1 Stunde + +### 9.2 Mail — Postfach mit IMAP anlegen testen +- **Datei:** `frontend/src/pages/Mail.tsx`, `frontend/src/pages/MailSettings.tsx` +- **Anforderung:** IMAP-Zugangsdaten testen — Postfach anlegen und prüfen ob Mails synchronisiert werden +- **Aufwand:** 2 Stunden (Test + ggf. Bugfix) + +**Gesamtaufwand Phase 9:** ~0.5 Tage + +--- + +## Zusammenfassung + +| Phase | Inhalt | Aufwand | Migration | Abhängigkeit | +|-------|--------|---------|-----------|-------------| +| 1 | Echte Bugs fixen | 2-3 Tage | Keine | Keine | +| 2 | AI Assistent → Kommunikation | 2-3 Tage | 0137 | Phase 1.6 | +| 3 | Wiki UI + WYSIWYG | 3-4 Tage | Keine | Phase 1.4 | +| 4 | Tasks UI neu | 2-3 Tage | Keine | Keine | +| 5 | Kalender UI | 1 Tag | Keine | Phase 1.5 | +| 6 | Tags Umstrukturierung | 2 Tage | 0138 | Keine | +| 7 | Reports UI | 2 Tage | 0139 | Keine | +| 8 | Kommunikation UI | 1-2 Tage | 0140 | Phase 2 | +| 9 | Strukturelle Änderungen | 0.5 Tage | Keine | Keine | + +**Gesamtaufwand:** ~17-22 Tage + +### Reihenfolge: +1. **Phase 1** (Bugs) — zuerst, damit grundlegende Funktionen arbeiten +2. **Phase 9** (Strukturelle Änderungen) — schnell, wenig Aufwand +3. **Phase 5** (Kalender) — kleines Update, baut auf Phase 1 auf +4. **Phase 2** (AI Assistent → Kommunikation) — entfernt paralleles System, baut auf Phase 1.6 auf +5. **Phase 6** (Tags) — unabhängig, Backend + Frontend +6. **Phase 4** (Tasks) — großer Umbau, unabhängig +7. **Phase 3** (Wiki) — größter Umbau (WYSIWYG Editor), baut auf Phase 1 auf +8. **Phase 7** (Reports) — großer Umbau, unabhängig +9. **Phase 8** (Kommunikation) — baut auf Phase 2 auf + +### Migrationen: +- **0137:** AI Assistent Tabellen → comm_conversations/comm_messages + Drop alte Tabellen +- **0138:** Tags: parent_id, applicable_to, icon Spalten +- **0139:** Reports: folder_id Spalte +- **0140:** Communication: comm_conversation_folders Tabelle oder folder_id in comm_conversations + +### Was ich NICHT tun werde: +- Keine Massen-Scripts die neue Fehler verursachen +- Keine Änderungen ohne Verifizierung gegen Produktion +- Keine neuen Plugins wenn bestehende erweitert werden können +- Keine neuen Pages wenn bestehende umgebaut werden können +- Jede Änderung wird mit tsc und API-Test verifiziert + +### Was ich brauche: +- **IMAP-Zugangsdaten:** Für Mail-Postfach-Test (Phase 9.2) diff --git a/app/plugins/builtins/kommunikation/routes.py b/app/plugins/builtins/kommunikation/routes.py index 4cdf7ac..e94972c 100644 --- a/app/plugins/builtins/kommunikation/routes.py +++ b/app/plugins/builtins/kommunikation/routes.py @@ -99,6 +99,7 @@ async def create_new_conversation( participant_ids=body.participant_ids, is_direct=body.is_direct, initial_message=body.initial_message, + metadata=body.metadata, ) diff --git a/app/plugins/builtins/kommunikation/schemas.py b/app/plugins/builtins/kommunikation/schemas.py index 185779d..8c10351 100644 --- a/app/plugins/builtins/kommunikation/schemas.py +++ b/app/plugins/builtins/kommunikation/schemas.py @@ -24,6 +24,7 @@ class ConversationCreate(BaseModel): participant_types: list[str] = Field(default_factory=lambda: ["user"]) initial_message: str | None = None is_direct: bool = False + metadata: dict[str, Any] = Field(default_factory=dict) class ConversationUpdate(BaseModel): diff --git a/app/plugins/builtins/kommunikation/services.py b/app/plugins/builtins/kommunikation/services.py index 7716784..c32fe0c 100644 --- a/app/plugins/builtins/kommunikation/services.py +++ b/app/plugins/builtins/kommunikation/services.py @@ -253,6 +253,7 @@ async def create_conversation( participant_ids: list[str] | None = None, is_direct: bool = False, initial_message: str | None = None, + metadata: dict[str, Any] | None = None, ) -> dict[str, Any]: """Create a new conversation.""" conv = CommConversation( @@ -262,7 +263,7 @@ async def create_conversation( is_direct=is_direct, created_by=user_id, created_by_type="user", - metadata_={}, + metadata_=metadata or {}, ) from app.core.hooks import do_action await do_action("comm.conversation.before_create", tenant_id=tenant_id, user_id=user_id) diff --git a/frontend/src/components/contacts/ContactDetail.tsx b/frontend/src/components/contacts/ContactDetail.tsx index 20db1c7..ceebef5 100644 --- a/frontend/src/components/contacts/ContactDetail.tsx +++ b/frontend/src/components/contacts/ContactDetail.tsx @@ -1,5 +1,5 @@ import { asError } from '@/utils/errorTypes'; -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; @@ -8,7 +8,7 @@ import { Modal } from '@/components/ui/Modal'; import { Input } from '@/components/ui/Input'; import { useToast } from '@/components/ui/Toast'; -import { Loader2, FileText, FolderOpen, Mail, Calendar, Link, Tag } from 'lucide-react'; +import { Loader2, FileText, FolderOpen, Mail, Calendar, Link, Tag, FolderInput } from 'lucide-react'; const ICON_MAP: Record> = { FileText, @@ -36,6 +36,8 @@ import { useUpdateContactPerson, useDeleteContactPerson, } from '@/api/hooks'; +import { useContactFolders, useMoveContactToFolder } from '@/api/contacts'; +import type { ContactFolder } from '@/api/contactFolders'; export interface ContactDetailProps { contact: UnifiedContact | null; @@ -178,6 +180,11 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId const createPersonMutation = useCreateContactPerson(); const updatePersonMutation = useUpdateContactPerson(); const deletePersonMutation = useDeleteContactPerson(); + const moveContactMut = useMoveContactToFolder(); + const { data: folderData } = useContactFolders(); + const folders: ContactFolder[] = folderData ?? []; + const [moveFolderOpen, setMoveFolderOpen] = useState(false); + const moveFolderRef = useRef(null); const [personModalOpen, setPersonModalOpen] = useState(false); const [editingPerson, setEditingPerson] = useState(null); const [activeTab, setActiveTab] = useState('details'); @@ -199,6 +206,35 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId } }, [aiActiveTab]); + // Close move folder dropdown on outside click + useEffect(() => { + if (!moveFolderOpen) return; + const handler = (e: MouseEvent) => { + if (moveFolderRef.current && !moveFolderRef.current.contains(e.target as Node)) { + setMoveFolderOpen(false); + } + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [moveFolderOpen]); + + const handleMoveToFolder = (folderId: string | null) => { + if (!contact) return; + moveContactMut.mutate( + { contactId: contact.id, folderId }, + { + onSuccess: () => { + toast.success(folderId ? t('contacts.movedToFolder') : t('contacts.removedFromFolder')); + setMoveFolderOpen(false); + }, + onError: (err: unknown) => { + const errObj = asError(err); + toast.error(errObj.message || t('common.error')); + }, + }, + ); + }; + // Sync modal from AI UI Control (Phase 4.7) useEffect(() => { if (aiActiveModal === 'edit' || aiActiveModal === 'create') { @@ -308,6 +344,42 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
+ {canAccess('contacts:write') && ( +
+ + {moveFolderOpen && ( +
+ + {folders.map((f) => ( + + ))} + {folders.length === 0 && ( +
{t('contacts.noFolders', 'Keine Ordner')}
+ )} +
+ )} +
+ )} {canAccess('contacts:write') && onEdit && ( )} diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 1cbc8bf..4f78e87 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -54,7 +54,6 @@ function getIcon(name: string): React.ReactNode { const singleItems: NavSingleItem[] = [ { to: '/dashboard', labelKey: 'nav.dashboard', icon:
{/* AI Chat Panel */} diff --git a/frontend/src/pages/Communication.tsx b/frontend/src/pages/Communication.tsx index f637d66..9e4d883 100644 --- a/frontend/src/pages/Communication.tsx +++ b/frontend/src/pages/Communication.tsx @@ -89,6 +89,10 @@ type ConversationCategory = 'system' | 'ai' | 'colleague'; // ─── Helpers ─── function categorizeConversation(conv: Conversation): ConversationCategory { + // Check metadata first (most reliable for AI/system chats) + const convType = conv.metadata?.conversation_type; + if (convType === 'system') return 'system'; + if (convType === 'ai') return 'ai'; // System messages: created by system participant type if (conv.participants.some(p => p.participant_type === 'system')) return 'system'; // AI conversations: have an AI participant @@ -136,12 +140,13 @@ async function sendMessage(convId: string, content: string, replyToId?: string): return r.data; } -async function createConversation(title: string | null, participantIds: string[], isDirect: boolean, initialMessage?: string): Promise { +async function createConversation(title: string | null, participantIds: string[], isDirect: boolean, initialMessage?: string, metadata?: Record): Promise { const r = await apiClient.post('/comm/conversations', { title, participant_ids: participantIds, is_direct: isDirect, initial_message: initialMessage, + metadata: metadata || {}, }); return r.data; } @@ -781,7 +786,11 @@ export function CommunicationPage() { try { const category = showNewChat; const isDirect = category === 'colleague' && participantIds.length === 1; - const conv = await createConversation(title || null, participantIds, isDirect); + const metadata: Record = + category === 'ai' ? { conversation_type: 'ai' } : + category === 'system' ? { conversation_type: 'system' } : + {}; + const conv = await createConversation(title || null, participantIds, isDirect, undefined, metadata); setConversations(prev => [...prev, conv]); setActiveConvId(conv.id); setMobileView('chat'); diff --git a/frontend/src/pages/Wiki.tsx b/frontend/src/pages/Wiki.tsx index b28c334..445155d 100644 --- a/frontend/src/pages/Wiki.tsx +++ b/frontend/src/pages/Wiki.tsx @@ -80,6 +80,7 @@ export function WikiPage() { const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); const [restoring, setRestoring] = useState(false); + const [browserRefreshKey, setBrowserRefreshKey] = useState(0); const loadCategories = useCallback(async () => { try { @@ -163,6 +164,7 @@ export function WikiPage() { toast.success(t('knowledge.editor.created')); } setEditorOpen(false); + setBrowserRefreshKey((k) => k + 1); } catch (err) { toast.error(err instanceof Error ? err.message : t('knowledge.editor.saveError')); } finally { @@ -179,6 +181,7 @@ export function WikiPage() { setArticle(null); setSelectedArticleId(null); setDeleteTarget(null); + setBrowserRefreshKey((k) => k + 1); } catch (err) { toast.error(err instanceof Error ? err.message : t('knowledge.editor.deleteError')); } finally { @@ -237,7 +240,7 @@ export function WikiPage() {
{/* Browser sidebar */}
- +
{/* Article detail / editor */}