Add break time feature to schedule blocks

- Admin: per-block "Break Time" checkbox + duration (min) setting; new
  Break Activities section (global list, same pattern as Morning Routine)
- Dashboard: break timer section appears on blocks with break enabled;
  Start/Pause/Resume/Reset controls work independently of the main timer
- TV: left column switches to amber break badge + countdown during break;
  center column shows configurable Break Activities list
- Backend: break_time_enabled/break_time_minutes columns on schedule_blocks
  (auto-migrated on startup); break_activity_items table + CRUD router;
  break timer events (break_start/pause/resume/reset) stored as TimerEvents
  and broadcast via WebSocket; break_activities included in dashboard
  snapshot and session_update broadcast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 08:40:49 -08:00
parent 13f3e08744
commit 87315b8902
14 changed files with 578 additions and 24 deletions

View File

@@ -14,6 +14,12 @@ export const useScheduleStore = defineStore('schedule', () => {
const dayStartTime = ref(null) // "HH:MM:SS" string or null
const dayEndTime = ref(null) // "HH:MM:SS" string or null
const morningRoutine = ref([]) // list of text strings shown during greeting state
const breakActivities = ref([]) // list of text strings shown during break time
// Break timer state (per-block break time at end of block)
const isBreakMode = ref(false) // currently in break time
const breakStartedAt = ref(null) // Date.now() ms when break counting started
const breakElapsedOffset = ref(0) // break seconds already elapsed
const breakElapsedCache = ref({}) // blockId → total break elapsed seconds
const currentBlock = computed(() =>
session.value?.current_block_id
@@ -42,6 +48,7 @@ export const useScheduleStore = defineStore('schedule', () => {
dayStartTime.value = snapshot.day_start_time || null
dayEndTime.value = snapshot.day_end_time || null
morningRoutine.value = snapshot.morning_routine || []
breakActivities.value = snapshot.break_activities || []
// Restore elapsed time from server-computed value and seed the per-block cache
const serverElapsed = snapshot.block_elapsed_seconds || 0
if (snapshot.session?.current_block_id) {
@@ -54,6 +61,11 @@ export const useScheduleStore = defineStore('schedule', () => {
blockElapsedOffset.value = 0
blockStartedAt.value = null
}
// Reset break state on snapshot (not persisted across page loads)
isBreakMode.value = false
breakStartedAt.value = null
breakElapsedOffset.value = 0
breakElapsedCache.value = {}
}
function applyWsEvent(event) {
@@ -76,6 +88,39 @@ export const useScheduleStore = defineStore('schedule', () => {
blockElapsedCache.value = {}
dayStartTime.value = null
dayEndTime.value = null
isBreakMode.value = false
breakStartedAt.value = null
breakElapsedOffset.value = 0
breakElapsedCache.value = {}
return
}
// Break timer events
if (event.event === 'break_start') {
const elapsed = event.break_elapsed_seconds ?? breakElapsedCache.value[event.block_id] ?? 0
breakElapsedOffset.value = elapsed
if (event.block_id) breakElapsedCache.value[event.block_id] = elapsed
breakStartedAt.value = Date.now()
isBreakMode.value = true
}
if (event.event === 'break_pause') {
if (breakStartedAt.value) {
breakElapsedOffset.value += Math.floor((Date.now() - breakStartedAt.value) / 1000)
}
if (event.block_id) breakElapsedCache.value[event.block_id] = breakElapsedOffset.value
breakStartedAt.value = null
isBreakMode.value = true
}
if (event.event === 'break_resume') {
breakStartedAt.value = Date.now()
isBreakMode.value = true
}
if (event.event === 'break_reset') {
if (event.block_id) breakElapsedCache.value[event.block_id] = 0
breakElapsedOffset.value = 0
breakStartedAt.value = Date.now()
isBreakMode.value = true
}
if (['break_start', 'break_pause', 'break_resume', 'break_reset'].includes(event.event)) {
return
}
// Pause — accumulate elapsed, save to cache, stop counting
@@ -101,6 +146,12 @@ export const useScheduleStore = defineStore('schedule', () => {
}
blockStartedAt.value = Date.now()
isPaused.value = false
// Switching to a new block clears break mode
if (event.block_id !== event.current_block_id || !isBreakMode.value) {
isBreakMode.value = false
breakStartedAt.value = null
breakElapsedOffset.value = breakElapsedCache.value[event.block_id] ?? 0
}
}
// Reset — clear elapsed to 0 and start counting immediately
if (event.event === 'reset') {
@@ -121,6 +172,10 @@ export const useScheduleStore = defineStore('schedule', () => {
if (event.block_id) blockElapsedCache.value[event.block_id] = elapsed
blockStartedAt.value = null
isPaused.value = true
// Switching blocks clears break mode
isBreakMode.value = false
breakStartedAt.value = null
breakElapsedOffset.value = breakElapsedCache.value[event.block_id] ?? 0
}
// Resume — continue from where we left off
if (event.event === 'resume') {
@@ -212,6 +267,41 @@ export const useScheduleStore = defineStore('schedule', () => {
sendTimerAction(sessionId, 'start', session.value.current_block_id)
}
// Break timer actions
function startBreak(sessionId) {
if (!session.value?.current_block_id) return
const blockId = session.value.current_block_id
isBreakMode.value = true
breakElapsedOffset.value = breakElapsedCache.value[blockId] ?? 0
breakStartedAt.value = Date.now()
sendTimerAction(sessionId, 'break_start', blockId)
}
function pauseBreak(sessionId) {
if (!session.value?.current_block_id) return
if (breakStartedAt.value) {
breakElapsedOffset.value += Math.floor((Date.now() - breakStartedAt.value) / 1000)
}
breakStartedAt.value = null
sendTimerAction(sessionId, 'break_pause', session.value.current_block_id)
}
function resumeBreak(sessionId) {
if (!session.value?.current_block_id) return
breakStartedAt.value = Date.now()
sendTimerAction(sessionId, 'break_resume', session.value.current_block_id)
}
function resetBreak(sessionId) {
if (!session.value?.current_block_id) return
const blockId = session.value.current_block_id
breakElapsedCache.value[blockId] = 0
breakElapsedOffset.value = 0
breakStartedAt.value = Date.now()
isBreakMode.value = true
sendTimerAction(sessionId, 'break_reset', blockId)
}
// Reset the current block's timer to 0 and start counting immediately.
function resetCurrentBlock(sessionId) {
if (!session.value?.current_block_id) return
@@ -235,6 +325,11 @@ export const useScheduleStore = defineStore('schedule', () => {
dayStartTime,
dayEndTime,
morningRoutine,
breakActivities,
isBreakMode,
breakStartedAt,
breakElapsedOffset,
breakElapsedCache,
currentBlock,
progressPercent,
applySnapshot,
@@ -246,5 +341,9 @@ export const useScheduleStore = defineStore('schedule', () => {
selectBlock,
startCurrentBlock,
resetCurrentBlock,
startBreak,
pauseBreak,
resumeBreak,
resetBreak,
}
})