Spaces:
Runtime error
Runtime error
| import React, { useState } from 'react'; | |
| import { useQuery } from '@tanstack/react-query'; | |
| import api from '../api/axiosConfig'; | |
| import { MapPin, MessageSquare, Users, ChevronRight } from 'lucide-react'; | |
| const AnonymousUsersGeo = ({ websiteId }) => { | |
| const [selectedUser, setSelectedUser] = useState(null); | |
| const { data: users, isLoading, error } = useQuery({ | |
| queryKey: ['anonymous-users-geo', websiteId], | |
| queryFn: async () => { | |
| const response = await api.get(`/dashboard/anonymous-users-geo/${websiteId}`); | |
| return response.data.anonymous_users || []; | |
| }, | |
| refetchInterval: 30000, | |
| retry: 1, | |
| }); | |
| const { data: chatHistory } = useQuery({ | |
| queryKey: ['user-chat-geo', websiteId, selectedUser?.ip], | |
| queryFn: async () => { | |
| if (!selectedUser) return null; | |
| try { | |
| const response = await api.get(`/dashboard/user-chat-geo/${websiteId}/${selectedUser.ip}`); | |
| return response.data; | |
| } catch (error) { | |
| console.error('Failed to fetch chat history:', error); | |
| return null; | |
| } | |
| }, | |
| enabled: !!selectedUser, | |
| }); | |
| if (isLoading) { | |
| return ( | |
| <div className="bg-white rounded-2xl shadow-sm border border-secondary-100 p-6"> | |
| <div className="animate-pulse space-y-4"> | |
| {[...Array(3)].map((_, i) => ( | |
| <div key={i} className="h-20 bg-secondary-100 rounded-xl" /> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (error) { | |
| return ( | |
| <div className="bg-white rounded-2xl shadow-sm border border-secondary-100 p-6"> | |
| <p className="text-red-600">Failed to load anonymous users</p> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| {/* Users List */} | |
| <div className="lg:col-span-1 bg-white rounded-2xl shadow-sm border border-secondary-100 overflow-hidden flex flex-col"> | |
| <div className="p-4 md:p-6 border-b border-secondary-100"> | |
| <h3 className="text-lg font-bold text-secondary-900 flex items-center gap-2"> | |
| <Users className="w-5 h-5 text-primary-600" /> | |
| Anonymous Users | |
| </h3> | |
| <p className="text-sm text-secondary-500 mt-1">{users?.length || 0} unique locations</p> | |
| </div> | |
| <div className="divide-y divide-secondary-100 flex-1 overflow-y-auto"> | |
| {users && users.length > 0 ? ( | |
| users.map((user) => ( | |
| <button | |
| key={user.ip} | |
| onClick={() => setSelectedUser(user)} | |
| className={`w-full p-4 text-left transition-colors hover:bg-secondary-50 ${ | |
| selectedUser?.ip === user.ip ? 'bg-primary-50 border-l-4 border-primary-600' : 'border-l-4 border-transparent' | |
| }`} | |
| > | |
| <div className="space-y-2"> | |
| <div className="font-mono text-sm font-semibold text-secondary-900 truncate"> | |
| {user.ip} | |
| </div> | |
| <div className="text-sm text-secondary-600 truncate"> | |
| {user.display_name} | |
| </div> | |
| <div className="flex items-center gap-3 text-xs text-secondary-500 flex-wrap"> | |
| <span className="flex items-center gap-1"> | |
| <MessageSquare className="w-3 h-3" /> | |
| {user.total_messages} | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <Users className="w-3 h-3" /> | |
| {user.sessions.length} | |
| </span> | |
| </div> | |
| </div> | |
| </button> | |
| )) | |
| ) : ( | |
| <div className="p-8 text-center text-secondary-500"> | |
| <p className="text-sm">No anonymous users yet</p> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Chat History */} | |
| <div className="lg:col-span-2 bg-white rounded-2xl shadow-sm border border-secondary-100 overflow-hidden flex flex-col"> | |
| {selectedUser ? ( | |
| <> | |
| {/* Header */} | |
| <div className="p-4 md:p-6 border-b border-secondary-100 bg-gradient-to-r from-primary-50 to-transparent"> | |
| <div className="space-y-2"> | |
| <div className="font-mono text-lg font-bold text-secondary-900 truncate"> | |
| {selectedUser.ip} | |
| </div> | |
| <div className="text-secondary-600 flex items-center gap-2 text-sm"> | |
| <MapPin className="w-4 h-4 text-primary-600 flex-shrink-0" /> | |
| <span className="truncate">{selectedUser.display_name}</span> | |
| </div> | |
| {chatHistory && ( | |
| <div className="flex gap-4 md:gap-6 text-xs md:text-sm text-secondary-500 pt-2 flex-wrap"> | |
| <span>{chatHistory.total_messages} messages</span> | |
| <span>{chatHistory.total_sessions} sessions</span> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Messages */} | |
| <div className="flex-1 overflow-y-auto p-4 md:p-6 space-y-4 bg-secondary-50/30"> | |
| {chatHistory?.chat_history && chatHistory.chat_history.length > 0 ? ( | |
| chatHistory.chat_history.map((msg, idx) => ( | |
| <div | |
| key={idx} | |
| className={`flex ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`} | |
| > | |
| <div | |
| className={`max-w-xs px-4 py-3 rounded-lg text-sm ${ | |
| msg.sender === 'user' | |
| ? 'bg-primary-600 text-white rounded-br-none' | |
| : 'bg-white text-secondary-900 border border-secondary-200 rounded-bl-none' | |
| }`} | |
| > | |
| <p className="break-words">{msg.text}</p> | |
| <p className={`text-xs mt-1 ${msg.sender === 'user' ? 'text-primary-100' : 'text-secondary-400'}`}> | |
| {new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} | |
| </p> | |
| </div> | |
| </div> | |
| )) | |
| ) : ( | |
| <div className="flex items-center justify-center h-full text-secondary-500"> | |
| <p className="text-sm">No messages yet</p> | |
| </div> | |
| )} | |
| </div> | |
| </> | |
| ) : ( | |
| <div className="flex-1 flex items-center justify-center text-secondary-400"> | |
| <div className="text-center px-6"> | |
| <MessageSquare className="w-12 h-12 mx-auto mb-4 text-secondary-300" /> | |
| <p className="text-sm">Select a user to view chat history</p> | |
| <p className="text-xs text-secondary-400 mt-2">Click on any user from the list</p> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default AnonymousUsersGeo; | |