import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { X, Building2, Loader2, Trash2, UserCheck, AlertTriangle } from 'lucide-react'; import { useAuth } from '../lib/auth'; import { useTenant } from '../lib/tenant'; import { api } from '../lib/api'; import { useToast } from '../hooks/useToast'; import { logWarn } from '../lib/logger'; export default function UserListPage() { const { t } = useTranslation(); const toast = useToast(); const { token } = useAuth(); const { selectedOrgId } = useTenant(); const [users, setUsers] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const LIMIT = 20; const [loading, setLoading] = useState(true); const [selectedUser, setSelectedUser] = useState(null); const [messages, setMessages] = useState([]); const [loadingMsg, setLoadingMsg] = useState(false); const [handoffStatus, setHandoffStatus] = useState>({}); const [deletingId, setDeletingId] = useState(null); const [releasingId, setReleasingId] = useState(null); const loadUsers = () => { if (!selectedOrgId) { setLoading(false); return; } setLoading(true); api.get(`/v1/admin/users?page=${page}&limit=${LIMIT}`, token, selectedOrgId) .then(d => { setUsers(d.users || d); setTotal(d.total || 0); setLoading(false); }) .catch((err) => { setLoading(false); toast.error(err?.message ?? t('users.load_error')); }); }; useEffect(() => { loadUsers(); }, [token, selectedOrgId, page]); const viewMessages = async (userId: string) => { setLoadingMsg(true); setSelectedUser({ id: userId }); try { const [data, handoff] = await Promise.all([ api.get(`/v1/admin/users/${userId}/messages`, token, selectedOrgId), api.get(`/v1/admin/users/${userId}/handoff`, token, selectedOrgId).catch(() => ({ handoffActive: false })), ]); setSelectedUser(data.user); setMessages(data.messages || []); setHandoffStatus(prev => ({ ...prev, [userId]: handoff.handoffActive })); } catch (err) { logWarn('[UserList] fetchMessages failed', err); toast.error(t('common.error')); } finally { setLoadingMsg(false); } }; const handleDelete = async (userId: string) => { if (!confirm(t('users.confirm_delete'))) return; setDeletingId(userId); try { await api.delete(`/v1/admin/users/${userId}`, token, selectedOrgId); setUsers(prev => prev.filter(u => u.id !== userId)); setTotal(prev => prev - 1); toast.success(t('users.delete_success')); } catch (err: any) { toast.error(err?.message ?? t('users.delete_error')); } finally { setDeletingId(null); } }; const handleReleaseHandoff = async (userId: string) => { setReleasingId(userId); try { const res = await api.delete(`/v1/admin/users/${userId}/handoff`, token, selectedOrgId); setHandoffStatus(prev => ({ ...prev, [userId]: false })); toast.success(res.wasActive ? t('users.handoff_released') : t('users.handoff_none')); } catch (err: any) { toast.error(err?.message ?? t('users.handoff_error')); } finally { setReleasingId(null); } }; if (!selectedOrgId) { return (

{t('settings.no_org_selected')}

); } if (loading) { return (

{t('common.loading')}

); } const tableHeaders = [ t('common.phone'), t('common.name'), t('users.language_column'), t('users.sector_column'), t('nav.organizations'), t('users.columns.status'), t('common.date'), t('common.actions') ]; return (

{t('users.title')} ({total})

{tableHeaders.map(h => )} {users.map((u: any) => ( ))} {!users.length && ( )}
{h}
{u.phone} {u.name || '—'} {u.language} {u.activity || '—'} {u._count?.enrollments || 0} {u._count?.responses || 0} {new Date(u.createdAt).toLocaleDateString()}
{t('users.no_users')}
{/* Pagination */} {total > LIMIT && (
{((page - 1) * LIMIT) + 1}–{Math.min(page * LIMIT, total)} {t('common.of')} {total}
)} {/* Conversation Detail Modal */} {selectedUser && (

{selectedUser.name || 'Chat'}

{selectedUser.phone}

{handoffStatus[selectedUser.id] && (
{t('users.handoff_active')}
)}
{loadingMsg ? (
{t('common.loading')}
) : messages.length === 0 ? (
{t('crm.inbox.no_messages')}
) : ( messages.map((m: any) => { const isBot = m.direction === 'OUTBOUND'; return (
{m.mediaUrl && (
{m.mediaUrl.endsWith('.mp3') || m.mediaUrl.endsWith('.ogg') || m.mediaUrl.endsWith('.webm') ?
)} {m.content &&

{m.content}

}

{new Date(m.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

); }) )}
)}
); }