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 (
Loading...
); } return (

Session History

{/* Controls Bar */}
Filter:
Show:
{sessions.length === absoluteTotal ? `${absoluteTotal} session${absoluteTotal !== 1 ? 's' : ''} total` : `${sessions.length} visible (${absoluteTotal} total)` } {isAuthenticated && ( )}
{/* Session List */} {sessions.length === 0 ? (

No sessions found

) : (
{sessions.map(session => { const isActive = session.is_active === 1; const isSelected = selectedIds.has(session.id); const isSundaySession = isSunday(session.created_at); const isArchived = session.archived === 1; const canSelect = selectMode && !isActive; return (
{ if (longPressFired.current) { longPressFired.current = false; return; } if (selectMode) { if (!isActive) toggleSelection(session.id); } else { navigate(`/history/${session.id}`); } }} onPointerDown={() => { if (!isActive) handlePointerDown(session.id); }} onPointerUp={handlePointerUp} onPointerLeave={handlePointerUp} >
{selectMode && (
{isSelected && ( โœ“ )}
)}
Session #{session.id} {isActive && ( Active )} {isSundaySession && ( ๐ŸŽฒ Game Night )} {isArchived && (filter === 'all' || filter === 'archived') && ( Archived )}
{session.games_played} game{session.games_played !== 1 ? 's' : ''}
{formatLocalDate(session.created_at)} {isSundaySession && ( ยท Sunday )}
{session.has_notes && session.notes_preview && (
{session.notes_preview}
)}
{!selectMode && isAuthenticated && isActive && (
)}
); })}
)} {/* Multi-select Action Bar */} {selectMode && selectedIds.size > 0 && (
{selectedIds.size} selected
{filter !== 'archived' && ( )} {filter !== 'default' && ( )}
)}
{/* End Session Modal */} {closingSession && ( setClosingSession(null)} onConfirm={handleCloseSession} /> )} {/* Bulk Delete Confirmation Modal */} {showBulkDeleteConfirm && (

Delete {selectedIds.size} Session{selectedIds.size !== 1 ? 's' : ''}?

This will permanently delete {selectedIds.size} session{selectedIds.size !== 1 ? 's' : ''} and all associated games and chat logs. This action cannot be undone.

)}
); } function EndSessionModal({ sessionId, onClose, onConfirm }) { const [notes, setNotes] = useState(''); return (

End Session #{sessionId}