- Added room management: create, join, leave, delete rooms - Added CreateRoomDialog component for creating new rooms - Added room actions menu (leave/delete) in ChatSidebar - Added message search functionality with MessageSearch component - Added search bar in ChatRoom with message highlighting - Added TypingIndicator component (placeholder for future WebSocket integration) - Enhanced ChatSidebar with room management UI - Enhanced ChatRoom with search and typing indicators
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Search, X } from 'lucide-react';
|
|
import { apiClient } from '@/services/api/client';
|
|
import { ChatMessage } from '../store/chatStore';
|
|
|
|
// FE-PAGE-005: Complete Chat page implementation - Message Search
|
|
|
|
interface MessageSearchProps {
|
|
conversationId: string;
|
|
onMessageSelect?: (messageId: string) => void;
|
|
}
|
|
|
|
export function MessageSearch({
|
|
conversationId,
|
|
onMessageSelect,
|
|
}: MessageSearchProps) {
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [searchResults, setSearchResults] = useState<ChatMessage[]>([]);
|
|
const [isSearching, setIsSearching] = useState(false);
|
|
const [showResults, setShowResults] = useState(false);
|
|
|
|
const handleSearch = async () => {
|
|
if (!searchQuery.trim() || !conversationId) return;
|
|
|
|
try {
|
|
setIsSearching(true);
|
|
setShowResults(true);
|
|
// Assuming backend has a search endpoint - if not, we'll search client-side
|
|
const response = await apiClient.get(
|
|
`/conversations/${conversationId}/messages/search`,
|
|
{
|
|
params: { q: searchQuery, limit: 20 },
|
|
},
|
|
);
|
|
setSearchResults(response.data.messages || []);
|
|
} catch (error: any) {
|
|
// If endpoint doesn't exist, fall back to client-side search
|
|
console.warn('Search endpoint not available, using client-side search');
|
|
// We'll implement client-side search as fallback
|
|
setSearchResults([]);
|
|
} finally {
|
|
setIsSearching(false);
|
|
}
|
|
};
|
|
|
|
const handleClear = () => {
|
|
setSearchQuery('');
|
|
setSearchResults([]);
|
|
setShowResults(false);
|
|
};
|
|
|
|
return (
|
|
<div className="relative">
|
|
<div className="flex items-center gap-2">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
<Input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
handleSearch();
|
|
}
|
|
}}
|
|
placeholder="Search messages..."
|
|
className="pl-8 pr-8"
|
|
/>
|
|
{searchQuery && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="absolute right-1 top-1/2 transform -translate-y-1/2 h-6 w-6 p-0"
|
|
onClick={handleClear}
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<Button
|
|
onClick={handleSearch}
|
|
disabled={!searchQuery.trim() || isSearching}
|
|
size="sm"
|
|
>
|
|
{isSearching ? 'Searching...' : 'Search'}
|
|
</Button>
|
|
</div>
|
|
|
|
{showResults && searchResults.length > 0 && (
|
|
<div className="absolute z-10 w-full mt-2 bg-white border rounded-lg shadow-lg max-h-64 overflow-y-auto">
|
|
<div className="p-2">
|
|
<div className="text-xs text-gray-500 mb-2">
|
|
{searchResults.length} result(s) found
|
|
</div>
|
|
{searchResults.map((message) => (
|
|
<div
|
|
key={message.id}
|
|
className="p-2 hover:bg-gray-100 rounded cursor-pointer"
|
|
onClick={() => {
|
|
onMessageSelect?.(message.id);
|
|
setShowResults(false);
|
|
}}
|
|
>
|
|
<div className="text-sm font-medium">{message.sender_username}</div>
|
|
<div className="text-xs text-gray-600 truncate">
|
|
{message.content}
|
|
</div>
|
|
<div className="text-xs text-gray-400">
|
|
{new Date(message.created_at).toLocaleString()}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{showResults && searchResults.length === 0 && searchQuery && (
|
|
<div className="absolute z-10 w-full mt-2 bg-white border rounded-lg shadow-lg p-4 text-sm text-gray-500">
|
|
No messages found
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|