diff --git a/src/views/Modals/Inputs/TimerEditModal.jsx b/src/views/Modals/Inputs/TimerEditModal.jsx
new file mode 100644
index 0000000..eb395ad
--- /dev/null
+++ b/src/views/Modals/Inputs/TimerEditModal.jsx
@@ -0,0 +1,870 @@
+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 FadeModal from '../../../components/common/FadeModal'
+import { useNotification } from '../../../service/NotificationProvider'
+import {
+ DeleteTimeSession,
+ GetChoreTimer,
+ UpdateTimeSession,
+} from '../../../utils/Fetcher'
+import ConfirmationModal from './ConfirmationModal'
+
+const TimerEditModal = ({ isOpen, onClose, choreId, onTimerUpdate }) => {
+ 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()
+
+ // Fetch timer data when modal opens
+ useEffect(() => {
+ if (isOpen && choreId) {
+ fetchTimerData()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isOpen, choreId])
+
+ // 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 fetchTimerData = async () => {
+ setLoading(true)
+ try {
+ const response = await GetChoreTimer(choreId)
+ if (response.ok) {
+ const data = await response.json()
+ setTimerData(data.res) // data.res is the timer session object
+ } else {
+ showError({
+ title: 'Failed to fetch timer data',
+ message: 'Please try again.',
+ })
+ }
+ } catch (error) {
+ showError({
+ title: 'Error fetching timer data',
+ message: error.message,
+ })
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ 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,
+ }
+
+ const response = await UpdateTimeSession(choreId, sessionId, updateData)
+ if (response.ok) {
+ showSuccess({
+ title: 'Session updated',
+ message: 'Timer session has been updated successfully.',
+ })
+ await fetchTimerData()
+ cancelEditingSession(sessionId)
+ onTimerUpdate?.()
+ } else {
+ 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)
+ try {
+ const response = await DeleteTimeSession(choreId, sessionId)
+ if (response.ok) {
+ showSuccess({
+ title: 'Session deleted',
+ message: 'Timer session has been deleted successfully.',
+ })
+ await fetchTimerData()
+ onTimerUpdate?.()
+ } else {
+ showError({
+ title: 'Failed to delete session',
+ message: 'Please try again.',
+ })
+ }
+ } catch (error) {
+ showError({
+ title: 'Error deleting session',
+ message: error.message,
+ })
+ } finally {
+ 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 */}
+
+
+ {formatDuration(calculateCurrentActiveDuration())}
+
+
+ Active Work
+
+
+
+ {/* Idle Time */}
+
+
+ {formatDuration(calculateIdleTime())}
+
+
+ Break Time
+
+
+
+ {/* Total Sessions */}
+
+
+ {timerData.pauseLog?.length || 0}
+
+
+ Work Sessions
+
+
+
+ {/* Total Session Time */}
+
+
+ {formatTime(calculateTotalDuration())}
+
+
+ Total Time
+
+
+
+
+ {/* 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 = moment(pause.start).format(
+ 'HH:mm',
+ )
+ const endTime = pause.end
+ ? moment(pause.end).format('HH:mm')
+ : 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