feat: window management system with minimize, fullscreen, and AI chat split

- Window manager store (Zustand) for managing open/minimized/fullscreen windows
- Window component with header controls: KI-Chat toggle, fullscreen, minimize, close
- AI chat panel with streaming chat via existing /ai/sessions API
- WindowContainer renders all non-minimized windows
- TopBar shows minimized windows as clickable pills
- ContactEditForm extracted from ContactEditModal for window rendering
- ContactsList and ContactDetailPage use openWindow() instead of modal state
- Draggable windows with z-index management
This commit is contained in:
Agent Zero
2026-07-24 23:42:53 +02:00
parent c1d49e4dfe
commit 35e87b5d51
9 changed files with 936 additions and 39 deletions
+13 -16
View File
@@ -6,7 +6,8 @@ import React from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { ContactDetail } from '@/components/contacts/ContactDetail';
import { ContactEditModal } from '@/components/contacts/ContactEditModal';
import { ContactEditForm } from '@/components/contacts/ContactEditForm';
import { useWindowStore } from '@/store/windowStore';
import { useUnifiedContact, type UnifiedContact } from '@/api/hooks';
import { Button } from '@/components/ui/Button';
import { ChevronLeft } from 'lucide-react';
@@ -16,13 +17,20 @@ export function ContactDetailPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { data: contact, isLoading } = useUnifiedContact(id);
const [editModalOpen, setEditModalOpen] = React.useState(false);
const [editingContact, setEditingContact] = React.useState<UnifiedContact | null>(null);
const openWindow = useWindowStore((s) => s.openWindow);
const handleEdit = () => {
if (contact) {
setEditingContact(contact);
setEditModalOpen(true);
openWindow({
title: t('contacts.edit'),
type: 'contact-edit',
component: ContactEditForm,
componentProps: {
contact,
onClose: () => {},
onSaved: () => {},
},
});
}
};
@@ -30,11 +38,6 @@ export function ContactDetailPage() {
navigate('/contacts');
};
const handleSaved = () => {
setEditModalOpen(false);
setEditingContact(null);
};
return (
<div className="flex flex-col h-full" data-testid="contact-detail-page">
<div className="flex items-center gap-2 px-4 py-2 border-b border-secondary-200 bg-white">
@@ -55,12 +58,6 @@ export function ContactDetailPage() {
onDeleted={handleDeleted}
/>
</div>
<ContactEditModal
open={editModalOpen}
onClose={() => setEditModalOpen(false)}
contact={editingContact}
onSaved={handleSaved}
/>
</div>
);
}
+31 -22
View File
@@ -15,7 +15,8 @@ import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
import { ContactFolderTree, type ContactFilter } from '@/components/contacts/ContactFolderTree';
import { ContactList, type ContactViewMode } from '@/components/contacts/ContactList';
import { ContactDetail } from '@/components/contacts/ContactDetail';
import { ContactEditModal } from '@/components/contacts/ContactEditModal';
import { ContactEditForm } from '@/components/contacts/ContactEditForm';
import { useWindowStore } from '@/store/windowStore';
import { SavedFilters } from '@/components/SavedFilters';
import { ArrowDownAZ, ArrowUpZA, ChevronLeft, LayoutGrid, List, Plus } from 'lucide-react';
import {
@@ -39,8 +40,7 @@ export function ContactsListPage() {
const [viewMode, setViewMode] = useState<ContactViewMode>('list');
const [selectedContactId, setSelectedContactId] = useState<string | null>(null);
const [activeView, setActiveView] = useState<'folders' | 'list' | 'detail'>('folders');
const [editModalOpen, setEditModalOpen] = useState(false);
const [editingContact, setEditingContact] = useState<UnifiedContact | null>(null);
const openWindow = useWindowStore((s) => s.openWindow);
// Debounce search
useEffect(() => {
@@ -134,19 +134,40 @@ export function ContactsListPage() {
setPage(1);
}, []);
// Handle edit modal saved
const handleSaved = useCallback((id: string) => {
setSelectedContactId(id);
setActiveView('detail');
}, []);
// Handle create
const handleCreate = useCallback(() => {
setEditingContact(null);
setEditModalOpen(true);
}, []);
openWindow({
title: t('contacts.create'),
type: 'contact-create',
component: ContactEditForm,
componentProps: {
onClose: () => {},
onSaved: handleSaved,
},
});
}, [openWindow, t, handleSaved]);
// Handle edit
const handleEdit = useCallback(() => {
if (selectedContact) {
setEditingContact(selectedContact);
setEditModalOpen(true);
openWindow({
title: t('contacts.edit'),
type: 'contact-edit',
component: ContactEditForm,
componentProps: {
contact: selectedContact,
onClose: () => {},
onSaved: handleSaved,
},
});
}
}, [selectedContact]);
}, [selectedContact, openWindow, t, handleSaved]);
// Handle delete (from detail)
const handleDeleted = useCallback(() => {
@@ -154,12 +175,6 @@ export function ContactsListPage() {
setActiveView('list');
}, []);
// Handle edit modal saved
const handleSaved = useCallback((id: string) => {
setSelectedContactId(id);
setActiveView('detail');
}, []);
// Register toolbar items
const registerItems = usePluginToolbarStore((s) => s.registerItems);
const unregisterPlugin = usePluginToolbarStore((s) => s.unregisterPlugin);
@@ -458,13 +473,7 @@ export function ContactsListPage() {
)}
</div>
{/* Edit/Create Modal */}
<ContactEditModal
open={editModalOpen}
onClose={() => setEditModalOpen(false)}
contact={editingContact}
onSaved={handleSaved}
/>
</div>
);
}