"use client" import { useState, useEffect, useRef } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Textarea } from "@/components/ui/textarea" import { ScrollArea } from "@/components/ui/scroll-area" import { Search, Send, Paperclip, MessageSquareOff, Settings } from "lucide-react" import { chatApi, Conversation, Message } from "@/lib/api" import { appwriteClient, APPWRITE_CONFIG } from "@/lib/appwrite" import { toast } from "sonner" import { formatDistanceToNow } from "date-fns" import Link from "next/link" // Check if Appwrite is properly configured const isAppwriteConfigured = () => { const projectId = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID const databaseId = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID return !!(projectId && projectId.trim() !== '' && databaseId && databaseId.trim() !== '') } export default function AdminMessagesPage() { const [searchTerm, setSearchTerm] = useState("") const [conversations, setConversations] = useState([]) const [selectedConversation, setSelectedConversation] = useState(null) const [messages, setMessages] = useState([]) const [messageText, setMessageText] = useState("") const [loading, setLoading] = useState(true) const [serviceConfigured, setServiceConfigured] = useState(true) const [error, setError] = useState(null) const [stats, setStats] = useState({ repliedToday: 0, avgResponseTime: '-' }) const processedMessageIds = useRef(new Set()) // Check configuration on mount useEffect(() => { setServiceConfigured(isAppwriteConfigured()) if (!isAppwriteConfigured()) { setLoading(false) } }, []) // Fetch Conversations const fetchConversations = async () => { if (!serviceConfigured) { setLoading(false) return } try { const data = await chatApi.listConversations() // Guard against null/undefined response const safeData = data || [] setConversations(safeData) // Calculate stats const today = new Date() today.setHours(0, 0, 0, 0) const repliedToday = safeData.filter(c => { const lastMsg = new Date(c.lastMessageAt) return lastMsg >= today }).length setStats({ repliedToday, avgResponseTime: safeData.length > 0 ? '~2h' : '-' }) if (safeData.length > 0 && !selectedConversation) { setSelectedConversation(safeData[0]) } setLoading(false) setError(null) } catch (error: any) { console.error("Failed to load conversations", error) // Check if it's a configuration error if (error?.message?.includes('Project ID') || error?.code === 400) { setServiceConfigured(false) } else { setError("Falha ao carregar conversas. Tente novamente.") } setLoading(false) } } useEffect(() => { if (serviceConfigured) { fetchConversations() } }, [serviceConfigured]) // Fetch Messages when conversation changes useEffect(() => { if (!selectedConversation || !serviceConfigured) return setMessages([]) processedMessageIds.current.clear() const loadMessages = async () => { try { const data = await chatApi.listMessages(selectedConversation.id) setMessages(data) data.forEach(m => processedMessageIds.current.add(m.id)) } catch (error) { console.error("Failed to load messages", error) toast.error("Failed to load messages") } } loadMessages() // Appwrite Realtime Subscription // Only subscribe if config is present if (APPWRITE_CONFIG.databaseId && APPWRITE_CONFIG.collectionId && serviceConfigured) { try { const channel = `databases.${APPWRITE_CONFIG.databaseId}.collections.${APPWRITE_CONFIG.collectionId}.documents` const unsubscribe = appwriteClient.subscribe(channel, (response) => { if (response.events.includes("databases.*.collections.*.documents.*.create")) { const payload = response.payload as any // Check if belongs to current conversation if (payload.conversation_id === selectedConversation.id) { const msgId = payload.$id if (processedMessageIds.current.has(msgId)) return const newMessage: Message = { id: msgId, conversationId: payload.conversation_id, senderId: payload.sender_id, content: payload.content, createdAt: payload.timestamp, isMine: false } setMessages(prev => [...prev, newMessage]) processedMessageIds.current.add(msgId) } } }) return () => unsubscribe() } catch (error) { console.error("Failed to subscribe to realtime", error) } } }, [selectedConversation, serviceConfigured]) const filteredConversations = (conversations || []).filter((conv) => (conv.participantName || "Unknown").toLowerCase().includes(searchTerm.toLowerCase()), ) const handleSendMessage = async () => { if (!messageText.trim() || !selectedConversation) return const content = messageText setMessageText("") // Optimistic clear try { const newMsg = await chatApi.sendMessage(selectedConversation.id, content) // Update messages list setMessages(prev => [...prev, newMsg]) processedMessageIds.current.add(newMsg.id) // Update conversation last message setConversations(prev => prev.map(c => c.id === selectedConversation.id ? { ...c, lastMessage: content, lastMessageAt: new Date().toISOString() } : c )) } catch (error) { console.error("Failed to send message", error) toast.error("Failed to send message") setMessageText(content) // Restore on failure } } // --- Render Helpers --- const formatTime = (isoString?: string) => { if (!isoString) return "" try { return formatDistanceToNow(new Date(isoString), { addSuffix: true }) } catch { return "" } } // Service not configured - show friendly message if (!serviceConfigured) { return (
{/* Header */}

Messages

Communicate with candidates and companies

{/* Not Configured Card */}

Serviço de Mensagens não Configurado

O serviço de mensagens em tempo real (Appwrite) ainda não foi configurado para este ambiente. Entre em contato com o administrador do sistema para ativar esta funcionalidade.

{/* Technical Details (collapsed by default) */}
Detalhes técnicos

Variáveis necessárias:

  • NEXT_PUBLIC_APPWRITE_ENDPOINT
  • NEXT_PUBLIC_APPWRITE_PROJECT_ID
  • NEXT_PUBLIC_APPWRITE_DATABASE_ID
  • NEXT_PUBLIC_APPWRITE_COLLECTION_ID
) } return (
{/* Header */}

Messages

Communicate with candidates and companies

{/* Stats */}
Total conversations {conversations.length} Unread {conversations.reduce((acc, conv) => acc + (conv.unreadCount || 0), 0)} Replied today {stats.repliedToday} Average response time {stats.avgResponseTime}
{/* Messages Interface */}
{/* Conversations List */}
setSearchTerm(e.target.value)} className="pl-10" />
{loading ?

Loading...

: error ?

{error}

: filteredConversations.length === 0 ?

No conversations found

: filteredConversations.map((conversation) => ( ))}
{/* Chat Area */}
{selectedConversation ? ( <> {/* Chat Header */}
{selectedConversation.participantName} Connected
{/* Messages */}
{messages.length === 0 ? (

Start the conversation

) : ( messages.map((message) => (

{message.content}

{formatTime(message.createdAt)}
)) )}
{/* Message Input */}