Files
leocrm/frontend/src/components/window/Window.tsx
T

172 lines
5.4 KiB
TypeScript

import React, { useRef, useState, useCallback } from 'react';
import clsx from 'clsx';
import { Sparkles, Maximize2, Minimize2, Minus, X } from 'lucide-react';
import { useWindowStore, type WindowState } from '@/store/windowStore';
import { AiChatPanel } from './AiChatPanel';
import { useTranslation } from 'react-i18next';
interface WindowProps {
window: WindowState;
}
export function Window({ window: win }: WindowProps) {
const { t } = useTranslation();
const {
closeWindow,
minimizeWindow,
toggleFullscreen,
toggleAiChat,
setActiveWindow,
updateWindowPosition,
} = useWindowStore();
const headerRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const dragStart = useRef({ x: 0, y: 0, posX: 0, posY: 0 });
const ContentComponent = win.component;
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
if (win.state === 'fullscreen') return;
// Only drag from header, not from buttons
if ((e.target as HTMLElement).closest('button')) return;
setIsDragging(true);
dragStart.current = {
x: e.clientX,
y: e.clientY,
posX: win.position.x,
posY: win.position.y,
};
setActiveWindow(win.id);
},
[win.id, win.state, win.position, setActiveWindow]
);
React.useEffect(() => {
if (!isDragging) return;
const handleMouseMove = (e: MouseEvent) => {
const dx = e.clientX - dragStart.current.x;
const dy = e.clientY - dragStart.current.y;
const newX = dragStart.current.posX + dx;
const newY = Math.max(0, dragStart.current.posY + dy);
updateWindowPosition(win.id, { x: newX, y: newY });
};
const handleMouseUp = () => setIsDragging(false);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, win.id, updateWindowPosition]);
if (win.state === 'minimized') return null;
const isFullscreen = win.state === 'fullscreen';
const containerClass = clsx(
'fixed flex flex-col bg-white shadow-2xl rounded-lg overflow-hidden',
isFullscreen
? 'inset-2 rounded-lg'
: 'rounded-lg',
isDragging && 'cursor-grabbing'
);
const containerStyle: React.CSSProperties = isFullscreen
? { zIndex: win.zIndex }
: {
left: win.position.x,
top: win.position.y,
width: win.size.width,
height: win.size.height,
zIndex: win.zIndex,
};
return (
<div
className={containerClass}
style={containerStyle}
onMouseDown={() => setActiveWindow(win.id)}
role="dialog"
aria-label={win.title}
>
{/* Header */}
<div
ref={headerRef}
onMouseDown={handleMouseDown}
className="bg-secondary-50 border-b border-secondary-200 px-4 py-2 flex items-center justify-between cursor-grab select-none flex-shrink-0"
>
<span className="text-sm font-semibold text-secondary-700 truncate">
{win.title}
</span>
<div className="flex items-center gap-1">
{/* AI Chat Toggle */}
<button
onClick={() => toggleAiChat(win.id)}
className={clsx(
'p-1.5 rounded hover:bg-secondary-200',
win.aiChatVisible && 'bg-primary-100 text-primary-600'
)}
aria-label={t('window.kichateinaus')}
title={t('window.kichat')}
>
<Sparkles className="w-4 h-4" />
</button>
{/* Fullscreen Toggle */}
<button
onClick={() => toggleFullscreen(win.id)}
className="p-1.5 rounded hover:bg-secondary-200"
aria-label={isFullscreen ? 'Vollbild verlassen' : 'Vollbild'}
title={isFullscreen ? 'Vollbild verlassen' : 'Vollbild'}
>
{isFullscreen ? (
<Minimize2 className="w-4 h-4" />
) : (
<Maximize2 className="w-4 h-4" />
)}
</button>
{/* Minimize */}
<button
onClick={() => minimizeWindow(win.id)}
className="p-1.5 rounded hover:bg-secondary-200"
aria-label={t('window.minimieren')}
title={t('window.minimieren')}
>
<Minus className="w-4 h-4" />
</button>
{/* Close */}
<button
onClick={() => closeWindow(win.id)}
className="p-1.5 rounded hover:bg-danger-100 hover:text-danger-600"
aria-label={t('window.schließen')}
title={t('window.schließen')}
>
<X className="w-4 h-4" />
</button>
</div>
</div>
{/* Content Area */}
<div className="flex-1 flex overflow-hidden">
{/* Main Content */}
<div
className={clsx(
'flex-1 overflow-y-auto',
win.aiChatVisible && 'border-r border-secondary-200'
)}
>
<ContentComponent {...win.componentProps} windowId={win.id} />
</div>
{/* AI Chat Panel */}
{win.aiChatVisible && (
<div className="w-1/2 flex flex-col">
<AiChatPanel windowTitle={win.title} windowType={win.type} />
</div>
)}
</div>
</div>
);
}