import { Add, Delete, Edit } from '@mui/icons-material' import { Alert, Box, Button, Card, Chip, FormControl, FormHelperText, Input, Typography, } from '@mui/joy' import moment from 'moment' import { useEffect, useState } from 'react' import { useLocalization } from '../../../contexts/LocalizationContext' import { useResponsiveModal } from '../../../hooks/useResponsiveModal' import { useNotification } from '../../../service/NotificationProvider' import { useChoreTimer, useDeleteTimeSession, useUpdateTimeSession, } from '../../../queries/TimeQueries' import ConfirmationModal from './ConfirmationModal' const TimerEditModal = ({ isOpen, onClose, choreId, onTimerUpdate }) => { const { ResponsiveModal } = useResponsiveModal() const { fmt } = useLocalization() const [timerData, setTimerData] = useState(null) const [loading, setLoading] = useState(false) const [editingSessions, setEditingSessions] = useState({}) const [confirmDeleteConfig, setConfirmDeleteConfig] = useState({}) const [currentTime, setCurrentTime] = useState(new Date()) const { showError, showSuccess } = useNotification() // Timer hooks const { data: choreTimer, refetch: refetchTimer } = useChoreTimer(choreId) const updateTimeSession = useUpdateTimeSession() const deleteTimeSession = useDeleteTimeSession() // Update timerData when choreTimer data changes useEffect(() => { if (choreTimer?.res) { setTimerData(choreTimer.res) } }, [choreTimer]) // Real-time update interval for active timers useEffect(() => { let interval if (isOpen && timerData && !timerData.endTime) { // Update every second if timer is active interval = setInterval(() => { setCurrentTime(new Date()) }, 1000) } return () => { if (interval) clearInterval(interval) } }, [isOpen, timerData]) const formatTime = seconds => { const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) const secs = seconds % 60 return `${hours.toString().padStart(2, '0')}:${minutes .toString() .padStart(2, '0')}:${secs.toString().padStart(2, '0')}` } const formatDuration = seconds => { if (seconds < 60) return `${seconds}s` if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s` const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) return `${hours}h ${minutes}m` } const startEditingSession = () => { if (timerData) { setEditingSessions(prev => ({ ...prev, [timerData.id]: { startTime: moment(timerData.startTime).format('YYYY-MM-DDTHH:mm:ss'), endTime: timerData.endTime ? moment(timerData.endTime).format('YYYY-MM-DDTHH:mm:ss') : '', duration: timerData.duration, formattedDuration: formatTime(timerData.duration), pauseLog: timerData.pauseLog || [], }, })) } } const addPauseLogEntry = sessionId => { setEditingSessions(prev => ({ ...prev, [sessionId]: { ...prev[sessionId], pauseLog: [ ...prev[sessionId].pauseLog, { start: new Date().toISOString(), end: null, duration: 0, updatedBy: 0, // This should be current user ID }, ], }, })) } const updatePauseLogEntry = (sessionId, pauseIndex, field, value) => { setEditingSessions(prev => { const updatedPauseLog = prev[sessionId].pauseLog.map((pause, index) => { if (index === pauseIndex) { const updatedPause = { ...pause, [field]: value } // Auto-calculate duration if both start and end are present if (updatedPause.start && updatedPause.end) { const startTime = new Date(updatedPause.start) const endTime = new Date(updatedPause.end) updatedPause.duration = Math.floor((endTime - startTime) / 1000) } return updatedPause } return pause }) return { ...prev, [sessionId]: { ...prev[sessionId], pauseLog: updatedPauseLog, }, } }) } const deletePauseLogEntry = (sessionId, pauseIndex) => { setEditingSessions(prev => ({ ...prev, [sessionId]: { ...prev[sessionId], pauseLog: prev[sessionId].pauseLog.filter( (_, index) => index !== pauseIndex, ), }, })) } const cancelEditingSession = sessionId => { setEditingSessions(prev => { // eslint-disable-next-line no-unused-vars const { [sessionId]: removed, ...rest } = prev return rest }) } const saveSession = async sessionId => { const editingData = editingSessions[sessionId] if (!editingData) return setLoading(true) try { // Use the auto-calculated duration from the editing session const updateData = { startTime: new Date(editingData.startTime).toISOString(), endTime: editingData.endTime ? new Date(editingData.endTime).toISOString() : null, duration: editingData.duration, pauseLog: editingData.pauseLog, } updateTimeSession.mutate( { choreId, sessionId, sessionData: updateData }, { onSuccess: () => { showSuccess({ title: 'Session updated', message: 'Timer session has been updated successfully.', }) refetchTimer() cancelEditingSession(sessionId) onTimerUpdate?.() }, onError: () => { showError({ title: 'Failed to update session', message: 'Please try again.', }) }, }, ) } catch (error) { showError({ title: 'Error updating session', message: error.message, }) } finally { setLoading(false) } } const deleteSession = async sessionId => { setLoading(true) deleteTimeSession.mutate( { choreId, sessionId }, { onSuccess: () => { showSuccess({ title: 'Session deleted', message: 'Timer session has been deleted successfully.', }) refetchTimer() onTimerUpdate?.() }, onError: error => { showError({ title: 'Error deleting session', message: error.message, }) }, onSettled: () => { setLoading(false) }, }, ) } const confirmDeleteSession = sessionId => { setConfirmDeleteConfig({ isOpen: true, title: 'Delete Timer Session', message: 'Are you sure you want to delete this timer session?', confirmText: 'Delete', cancelText: 'Cancel', color: 'danger', onClose: isConfirmed => { if (isConfirmed) { deleteSession(sessionId) } setConfirmDeleteConfig({}) setEditingSessions({}) onClose?.() }, }) } const handleClose = () => { setEditingSessions({}) onClose?.() } // Calculate total duration from start to now/end (real-time) const calculateTotalDuration = () => { if (!timerData) return 0 const startTime = new Date(timerData.startTime) const endTime = timerData.endTime ? new Date(timerData.endTime) : currentTime return Math.floor((endTime - startTime) / 1000) // in seconds } // Calculate current active duration (including ongoing session) (real-time) const calculateCurrentActiveDuration = () => { if (!timerData || !timerData.pauseLog) return 0 let totalActive = 0 const now = currentTime timerData.pauseLog.forEach(session => { if (session.start && session.end) { // Completed session totalActive += Math.floor( (new Date(session.end) - new Date(session.start)) / 1000, ) } else if (session.start && !session.end) { // Ongoing session - real-time calculation totalActive += Math.floor((now - new Date(session.start)) / 1000) } }) return totalActive } // Calculate idle time (total time minus active time) (real-time) const calculateIdleTime = () => { const totalDuration = calculateTotalDuration() const activeDuration = calculateCurrentActiveDuration() return Math.max(0, totalDuration - activeDuration) } return ( <> Timer Details {loading && ( Loading timer data... )} {!loading && !timerData && ( No timer data found for this chore. )} {!loading && timerData && ( {/* Timer Summary */} {/* Header with timeline */} {/* Stats Grid */} {/* Active Time */} Active Work {formatDuration(calculateCurrentActiveDuration())} {/* Idle Time */} Break Time {formatDuration(calculateIdleTime())} {/* Total Sessions */} Work Sessions {timerData.pauseLog?.length || 0} {/* Total Session Time */} Total Time {formatTime(calculateTotalDuration())} {/* Progress Bar */} Work vs Break Distribution {calculateCurrentActiveDuration() > 0 ? `${Math.round((calculateCurrentActiveDuration() / calculateTotalDuration()) * 100)}% active` : 'No active time yet'} {/* Time Session */} Session Breakdown {!editingSessions[timerData.id] ? ( {/* Read-only view */} {/* Sessions */} {timerData.pauseLog && timerData.pauseLog.length > 0 && ( Work Sessions ({timerData.pauseLog.length}) {timerData.pauseLog .sort((a, b) => moment(b.start) - moment(a.start)) .map((pause, pauseIndex) => { const isOngoing = !pause.end const sessionDate = moment(pause.start).format( 'MMM DD', ) const startTime = fmt.time(pause.start) const endTime = pause.end ? fmt.time(pause.end) : null const realTimeDuration = isOngoing ? Math.max( 0, Math.floor( (currentTime - new Date(pause.start)) / 1000, ), ) : pause.duration return ( {/* Session indicator */} {/* Duration - Main focus */} {formatDuration(realTimeDuration)} {isOngoing && ( Live )} {/* Session details */} Session #{pauseIndex + 1} • {sessionDate} {startTime}{' '} {endTime ? `→ ${endTime}` : '→ ongoing'} ) })} )} ) : ( {/* Editing view */} {/* Session Editor */} Sessions {editingSessions[timerData.id].pauseLog.map( (pause, pauseIndex) => ( Session #{pauseIndex + 1} Start Time updatePauseLogEntry( timerData.id, pauseIndex, 'start', new Date(e.target.value).toISOString(), ) } /> End Time updatePauseLogEntry( timerData.id, pauseIndex, 'end', e.target.value ? new Date( e.target.value, ).toISOString() : null, ) } /> Leave empty if session is ongoing Duration (Auto-calculated) {formatDuration(pause.duration)} ( {pause.duration}s) ), )} )} {!timerData && ( No timer session found for this chore. )} )} {/* Action buttons on the right */} {!loading && timerData && !editingSessions[timerData.id] && ( <> )} {/* Save button when editing */} {!loading && timerData && editingSessions[timerData.id] && ( )} ) } export default TimerEditModal