Files
jackboxpartypack-gamepicker/frontend/src/pages/History.jsx

181 lines
6.7 KiB
React
Raw Normal View History

2025-10-30 17:52:44 -04:00
import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
2025-10-30 04:27:43 -04:00
import { useAuth } from '../context/AuthContext';
import { useToast } from '../components/Toast';
2025-10-30 04:27:43 -04:00
import api from '../api/axios';
import { formatLocalDate } from '../utils/dateUtils';
2025-10-30 04:27:43 -04:00
function History() {
const { isAuthenticated } = useAuth();
const { error, success } = useToast();
const navigate = useNavigate();
2025-10-30 04:27:43 -04:00
const [sessions, setSessions] = useState([]);
const [loading, setLoading] = useState(true);
const [closingSession, setClosingSession] = useState(null);
const [showAllSessions, setShowAllSessions] = useState(false);
2025-10-30 04:27:43 -04:00
2025-10-30 17:52:44 -04:00
const loadSessions = useCallback(async () => {
try {
const response = await api.get('/sessions');
setSessions(response.data);
} catch (err) {
console.error('Failed to load sessions', err);
} finally {
setLoading(false);
}
}, []);
2025-10-30 04:27:43 -04:00
useEffect(() => {
loadSessions();
2025-10-30 17:52:44 -04:00
}, [loadSessions]);
2025-10-30 04:27:43 -04:00
2025-10-30 17:34:44 -04:00
useEffect(() => {
const interval = setInterval(() => {
loadSessions();
}, 3000);
return () => clearInterval(interval);
2025-10-30 17:52:44 -04:00
}, [loadSessions]);
2025-10-30 17:34:44 -04:00
2025-10-30 04:27:43 -04:00
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');
}
};
2025-10-30 04:27:43 -04:00
if (loading) {
return (
<div className="flex justify-center items-center h-64">
<div className="text-xl text-gray-600 dark:text-gray-400">Loading...</div>
2025-10-30 04:27:43 -04:00
</div>
);
}
return (
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-gray-800 dark:text-gray-100">Session History</h1>
2025-10-30 04:27:43 -04:00
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-semibold text-gray-800 dark:text-gray-100">Sessions</h2>
{sessions.length > 5 && (
<button
onClick={() => 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})`}
</button>
)}
2025-10-30 04:27:43 -04:00
</div>
{sessions.length === 0 ? (
<p className="text-gray-500 dark:text-gray-400">No sessions found</p>
) : (
<div className="space-y-2">
{(showAllSessions ? sessions : sessions.slice(0, 5)).map(session => (
<div
key={session.id}
className="border border-gray-300 dark:border-gray-600 rounded-lg hover:border-indigo-400 dark:hover:border-indigo-500 transition cursor-pointer"
>
<div
onClick={() => navigate(`/history/${session.id}`)}
className="p-4"
>
<div className="flex justify-between items-center mb-1">
<div className="flex items-center gap-2">
<span className="font-semibold text-gray-800 dark:text-gray-100">
Session #{session.id}
</span>
{session.is_active === 1 && (
<span className="bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 text-xs px-2 py-0.5 rounded">
Active
</span>
)}
</div>
<span className="text-sm text-gray-500 dark:text-gray-400">
{session.games_played} game{session.games_played !== 1 ? 's' : ''}
</span>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{formatLocalDate(session.created_at)}
</div>
{session.has_notes && session.notes_preview && (
<div className="mt-2 text-sm text-indigo-400 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/20 px-3 py-2 rounded border-l-2 border-indigo-500">
{session.notes_preview}
</div>
)}
2025-10-30 04:27:43 -04:00
</div>
{isAuthenticated && session.is_active === 1 && (
<div className="px-4 pb-4 pt-0">
<button
onClick={(e) => {
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
</button>
2025-10-30 04:27:43 -04:00
</div>
)}
</div>
))}
</div>
)}
2025-10-30 04:27:43 -04:00
</div>
{closingSession && (
<EndSessionModal
2025-10-30 04:27:43 -04:00
sessionId={closingSession}
onClose={() => setClosingSession(null)}
onConfirm={handleCloseSession}
/>
)}
</div>
);
}
function EndSessionModal({ sessionId, onClose, onConfirm }) {
2025-10-30 04:27:43 -04:00
const [notes, setNotes] = useState('');
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-8 max-w-md w-full">
<h2 className="text-2xl font-bold mb-4 dark:text-gray-100">End Session #{sessionId}</h2>
2025-10-30 04:27:43 -04:00
<div className="mb-4">
<label className="block text-gray-700 dark:text-gray-300 font-semibold mb-2">
2025-10-30 04:27:43 -04:00
Session Notes (optional)
</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg h-32 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
2025-10-30 04:27:43 -04:00
placeholder="Add any notes about this session..."
/>
</div>
<div className="flex gap-4">
<button
onClick={() => onConfirm(sessionId, notes)}
className="flex-1 bg-orange-600 dark:bg-orange-700 text-white py-3 rounded-lg hover:bg-orange-700 dark:hover:bg-orange-800 transition"
2025-10-30 04:27:43 -04:00
>
End Session
2025-10-30 04:27:43 -04:00
</button>
<button
onClick={onClose}
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"
2025-10-30 04:27:43 -04:00
>
Cancel
</button>
</div>
</div>
</div>
);
}
export default History;