fix(UI-Overhaul-Phase1): 7 Bugs behoben
Check Cross-Plugin Imports / check (push) Has been cancelled

1.1 Contacts refresh: useUnifiedContacts mutations already invalidate (verified)
1.2 Contacts drag-drop: ContactList items already draggable + ContactFolderTree onDrop (verified)
1.3 Contacts move dialog: Added move-to-folder dropdown in ContactDetail
1.4 Wiki save refresh: Added refreshKey prop to WikiBrowser, triggers reload after save/delete
1.5 Calendar dialog close: Window.tsx now injects windowId into componentProps
1.6 Communication AI chat: Added metadata support to ConversationCreate schema + service,
    frontend passes conversation_type metadata for AI/system chats,
    categorization checks metadata first
1.7 Wiki duplicate menu: Removed hardcoded /wiki from Sidebar.tsx (plugin provides it dynamically)

Backend: kommunikation schema/routes/services updated for metadata support
Frontend: tsc clean, build successful
This commit is contained in:
Agent Zero
2026-08-21 13:17:35 +02:00
parent 5d708c0905
commit 94c7c8fff5
10 changed files with 445 additions and 10 deletions
@@ -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<string, React.ComponentType<{ className?: string }>> = {
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<HTMLDivElement>(null);
const [personModalOpen, setPersonModalOpen] = useState(false);
const [editingPerson, setEditingPerson] = useState<ContactPerson | null>(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
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
{canAccess('contacts:write') && (
<div ref={moveFolderRef} className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => setMoveFolderOpen(!moveFolderOpen)}
icon={<FolderInput className="h-4 w-4" aria-hidden="true" />}
>
{t('contacts.moveFolder', 'Verschieben')}
</Button>
{moveFolderOpen && (
<div className="absolute right-0 top-full mt-1 bg-white border border-secondary-200 rounded-lg shadow-lg z-20 min-w-[200px] max-h-[300px] overflow-y-auto">
<button
className="w-full text-left px-3 py-2 text-sm hover:bg-secondary-50 border-b border-secondary-100"
onClick={() => handleMoveToFolder(null)}
>
{t('contacts.noFolder', 'Kein Ordner')}
</button>
{folders.map((f) => (
<button
key={f.id}
className="w-full text-left px-3 py-2 text-sm hover:bg-secondary-50 border-b border-secondary-100 last:border-b-0"
style={{ paddingLeft: `${(f.parent_id ? 24 : 12)}px` }}
onClick={() => handleMoveToFolder(f.id)}
>
<FolderOpen className="h-3.5 w-3.5 inline mr-1.5 text-primary-500" />
{f.name}
</button>
))}
{folders.length === 0 && (
<div className="px-3 py-2 text-xs text-secondary-400">{t('contacts.noFolders', 'Keine Ordner')}</div>
)}
</div>
)}
</div>
)}
{canAccess('contacts:write') && onEdit && (
<Button variant="ghost" size="sm" onClick={onEdit}>{t('common.edit')}</Button>
)}