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

@@ -72,6 +72,52 @@
:block-elapsed-offset="scheduleStore.blockElapsedOffset"
/>
</div>
<!-- Break Time section -->
<div
v-if="scheduleStore.currentBlock?.break_time_enabled"
class="break-section"
:class="{ 'break-active': scheduleStore.isBreakMode }"
>
<div class="break-header">
<span class="break-icon"></span>
<span class="break-title">Break Time</span>
<span class="break-duration-badge">{{ scheduleStore.currentBlock.break_time_minutes }} min</span>
</div>
<div v-if="scheduleStore.isBreakMode" class="break-timer-display">
<TimerDisplay
compact
:block="breakBlock"
:session="scheduleStore.session"
:is-paused="!scheduleStore.breakStartedAt"
:block-started-at="scheduleStore.breakStartedAt"
:block-elapsed-offset="scheduleStore.breakElapsedOffset"
/>
</div>
<div class="break-actions">
<button
class="btn-sm btn-break"
v-if="!scheduleStore.isBreakMode"
@click="scheduleStore.startBreak(scheduleStore.session.id)"
>Start Break</button>
<button
class="btn-sm btn-break"
v-if="scheduleStore.isBreakMode && scheduleStore.breakStartedAt"
@click="scheduleStore.pauseBreak(scheduleStore.session.id)"
>Pause</button>
<button
class="btn-sm btn-break"
v-if="scheduleStore.isBreakMode && !scheduleStore.breakStartedAt && scheduleStore.breakElapsedOffset > 0"
@click="scheduleStore.resumeBreak(scheduleStore.session.id)"
>Resume</button>
<button
class="btn-sm"
v-if="scheduleStore.isBreakMode"
@click="scheduleStore.resetBreak(scheduleStore.session.id)"
>Reset</button>
</div>
</div>
<div class="session-actions">
<div class="session-actions-left">
<button
@@ -161,6 +207,13 @@ import TimerDisplay from '@/components/TimerDisplay.vue'
const childrenStore = useChildrenStore()
const scheduleStore = useScheduleStore()
const activeChild = computed(() => childrenStore.activeChild)
// Virtual block for break timer (same block but with break duration)
const breakBlock = computed(() => {
const block = scheduleStore.currentBlock
if (!block?.break_time_enabled) return null
return { ...block, duration_minutes: block.break_time_minutes }
})
const showStartDialog = ref(false)
const selectedTemplate = ref(null)
const templates = ref([])
@@ -333,6 +386,34 @@ h1 { font-size: 1.75rem; font-weight: 700; }
.btn-sm.btn-start { border-color: #4f46e5; color: #818cf8; }
.btn-sm.btn-start:hover { background: #4f46e5; color: #fff; }
.break-section {
margin: 0.75rem 0;
background: #1c1207;
border: 1px solid #78350f;
border-radius: 0.75rem;
padding: 0.75rem 1rem;
}
.break-section.break-active { border-color: #f59e0b; background: #1c1a07; }
.break-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.break-icon { font-size: 1rem; }
.break-title { font-size: 0.8rem; font-weight: 600; color: #fbbf24; text-transform: uppercase; letter-spacing: 0.06em; flex: 1; }
.break-duration-badge {
font-size: 0.75rem;
background: #78350f;
color: #fde68a;
padding: 0.1rem 0.45rem;
border-radius: 999px;
}
.break-timer-display { display: flex; justify-content: flex-start; margin-bottom: 0.5rem; }
.break-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.btn-break { border-color: #92400e !important; color: #fbbf24 !important; }
.btn-break:hover { background: #78350f !important; }
.no-session { text-align: center; padding: 1.5rem 0; color: #64748b; }
.no-session p { margin-bottom: 1rem; }