Files
SIA/web/src/components/Chat/ChatWindow.tsx

172 lines
5.5 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import { MessageCircle, X, GripHorizontal } from 'lucide-react';
import ChatMessage from './ChatMessage';
import ChatInput from './ChatInput';
import useScreenSize from '../../hooks/useScreenSize';
import { useWebSocketContext } from '../../contexts/WebSocketContext';
import { api } from '../../services/api';
interface ChatWindowProps {
className?: string;
}
const ChatWindow: React.FC<ChatWindowProps> = ({ className = '' }) => {
const screenSize = useScreenSize();
const [chatOpen, setChatOpen] = useState(false);
const [chatHeight, setChatHeight] = useState(400);
const [chatWidth, setChatWidth] = useState(360);
const [isDragging, setIsDragging] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const startHeightRef = useRef<number>(0);
const startWidthRef = useRef<number>(0);
const startPosRef = useRef<{ x: number, y: number }>({ x: 0, y: 0 });
const { messages = [], isConnected } = useWebSocketContext();
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleResizeStart = (e: React.MouseEvent) => {
e.preventDefault();
setIsDragging(true);
startHeightRef.current = chatHeight;
startWidthRef.current = chatWidth;
startPosRef.current = { x: e.clientX, y: e.clientY };
};
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging) return;
const deltaY = startPosRef.current.y - e.clientY;
const deltaX = startPosRef.current.x - e.clientX;
const newHeight = Math.max(300, startHeightRef.current + deltaY);
const newWidth = Math.max(300, startWidthRef.current + deltaX);
setChatHeight(newHeight);
setChatWidth(newWidth);
};
const handleMouseUp = () => {
setIsDragging(false);
};
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging]);
const handleSendMessage = async (content: string) => {
try {
setIsLoading(true);
await api.addChatMessage({ message: content });
} catch (error) {
console.error('Failed to send message:', error);
} finally {
setIsLoading(false);
}
};
const handleDeleteMessage = async (id: string) => {
try {
await api.deleteChatMessage(id);
} catch (error) {
console.error('Failed to delete message:', error);
}
};
if (screenSize === 'mobile') {
return (
<div className={`h-full flex flex-col bg-white ${className}`}>
<div className="px-4 py-2 border-b bg-gray-50 flex justify-between items-center">
<h3 className="font-semibold text-sm">Chat</h3>
</div>
<div className="flex-1 overflow-y-auto p-4">
{messages.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-400">
<p>No messages yet</p>
</div>
) : (
messages.map((message) => (
<ChatMessage
key={message.id}
{...message}
onDelete={handleDeleteMessage}
/>
))
)}
<div ref={messagesEndRef} />
</div>
<ChatInput onSendMessage={handleSendMessage} disabled={isLoading || !isConnected} />
</div>
);
}
return (
<>
{!chatOpen && (
<button
onClick={() => setChatOpen(true)}
className="fixed bottom-4 right-4 w-14 h-14 bg-blue-500 text-white rounded-full shadow-lg hover:bg-blue-600 flex items-center justify-center z-40"
aria-label="Open chat"
>
<MessageCircle className="w-6 h-6" />
</button>
)}
{chatOpen && (
<div
className="fixed bottom-0 right-4 bg-white shadow-2xl rounded-t-lg flex flex-col z-40"
style={{ height: `${chatHeight}px`, width: `${chatWidth}px` }}
>
<div
className="flex items-center justify-between p-3 border-b bg-gray-50 rounded-t-lg cursor-move"
onMouseDown={handleResizeStart}
>
<div className="flex items-center gap-2">
<GripHorizontal className="w-4 h-4 text-gray-400" />
<h3 className="font-semibold text-sm">Chat</h3>
</div>
<button
onClick={() => setChatOpen(false)}
className="text-gray-500 hover:text-gray-700 p-1"
aria-label="Close chat"
>
<X className="w-4 h-4" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-4">
{messages.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-400">
<p>No messages yet</p>
</div>
) : (
messages.map((message) => (
<ChatMessage
key={message.id}
{...message}
onDelete={handleDeleteMessage}
/>
))
)}
<div ref={messagesEndRef} />
</div>
<ChatInput onSendMessage={handleSendMessage} disabled={isLoading || !isConnected} />
</div>
)}
</>
);
};
export default ChatWindow;