import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { useToast } from '../components/Toast'; import api from '../api/axios'; import { formatLocalDate, isSunday } from '../utils/dateUtils'; import { prefixKey } from '../utils/adminPrefs'; function History() { const { isAuthenticated, adminName } = useAuth(); const { error, success } = useToast(); const navigate = useNavigate(); const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); const [totalCount, setTotalCount] = useState(0); const [absoluteTotal, setAbsoluteTotal] = useState(0); const [closingSession, setClosingSession] = useState(null); const [filter, setFilter] = useState(() => localStorage.getItem(prefixKey(adminName, 'history-filter')) || 'default'); const [limit, setLimit] = useState(() => localStorage.getItem(prefixKey(adminName, 'history-show-limit')) || '5'); const [selectMode, setSelectMode] = useState(false); const [selectedIds, setSelectedIds] = useState(new Set()); const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false); const longPressTimer = useRef(null); const longPressFired = useRef(false); const loadSessions = useCallback(async () => { try { const response = await api.get('/sessions', { params: { filter, limit } }); setSessions(response.data); setTotalCount(parseInt(response.headers['x-total-count'] || '0', 10)); setAbsoluteTotal(parseInt(response.headers['x-absolute-total'] || '0', 10)); } catch (err) { console.error('Failed to load sessions', err); } finally { setLoading(false); } }, [filter, limit]); useEffect(() => { loadSessions(); }, [loadSessions]); useEffect(() => { const interval = setInterval(() => { loadSessions(); }, 3000); return () => clearInterval(interval); }, [loadSessions]); useEffect(() => { if (adminName) { const savedFilter = localStorage.getItem(prefixKey(adminName, 'history-filter')); const savedLimit = localStorage.getItem(prefixKey(adminName, 'history-show-limit')); if (savedFilter) setFilter(savedFilter); if (savedLimit) setLimit(savedLimit); } }, [adminName]); const handleFilterChange = (newFilter) => { setFilter(newFilter); localStorage.setItem(prefixKey(adminName, 'history-filter'), newFilter); setSelectedIds(new Set()); }; const handleLimitChange = (newLimit) => { setLimit(newLimit); localStorage.setItem(prefixKey(adminName, 'history-show-limit'), newLimit); setSelectedIds(new Set()); }; const handleCloseSession = async (sessionId, notes) => { try { await api.post(`/sessions/${sessionId}/close`, { notes }); await loadSessions(); setClosingSession(null); success('Session ended successfully'); } catch (err) { error('Failed to close session'); } }; // Multi-select handlers const toggleSelection = (sessionId) => { setSelectedIds(prev => { const next = new Set(prev); if (next.has(sessionId)) { next.delete(sessionId); } else { next.add(sessionId); } return next; }); }; const exitSelectMode = () => { setSelectMode(false); setSelectedIds(new Set()); setShowBulkDeleteConfirm(false); }; const handlePointerDown = (sessionId) => { if (!isAuthenticated || selectMode) return; longPressFired.current = false; longPressTimer.current = setTimeout(() => { longPressFired.current = true; setSelectMode(true); setSelectedIds(new Set([sessionId])); }, 500); }; const handlePointerUp = () => { if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null; } }; const handleBulkAction = async (action) => { try { await api.post('/sessions/bulk', { action, ids: Array.from(selectedIds) }); success(`${selectedIds.size} session${selectedIds.size !== 1 ? 's' : ''} ${action}d`); setSelectedIds(new Set()); setShowBulkDeleteConfirm(false); await loadSessions(); } catch (err) { error(err.response?.data?.error || `Failed to ${action} sessions`); } }; if (loading) { return (
No sessions found
) : (This will permanently delete {selectedIds.size} session{selectedIds.size !== 1 ? 's' : ''} and all associated games and chat logs. This action cannot be undone.