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:
@@ -0,0 +1,131 @@
|
||||
import { create } from 'zustand';
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
export interface WindowState {
|
||||
id: string;
|
||||
title: string;
|
||||
type: string; // 'contact-create', 'contact-edit', etc.
|
||||
component: ComponentType<any>;
|
||||
componentProps: Record<string, any>;
|
||||
state: 'open' | 'minimized' | 'fullscreen';
|
||||
aiChatVisible: boolean;
|
||||
zIndex: number;
|
||||
position: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
}
|
||||
|
||||
export interface WindowManagerStore {
|
||||
windows: WindowState[];
|
||||
activeWindowId: string | null;
|
||||
nextZIndex: number;
|
||||
|
||||
openWindow: (config: Omit<WindowState, 'id' | 'state' | 'aiChatVisible' | 'zIndex' | 'position' | 'size'>) => string;
|
||||
closeWindow: (id: string) => void;
|
||||
minimizeWindow: (id: string) => void;
|
||||
restoreWindow: (id: string) => void;
|
||||
toggleFullscreen: (id: string) => void;
|
||||
toggleAiChat: (id: string) => void;
|
||||
setActiveWindow: (id: string) => void;
|
||||
updateWindowPosition: (id: string, pos: { x: number; y: number }) => void;
|
||||
updateWindowSize: (id: string, size: { width: number; height: number }) => void;
|
||||
}
|
||||
|
||||
let windowIdCounter = 0;
|
||||
|
||||
const DEFAULT_SIZE = { width: 800, height: 600 };
|
||||
const DEFAULT_POSITION = { x: 120, y: 80 };
|
||||
|
||||
export const useWindowStore = create<WindowManagerStore>((set, get) => ({
|
||||
windows: [],
|
||||
activeWindowId: null,
|
||||
nextZIndex: 100,
|
||||
|
||||
openWindow: (config) => {
|
||||
const id = `win-${++windowIdCounter}`;
|
||||
const z = get().nextZIndex;
|
||||
const offset = get().windows.length * 24;
|
||||
const newWindow: WindowState = {
|
||||
...config,
|
||||
id,
|
||||
state: 'open',
|
||||
aiChatVisible: false,
|
||||
zIndex: z,
|
||||
position: { x: DEFAULT_POSITION.x + offset, y: DEFAULT_POSITION.y + offset },
|
||||
size: { ...DEFAULT_SIZE },
|
||||
};
|
||||
set((s) => ({
|
||||
windows: [...s.windows, newWindow],
|
||||
activeWindowId: id,
|
||||
nextZIndex: z + 1,
|
||||
}));
|
||||
return id;
|
||||
},
|
||||
|
||||
closeWindow: (id) =>
|
||||
set((s) => ({
|
||||
windows: s.windows.filter((w) => w.id !== id),
|
||||
activeWindowId: s.activeWindowId === id ? null : s.activeWindowId,
|
||||
})),
|
||||
|
||||
minimizeWindow: (id) =>
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id ? { ...w, state: 'minimized' as const } : w
|
||||
),
|
||||
activeWindowId: s.activeWindowId === id ? null : s.activeWindowId,
|
||||
})),
|
||||
|
||||
restoreWindow: (id) => {
|
||||
const z = get().nextZIndex;
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id
|
||||
? { ...w, state: 'open' as const, zIndex: z }
|
||||
: w
|
||||
),
|
||||
activeWindowId: id,
|
||||
nextZIndex: z + 1,
|
||||
}));
|
||||
},
|
||||
|
||||
toggleFullscreen: (id) =>
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id
|
||||
? { ...w, state: w.state === 'fullscreen' ? 'open' as const : 'fullscreen' as const }
|
||||
: w
|
||||
),
|
||||
})),
|
||||
|
||||
toggleAiChat: (id) =>
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id ? { ...w, aiChatVisible: !w.aiChatVisible } : w
|
||||
),
|
||||
})),
|
||||
|
||||
setActiveWindow: (id) => {
|
||||
const z = get().nextZIndex;
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id ? { ...w, zIndex: z } : w
|
||||
),
|
||||
activeWindowId: id,
|
||||
nextZIndex: z + 1,
|
||||
}));
|
||||
},
|
||||
|
||||
updateWindowPosition: (id, pos) =>
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id ? { ...w, position: pos } : w
|
||||
),
|
||||
})),
|
||||
|
||||
updateWindowSize: (id, size) =>
|
||||
set((s) => ({
|
||||
windows: s.windows.map((w) =>
|
||||
w.id === id ? { ...w, size } : w
|
||||
),
|
||||
})),
|
||||
}));
|
||||
Reference in New Issue
Block a user