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
}
onClick={() => addPauseLogEntry(timerData.id)}
>
Add Session
{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] && (
<>
}
onClick={() => startEditingSession()}
>
Edit
>
)}
{/* Save button when editing */}
{!loading && timerData && editingSessions[timerData.id] && (
)}
>
)
}
export default TimerEditModal