import React, { useState } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { Trash2, Calendar, Clock, ChevronDown, ChevronUp, History as HistoryIcon, Dumbbell, Ruler, Timer, Weight, Edit2, Gauge, Pencil, Save, MoreVertical, ClipboardList, Download } from 'lucide-react'; import { TopBar } from './ui/TopBar'; import { WorkoutSession, ExerciseType, WorkoutSet, Language } from '../types'; import { t } from '../services/i18n'; import { formatSetMetrics } from '../utils/setFormatting'; import { useSession } from '../context/SessionContext'; import { generateCsv, downloadCsv } from '../utils/csvExport'; import { useAuth } from '../context/AuthContext'; import { getExercises } from '../services/storage'; import { Button } from './ui/Button'; import { Ripple } from './ui/Ripple'; import { Card } from './ui/Card'; import { Modal } from './ui/Modal'; import { SideSheet } from './ui/SideSheet'; import EditSetModal from './EditSetModal'; import FilledInput from './FilledInput'; interface HistoryProps { lang: Language; } const History: React.FC = ({ lang }) => { const { sessions, updateSession, deleteSession } = useSession(); const { currentUser } = useAuth(); const userId = currentUser?.id || ''; const navigate = useNavigate(); const [exercises, setExercises] = useState([]); const [editingSession, setEditingSession] = useState(null); const [menuState, setMenuState] = useState<{ id: string, x: number, y: number } | null>(null); const [deletingId, setDeletingId] = useState(null); const [deletingSetInfo, setDeletingSetInfo] = useState<{ sessionId: string, setId: string } | null>(null); const [editingSetInfo, setEditingSetInfo] = useState<{ sessionId: string, set: WorkoutSet } | null>(null); const handleSaveSingleSet = async (updatedSet: WorkoutSet) => { if (!editingSetInfo) return; const session = sessions.find(s => s.id === editingSetInfo.sessionId); if (session) { const updatedSets = session.sets.map(s => s.id === updatedSet.id ? updatedSet : s); const updatedSession = { ...session, sets: updatedSets }; try { await updateSession(updatedSession); setEditingSetInfo(null); } catch (e) { console.error("Failed to update set", e); } } }; const handleSaveSetFromModal = async (updatedSet: WorkoutSet) => { if (!editingSetInfo) return; // If editing within the session edit modal, update local state only if (editingSession && editingSession.id === editingSetInfo.sessionId) { const updatedSets = editingSession.sets.map(s => s.id === updatedSet.id ? updatedSet : s ); setEditingSession({ ...editingSession, sets: updatedSets }); setEditingSetInfo(null); } else { // Otherwise save to backend (Quick Log flow) await handleSaveSingleSet(updatedSet); } }; React.useEffect(() => { if (!userId) return; getExercises(userId).then(exs => setExercises(exs)); }, [userId]); const calculateSessionWork = (session: WorkoutSession) => { const bw = session.userBodyWeight || 70; return session.sets.reduce((acc, set) => { let w = 0; if (set.type === ExerciseType.STRENGTH) { w = (set.weight || 0) * (set.reps || 0); } if (set.type === ExerciseType.BODYWEIGHT) { const percent = set.bodyWeightPercentage || 100; const effectiveBw = bw * (percent / 100); w = (effectiveBw + (set.weight || 0)) * (set.reps || 0); } return acc + Math.max(0, w); }, 0); }; const formatDateForInput = (timestamp: number) => { const d = new Date(timestamp); const pad = (n: number) => (n < 10 ? '0' + n : n); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; }; const parseDateFromInput = (value: string) => { return new Date(value).getTime(); }; const formatDuration = (startTime: number, endTime?: number) => { if (!endTime || isNaN(endTime) || isNaN(startTime)) return ''; const durationMs = endTime - startTime; if (durationMs < 0 || isNaN(durationMs)) return ''; const hours = Math.floor(durationMs / 3600000); const minutes = Math.floor((durationMs % 3600000) / 60000); if (hours > 0) { return `${hours}h ${minutes}m`; } if (minutes < 1) { return '<1m'; } return `${minutes}m`; }; const handleSaveEdit = async () => { if (editingSession) { try { await updateSession(editingSession); setEditingSession(null); } catch (e) { console.error("Failed to update session", e); } } }; const handleDeleteSet = (setId: string) => { if (!editingSession) return; setEditingSession({ ...editingSession, sets: editingSession.sets.filter(s => s.id !== setId) }); }; const handleConfirmDelete = async () => { if (deletingId) { try { await deleteSession(deletingId); setDeletingId(null); } catch (e) { console.error("Failed to delete session", e); } } else if (deletingSetInfo) { // Find the session const session = sessions.find(s => s.id === deletingSetInfo.sessionId); if (session) { // Create updated session with the set removed const updatedSession = { ...session, sets: session.sets.filter(s => s.id !== deletingSetInfo.setId) }; try { await updateSession(updatedSession); } catch (e) { console.error("Failed to update session after set delete", e); } } setDeletingSetInfo(null); } } if (sessions.length === 0) { return (

{t('history_empty', lang)}

); } return (
{ const csvContent = generateCsv(sessions, exercises); downloadCsv(csvContent); }} title={t('export_csv', lang)} aria-label={t('export_csv', lang)} > } />
{/* Regular Workout Sessions */} {sessions.filter(s => s.type === 'STANDARD').map((session) => { const totalWork = calculateSessionWork(session); return ( setEditingSession(JSON.parse(JSON.stringify(session)))} >
{new Date(session.startTime).toISOString().split('T')[0]} {new Date(session.startTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} {session.endTime && ( {formatDuration(session.startTime, session.endTime)} )} {session.planName || t('no_plan', lang)} {session.userBodyWeight && ( {session.userBodyWeight}kg )}
{t('sets_count', lang)}: {session.sets.length} {totalWork > 0 && ( {(totalWork / 1000).toFixed(1)}t )}
) })} {/* Quick Log Sessions */} {sessions.filter(s => s.type === 'QUICK_LOG').length > 0 && (

{t('quick_log', lang)}

{(Object.entries( sessions .filter(s => s.type === 'QUICK_LOG') .reduce>((groups, session) => { const date = new Date(session.startTime).toISOString().split('T')[0]; if (!groups[date]) groups[date] = []; groups[date].push(session); return groups; }, {}) ) as [string, WorkoutSession[]][]) .sort(([a], [b]) => b.localeCompare(a)) .map(([date, daySessions]) => (
{date}
{daySessions .reduce((acc, session) => acc.concat(session.sets), []) .map((set, idx) => (
{set.exerciseName} {set.side && {t(set.side.toLowerCase() as any, lang)}}
{formatSetMetrics(set, lang)}
{new Date(set.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
))}
))}
)}
{/* MENU PORTAL */} {menuState && typeof document !== 'undefined' && createPortal( <>
{ e.stopPropagation(); setMenuState(null); }} />
, document.body )} {/* DELETE CONFIRMATION MODAL */} { setDeletingId(null); setDeletingSetInfo(null); }} title={deletingId ? t('delete_workout', lang) : t('delete_set', lang) || 'Delete Set'} maxWidth="sm" >

{deletingId ? t('delete_confirm', lang) : t('delete_set_confirm', lang) || 'Are you sure you want to delete this set?'}

{/* EDIT SESSION MODAL */} {editingSession && ( setEditingSession(null)} title={t('edit', lang)} width="lg" footer={
} >
{/* Meta Info */}
setEditingSession({ ...editingSession, startTime: parseDateFromInput(e.target.value) })} /> setEditingSession({ ...editingSession, endTime: parseDateFromInput(e.target.value) })} />
setEditingSession({ ...editingSession, userBodyWeight: parseFloat(e.target.value) })} />

{t('sets_count', lang)} ({editingSession.sets.length})

{editingSession.sets.map((set, idx) => (
{idx + 1} {set.exerciseName} {set.side && {t(set.side.toLowerCase() as any, lang)}}
{formatSetMetrics(set, lang)}
))}
) } {editingSetInfo && ( setEditingSetInfo(null)} set={editingSetInfo.set} exerciseDef={exercises.find(e => e.id === editingSetInfo.set.exerciseId)} onSave={handleSaveSetFromModal} lang={lang} /> )}
); }; export default History;