diff --git a/frontend/src/pages/History.jsx b/frontend/src/pages/History.jsx
index 1de7a39..4dbdea4 100644
--- a/frontend/src/pages/History.jsx
+++ b/frontend/src/pages/History.jsx
@@ -1,21 +1,18 @@
import React, { useState, useEffect, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { useToast } from '../components/Toast';
import api from '../api/axios';
-import { formatLocalDateTime, formatLocalDate, formatLocalTime } from '../utils/dateUtils';
-import PopularityBadge from '../components/PopularityBadge';
+import { formatLocalDate } from '../utils/dateUtils';
function History() {
const { isAuthenticated } = useAuth();
const { error, success } = useToast();
+ const navigate = useNavigate();
const [sessions, setSessions] = useState([]);
- const [selectedSession, setSelectedSession] = useState(null);
- const [sessionGames, setSessionGames] = useState([]);
const [loading, setLoading] = useState(true);
- const [showChatImport, setShowChatImport] = useState(false);
const [closingSession, setClosingSession] = useState(null);
const [showAllSessions, setShowAllSessions] = useState(false);
- const [deletingSession, setDeletingSession] = useState(null);
const loadSessions = useCallback(async () => {
try {
@@ -28,124 +25,28 @@ function History() {
}
}, []);
- const refreshSessionGames = useCallback(async (sessionId, silent = false) => {
- try {
- const response = await api.get(`/sessions/${sessionId}/games`);
- // Reverse chronological order (most recent first) - create new array to avoid mutation
- setSessionGames([...response.data].reverse());
- } catch (err) {
- if (!silent) {
- console.error('Failed to load session games', err);
- }
- }
- }, []);
-
useEffect(() => {
loadSessions();
}, [loadSessions]);
- // Auto-select active session if navigating from picker
- useEffect(() => {
- if (sessions.length > 0 && !selectedSession) {
- const activeSession = sessions.find(s => s.is_active === 1);
- if (activeSession) {
- loadSessionGames(activeSession.id);
- }
- }
- }, [sessions, selectedSession]);
-
- // Poll for session list updates (to detect when sessions end/start)
useEffect(() => {
const interval = setInterval(() => {
loadSessions();
}, 3000);
-
return () => clearInterval(interval);
}, [loadSessions]);
- // Poll for updates on active session games
- useEffect(() => {
- if (!selectedSession) return;
-
- const currentSession = sessions.find(s => s.id === selectedSession);
- if (!currentSession || currentSession.is_active !== 1) return;
-
- // Refresh games every 3 seconds for active session
- const interval = setInterval(() => {
- refreshSessionGames(selectedSession, true); // silent refresh
- }, 3000);
-
- return () => clearInterval(interval);
- }, [selectedSession, sessions, refreshSessionGames]);
-
- const handleExport = async (sessionId, format) => {
- try {
- const response = await api.get(`/sessions/${sessionId}/export?format=${format}`, {
- responseType: 'blob'
- });
-
- // Create download link
- const url = window.URL.createObjectURL(new Blob([response.data]));
- const link = document.createElement('a');
- link.href = url;
- link.setAttribute('download', `session-${sessionId}.${format === 'json' ? 'json' : 'txt'}`);
- document.body.appendChild(link);
- link.click();
- link.parentNode.removeChild(link);
- window.URL.revokeObjectURL(url);
-
- success(`Session exported as ${format.toUpperCase()}`);
- } catch (err) {
- console.error('Failed to export session', err);
- error('Failed to export session');
- }
- };
-
- const loadSessionGames = async (sessionId, silent = false) => {
- try {
- const response = await api.get(`/sessions/${sessionId}/games`);
- // Reverse chronological order (most recent first) - create new array to avoid mutation
- setSessionGames([...response.data].reverse());
- if (!silent) {
- setSelectedSession(sessionId);
- }
- } catch (err) {
- if (!silent) {
- console.error('Failed to load session games', err);
- }
- }
- };
-
const handleCloseSession = async (sessionId, notes) => {
try {
await api.post(`/sessions/${sessionId}/close`, { notes });
await loadSessions();
setClosingSession(null);
- if (selectedSession === sessionId) {
- // Reload the session details to show updated state
- loadSessionGames(sessionId);
- }
success('Session ended successfully');
} catch (err) {
error('Failed to close session');
}
};
- const handleDeleteSession = async (sessionId) => {
- try {
- await api.delete(`/sessions/${sessionId}`);
- await loadSessions();
- setDeletingSession(null);
- if (selectedSession === sessionId) {
- setSelectedSession(null);
- setSessionGames([]);
- }
- success('Session deleted successfully');
- } catch (err) {
- error('Failed to delete session: ' + (err.response?.data?.error || err.message));
- }
- };
-
if (loading) {
return (
@@ -155,315 +56,97 @@ function History() {
}
return (
-
+
Session History
-
- {/* Sessions List */}
-
-
-
-
Sessions
- {sessions.length > 3 && (
- setShowAllSessions(!showAllSessions)}
- className="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 transition"
- >
- {showAllSessions ? 'Show Recent' : `Show All (${sessions.length})`}
-
- )}
-
-
- {sessions.length === 0 ? (
-
No sessions found
- ) : (
-
- {(showAllSessions ? sessions : sessions.slice(0, 3)).map(session => (
-
- {/* Main session info - clickable */}
-
loadSessionGames(session.id)}
- className="p-3 cursor-pointer"
- >
-
-
-
-
- Session #{session.id}
-
- {session.is_active === 1 && (
-
- Active
-
- )}
-
-
- {formatLocalDate(session.created_at)}
- •
- {session.games_played} game{session.games_played !== 1 ? 's' : ''}
-
-
-
-
-
- {/* Action buttons for authenticated users */}
- {isAuthenticated && (
-
- {session.is_active === 1 ? (
- {
- e.stopPropagation();
- setClosingSession(session.id);
- }}
- className="w-full bg-orange-600 dark:bg-orange-700 text-white px-4 py-2 rounded text-sm hover:bg-orange-700 dark:hover:bg-orange-800 transition"
- >
- End Session
-
- ) : (
- {
- e.stopPropagation();
- setDeletingSession(session.id);
- }}
- className="w-full bg-red-600 dark:bg-red-700 text-white px-4 py-2 rounded text-sm hover:bg-red-700 dark:hover:bg-red-800 transition"
- >
- Delete Session
-
- )}
-
- )}
-
- ))}
-
- )}
-
-
-
- {/* Session Details */}
-
- {selectedSession ? (
-
-
-
-
-
- Session #{selectedSession}
-
- {sessions.find(s => s.id === selectedSession)?.is_active === 1 && (
-
- 🟢 Active
-
- )}
-
-
- {sessions.find(s => s.id === selectedSession)?.created_at &&
- formatLocalDateTime(sessions.find(s => s.id === selectedSession).created_at)}
-
- {sessions.find(s => s.id === selectedSession)?.is_active === 1 && (
-
- Games update automatically
-
- )}
-
-
-
- {isAuthenticated && sessions.find(s => s.id === selectedSession)?.is_active === 1 && (
- setShowChatImport(true)}
- className="bg-indigo-600 dark:bg-indigo-700 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 dark:hover:bg-indigo-800 transition text-sm sm:text-base w-full sm:w-auto"
- >
- Import Chat Log
-
- )}
- {isAuthenticated && (
- <>
- handleExport(selectedSession, 'txt')}
- className="bg-gray-600 dark:bg-gray-700 text-white px-4 py-2 rounded-lg hover:bg-gray-700 dark:hover:bg-gray-800 transition text-sm sm:text-base w-full sm:w-auto"
- >
- Export as Text
-
- handleExport(selectedSession, 'json')}
- className="bg-gray-600 dark:bg-gray-700 text-white px-4 py-2 rounded-lg hover:bg-gray-700 dark:hover:bg-gray-800 transition text-sm sm:text-base w-full sm:w-auto"
- >
- Export as JSON
-
- >
- )}
-
-
-
- {showChatImport && (
-
setShowChatImport(false)}
- onImportComplete={() => {
- loadSessionGames(selectedSession);
- setShowChatImport(false);
- }}
- />
- )}
-
- {sessionGames.length === 0 ? (
- No games played in this session
- ) : (
-
-
- Games Played ({sessionGames.length})
-
-
- {sessionGames.map((game, index) => (
-
-
-
-
- {sessionGames.length - index}. {game.title}
-
-
{game.pack_name}
-
-
-
- {formatLocalTime(game.played_at)}
-
- {game.manually_added === 1 && (
-
- Manual
-
- )}
-
-
-
-
-
- Players: {game.min_players}-{game.max_players}
-
-
- Type: {game.game_type || 'N/A'}
-
-
-
-
- ))}
-
-
- )}
-
- ) : (
-
-
Select a session to view details
-
+
+
+
Sessions
+ {sessions.length > 5 && (
+ setShowAllSessions(!showAllSessions)}
+ className="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 transition"
+ >
+ {showAllSessions ? 'Show Recent' : `Show All (${sessions.length})`}
+
)}
+
+ {sessions.length === 0 ? (
+
No sessions found
+ ) : (
+
+ {(showAllSessions ? sessions : sessions.slice(0, 5)).map(session => (
+
+
navigate(`/history/${session.id}`)}
+ className="p-4"
+ >
+
+
+
+ Session #{session.id}
+
+ {session.is_active === 1 && (
+
+ Active
+
+ )}
+
+
+ {session.games_played} game{session.games_played !== 1 ? 's' : ''}
+
+
+
+ {formatLocalDate(session.created_at)}
+
+ {session.has_notes && session.notes_preview && (
+
+ {session.notes_preview}
+
+ )}
+
+
+ {isAuthenticated && session.is_active === 1 && (
+
+ {
+ e.stopPropagation();
+ setClosingSession(session.id);
+ }}
+ className="w-full bg-orange-600 dark:bg-orange-700 text-white px-4 py-2 rounded text-sm hover:bg-orange-700 dark:hover:bg-orange-800 transition"
+ >
+ End Session
+
+
+ )}
+
+ ))}
+
+ )}
- {/* End Session Modal */}
{closingSession && (
setClosingSession(null)}
onConfirm={handleCloseSession}
- onShowChatImport={() => {
- setShowChatImport(true);
- if (closingSession !== selectedSession) {
- loadSessionGames(closingSession);
- }
- }}
/>
)}
-
- {/* Delete Confirmation Modal */}
- {deletingSession && (
-
-
-
Delete Session?
-
- Are you sure you want to delete Session #{deletingSession}?
- This will permanently delete all games and chat logs associated with this session. This action cannot be undone.
-
-
- handleDeleteSession(deletingSession)}
- className="flex-1 bg-red-600 dark:bg-red-700 text-white py-3 rounded-lg hover:bg-red-700 dark:hover:bg-red-800 transition font-semibold"
- >
- Delete Permanently
-
- setDeletingSession(null)}
- className="flex-1 bg-gray-600 dark:bg-gray-700 text-white py-3 rounded-lg hover:bg-gray-700 dark:hover:bg-gray-800 transition"
- >
- Cancel
-
-
-
-
- )}
);
}
-function EndSessionModal({ sessionId, sessionGames, onClose, onConfirm, onShowChatImport }) {
+function EndSessionModal({ sessionId, onClose, onConfirm }) {
const [notes, setNotes] = useState('');
-
- // Check if any games have been voted on (popularity != 0)
- const hasPopularityData = sessionGames.some(game => game.popularity_score !== 0);
- const showPopularityWarning = sessionGames.length > 0 && !hasPopularityData;
return (
End Session #{sessionId}
-
- {/* Popularity Warning */}
- {showPopularityWarning && (
-
-
-
⚠️
-
-
- No Popularity Data
-
-
- You haven't imported chat reactions yet. Import now to track which games your players loved!
-
-
{
- onClose();
- onShowChatImport();
- }}
- className="text-sm bg-yellow-600 dark:bg-yellow-700 text-white px-4 py-2 rounded hover:bg-yellow-700 dark:hover:bg-yellow-800 transition"
- >
- Import Chat Log
-
-
-
-
- )}
-
Session Notes (optional)
@@ -475,7 +158,6 @@ function EndSessionModal({ sessionId, sessionGames, onClose, onConfirm, onShowCh
placeholder="Add any notes about this session..."
/>
-
onConfirm(sessionId, notes)}
@@ -495,183 +177,4 @@ function EndSessionModal({ sessionId, sessionGames, onClose, onConfirm, onShowCh
);
}
-function ChatImportPanel({ sessionId, onClose, onImportComplete }) {
- const [chatData, setChatData] = useState('');
- const [importing, setImporting] = useState(false);
- const [result, setResult] = useState(null);
- const { error, success } = useToast();
-
- const handleFileUpload = async (event) => {
- const file = event.target.files[0];
- if (!file) return;
-
- try {
- const text = await file.text();
- setChatData(text);
- success('File loaded successfully');
- } catch (err) {
- error('Failed to read file: ' + err.message);
- }
- };
-
- const handleImport = async () => {
- if (!chatData.trim()) {
- error('Please enter chat data or upload a file');
- return;
- }
-
- setImporting(true);
- setResult(null);
-
- try {
- const parsedData = JSON.parse(chatData);
- const response = await api.post(`/sessions/${sessionId}/chat-import`, {
- chatData: parsedData
- });
- setResult(response.data);
- success('Chat log imported successfully');
- setTimeout(() => {
- onImportComplete();
- }, 2000);
- } catch (err) {
- error('Import failed: ' + (err.response?.data?.error || err.message));
- } finally {
- setImporting(false);
- }
- };
-
- return (
-
-
Import Chat Log
-
-
- Upload a JSON file or paste JSON array with format: [{"{"}"username": "...", "message": "...", "timestamp": "..."{"}"}]
-
- The system will detect "thisgame++" and "thisgame--" patterns and update game popularity.
-
-
- Note: Popularity is cumulative - votes are added to each game's all-time totals.
-
-
-
- {/* File Upload */}
-
- Upload JSON File
-
-
-
-
- — or —
-
-
-
- Paste Chat JSON Data
-
-
- {result && (
-
-
Import Successful!
-
- Imported {result.messagesImported} messages, processed {result.votesProcessed} votes
- {result.duplicatesSkipped > 0 && (
-
- ({result.duplicatesSkipped} duplicate{result.duplicatesSkipped !== 1 ? 's' : ''} skipped)
-
- )}
-
- {result.votesByGame && Object.keys(result.votesByGame).length > 0 && (
-
-
Votes by game:
-
- {Object.values(result.votesByGame).map((vote, i) => (
-
- {vote.title}: +{vote.upvotes} / -{vote.downvotes}
-
- ))}
-
-
- Note: Popularity is cumulative across all sessions. If a game is played multiple times, votes apply to the game itself.
-
-
- )}
-
- {/* Debug Info */}
- {result.debug && (
-
-
- Debug Info (click to expand)
-
-
- {/* Session Timeline */}
-
-
Session Timeline:
-
- {result.debug.sessionGamesTimeline?.map((game, i) => (
-
- {game.title} - {new Date(game.played_at).toLocaleString()}
-
- ))}
-
-
-
- {/* Vote Matches */}
- {result.debug.voteMatches && result.debug.voteMatches.length > 0 && (
-
-
Vote Matches ({result.debug.voteMatches.length}):
-
- {result.debug.voteMatches.map((match, i) => (
-
- {match.username}: {match.vote} at {new Date(match.timestamp).toLocaleString()} → matched to "{match.matched_game}" (played at {new Date(match.game_played_at).toLocaleString()})
-
- ))}
-
-
- )}
-
-
- )}
-
- )}
-
-
-
- {importing ? 'Importing...' : 'Import'}
-
-
- Close
-
-
-
- );
-}
-
export default History;
-