import { BarChart3, Trash2, Clock, Users, MessageSquare, Video, Mic, ChevronDown, ChevronUp, Hand, BarChart2, Download } from 'lucide-react'; import { useState } from 'react'; import api from '../services/api'; import { useLanguage } from '../contexts/LanguageContext'; import toast from 'react-hot-toast'; export default function AnalyticsList({ analytics, onRefresh, isOwner = true }) { const [loading, setLoading] = useState({}); const [expanded, setExpanded] = useState({}); const [exportMenu, setExportMenu] = useState({}); const { t, language } = useLanguage(); const formatDate = (dateStr) => { if (!dateStr) return '—'; return new Date(dateStr).toLocaleDateString(language === 'de' ? 'de-DE' : 'en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }); }; const formatDurationSec = (sec) => { if (!sec || sec <= 0) return '0m'; const minutes = Math.floor(sec / 60); const hours = Math.floor(minutes / 60); const mins = minutes % 60; const secs = sec % 60; if (hours > 0) return `${hours}h ${mins}m`; if (minutes > 0) return `${mins}m ${secs}s`; return `${secs}s`; }; const handleDelete = async (id) => { if (!confirm(t('analytics.deleteConfirm'))) return; setLoading(prev => ({ ...prev, [id]: 'deleting' })); try { await api.delete(`/analytics/${id}`); toast.success(t('analytics.deleted')); onRefresh?.(); } catch { toast.error(t('analytics.deleteFailed')); } finally { setLoading(prev => ({ ...prev, [id]: null })); } }; const toggleExpand = (id) => { setExpanded(prev => ({ ...prev, [id]: !prev[id] })); }; const toggleExportMenu = (id) => { setExportMenu(prev => ({ ...prev, [id]: !prev[id] })); }; const handleExport = async (id, format) => { setExportMenu(prev => ({ ...prev, [id]: false })); setLoading(prev => ({ ...prev, [id]: 'exporting' })); try { const response = await api.get(`/analytics/${id}/export/${format}`, { responseType: 'blob' }); const disposition = response.headers['content-disposition']; const match = disposition?.match(/filename="?([^"]+)"?/); const filename = match?.[1] || `analytics.${format}`; const url = window.URL.createObjectURL(response.data); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); toast.success(t('analytics.exportSuccess')); } catch { toast.error(t('analytics.exportFailed')); } finally { setLoading(prev => ({ ...prev, [id]: null })); } }; // Extract user summary from BBB learning analytics callback data // Payload: { meeting_id, data: { duration, start, finish, attendees: [{ name, moderator, duration, engagement: { chats, talks, raisehand, emojis, poll_votes, talk_time } }] } } const getUserSummary = (data) => { const attendees = data?.data?.attendees; if (!Array.isArray(attendees)) return []; return attendees.map(a => ({ name: a.name || '—', isModerator: !!a.moderator, duration: a.duration || 0, talkTime: a.engagement?.talk_time || 0, chats: a.engagement?.chats || 0, talks: a.engagement?.talks || 0, raiseHand: a.engagement?.raisehand || 0, emojis: a.engagement?.emojis || 0, pollVotes: a.engagement?.poll_votes || 0, })); }; const getMeetingSummary = (data) => ({ duration: data?.data?.duration || 0, start: data?.data?.start || null, finish: data?.data?.finish || null, files: data?.data?.files || [], polls: data?.data?.polls || [], }); if (!analytics || analytics.length === 0) { return (
{t('analytics.noData')}
| {t('analytics.userName')} | {t('analytics.role')} |
|
|
|
|
{t('analytics.reactions')} |
|---|---|---|---|---|---|---|
| {u.name} | {u.isModerator ? t('analytics.moderator') : t('analytics.viewer')} | {formatDurationSec(u.duration)} | {formatDurationSec(u.talkTime)} | {u.chats} | {u.raiseHand} | {u.emojis} |