veza/apps/web/src/features/chat/components/ChatRoom.tsx

122 lines
4.1 KiB
TypeScript

import React, { useEffect, useRef, useState } from 'react';
import { useChatStore } from '../store/chatStore';
import { ChatMessageComponent } from './ChatMessage';
import { useChat } from '../hooks/useChat';
import { MessageSearch } from './MessageSearch';
import { TypingIndicator } from './TypingIndicator';
import { Search, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
// FE-PAGE-005: Complete Chat page implementation
interface ChatRoomProps {
conversationId: string;
}
export const ChatRoom: React.FC<ChatRoomProps> = ({ conversationId }) => {
const { messages } = useChatStore();
const { fetchHistory } = useChat();
const messagesEndRef = useRef<HTMLDivElement>(null);
const [showSearch, setShowSearch] = useState(false);
const [highlightedMessageId, setHighlightedMessageId] = useState<string | null>(null);
const currentMessages = messages[conversationId] || [];
// FE-BUG-002: Use a ref to track if we've already tried fetching to avoid infinite loops on failure
const fetchingRef = useRef<{ [key: string]: boolean }>({});
useEffect(() => {
if (conversationId && !messages[conversationId] && !fetchingRef.current[conversationId]) {
fetchingRef.current[conversationId] = true;
fetchHistory(conversationId).finally(() => {
// We keep it true to avoid re-fetching the same ID in this session
// if it returned nothing, or we could reset it if we want to allow retry.
// For now, let's just make sure it doesn't loop.
});
}
}, [conversationId, messages[conversationId], fetchHistory]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [currentMessages]);
const handleMessageSelect = (messageId: string) => {
setHighlightedMessageId(messageId);
// Scroll to message
const messageElement = document.getElementById(`message-${messageId}`);
if (messageElement) {
messageElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Remove highlight after 3 seconds
setTimeout(() => setHighlightedMessageId(null), 3000);
}
};
if (!conversationId) {
return (
<div className="flex-1 flex items-center justify-center text-gray-500">
Sélectionnez une conversation pour commencer
</div>
);
}
return (
<div className="flex-1 flex flex-col h-full bg-white">
{/* FE-PAGE-005: Message Search Bar */}
<div className="border-b p-2 bg-gray-50">
{showSearch ? (
<div className="flex items-center gap-2">
<div className="flex-1">
<MessageSearch
conversationId={conversationId}
onMessageSelect={handleMessageSelect}
/>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowSearch(false)}
>
<X className="h-4 w-4" />
</Button>
</div>
) : (
<div className="flex justify-end">
<Button
variant="ghost"
size="sm"
onClick={() => setShowSearch(true)}
>
<Search className="h-4 w-4 mr-2" />
Search Messages
</Button>
</div>
)}
</div>
<div className="flex-1 overflow-y-auto p-4">
{currentMessages.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-500">
Aucun message. Soyez le premier à envoyer un message !
</div>
) : (
currentMessages.map((msg) => (
<div
key={msg.id}
id={`message-${msg.id}`}
className={
highlightedMessageId === msg.id
? 'bg-yellow-100 rounded-lg p-2 -m-2 mb-2'
: ''
}
>
<ChatMessageComponent message={msg} />
</div>
))
)}
{/* FE-PAGE-005: Typing Indicator */}
<TypingIndicator conversationId={conversationId} />
<div ref={messagesEndRef} />
</div>
</div>
);
};