Remove school day hours from schedule templates

The day progress bar no longer uses day start/end times (it uses block
durations instead), so the field is no longer needed.

Removed from: Admin UI, schedule store, schedule model/schemas/router,
session broadcast payload, dashboard snapshot, and startup migrations.
DB columns are left in place (harmless, no migration required).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 13:45:05 -08:00
parent c05543d855
commit c565c94a23
9 changed files with 3 additions and 93 deletions

View File

@@ -30,8 +30,6 @@ async def lifespan(app: FastAPI):
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
# Idempotent column additions for schema migrations # Idempotent column additions for schema migrations
await _add_column_if_missing(conn, "schedule_templates", "day_start_time", "TIME NULL")
await _add_column_if_missing(conn, "schedule_templates", "day_end_time", "TIME NULL")
await _add_column_if_missing(conn, "schedule_blocks", "duration_minutes", "INT NULL") await _add_column_if_missing(conn, "schedule_blocks", "duration_minutes", "INT NULL")
await _add_column_if_missing(conn, "schedule_blocks", "break_time_enabled", "TINYINT(1) NOT NULL DEFAULT 0") await _add_column_if_missing(conn, "schedule_blocks", "break_time_enabled", "TINYINT(1) NOT NULL DEFAULT 0")
await _add_column_if_missing(conn, "schedule_blocks", "break_time_minutes", "INT NULL") await _add_column_if_missing(conn, "schedule_blocks", "break_time_minutes", "INT NULL")

View File

@@ -14,8 +14,6 @@ class ScheduleTemplate(TimestampMixin, Base):
) )
name: Mapped[str] = mapped_column(String(100), nullable=False) name: Mapped[str] = mapped_column(String(100), nullable=False)
is_default: Mapped[bool] = mapped_column(Boolean, default=False) is_default: Mapped[bool] = mapped_column(Boolean, default=False)
day_start_time: Mapped[time | None] = mapped_column(Time, nullable=True)
day_end_time: Mapped[time | None] = mapped_column(Time, nullable=True)
user: Mapped["User"] = relationship("User", back_populates="schedule_templates") # noqa: F821 user: Mapped["User"] = relationship("User", back_populates="schedule_templates") # noqa: F821
child: Mapped["Child | None"] = relationship("Child") # noqa: F821 child: Mapped["Child | None"] = relationship("Child") # noqa: F821

View File

@@ -13,7 +13,7 @@ from app.dependencies import get_db
from app.models.child import Child from app.models.child import Child
from app.models.morning_routine import MorningRoutineItem from app.models.morning_routine import MorningRoutineItem
from app.models.break_activity import BreakActivityItem from app.models.break_activity import BreakActivityItem
from app.models.schedule import ScheduleBlock, ScheduleTemplate from app.models.schedule import ScheduleBlock
from app.models.subject import Subject # noqa: F401 — needed for selectinload chain from app.models.subject import Subject # noqa: F401 — needed for selectinload chain
from app.models.session import DailySession, TimerEvent from app.models.session import DailySession, TimerEvent
from app.schemas.session import DashboardSnapshot from app.schemas.session import DashboardSnapshot
@@ -45,8 +45,6 @@ async def get_dashboard(child_id: int, db: AsyncSession = Depends(get_db)):
blocks = [] blocks = []
completed_ids = [] completed_ids = []
block_elapsed_seconds = 0 block_elapsed_seconds = 0
day_start_time = None
day_end_time = None
if session and session.template_id: if session and session.template_id:
blocks_result = await db.execute( blocks_result = await db.execute(
@@ -57,14 +55,6 @@ async def get_dashboard(child_id: int, db: AsyncSession = Depends(get_db)):
) )
blocks = blocks_result.scalars().all() blocks = blocks_result.scalars().all()
template_result = await db.execute(
select(ScheduleTemplate).where(ScheduleTemplate.id == session.template_id)
)
template = template_result.scalar_one_or_none()
if template:
day_start_time = template.day_start_time
day_end_time = template.day_end_time
events_result = await db.execute( events_result = await db.execute(
select(TimerEvent).where( select(TimerEvent).where(
TimerEvent.session_id == session.id, TimerEvent.session_id == session.id,
@@ -101,8 +91,6 @@ async def get_dashboard(child_id: int, db: AsyncSession = Depends(get_db)):
completed_block_ids=completed_ids, completed_block_ids=completed_ids,
block_elapsed_seconds=block_elapsed_seconds, block_elapsed_seconds=block_elapsed_seconds,
is_paused=is_paused, is_paused=is_paused,
day_start_time=day_start_time,
day_end_time=day_end_time,
morning_routine=morning_routine, morning_routine=morning_routine,
break_activities=break_activities, break_activities=break_activities,
) )

View File

@@ -44,8 +44,6 @@ async def create_template(
name=body.name, name=body.name,
child_id=body.child_id, child_id=body.child_id,
is_default=body.is_default, is_default=body.is_default,
day_start_time=body.day_start_time,
day_end_time=body.day_end_time,
) )
db.add(template) db.add(template)
await db.flush() # get template.id before adding blocks await db.flush() # get template.id before adding blocks

View File

@@ -9,7 +9,7 @@ from app.dependencies import get_db, get_current_user
from app.models.child import Child from app.models.child import Child
from app.models.morning_routine import MorningRoutineItem from app.models.morning_routine import MorningRoutineItem
from app.models.break_activity import BreakActivityItem from app.models.break_activity import BreakActivityItem
from app.models.schedule import ScheduleBlock, ScheduleTemplate from app.models.schedule import ScheduleBlock
from app.models.subject import Subject # noqa: F401 — needed for selectinload chain from app.models.subject import Subject # noqa: F401 — needed for selectinload chain
from app.models.session import DailySession, TimerEvent from app.models.session import DailySession, TimerEvent
from app.models.user import User from app.models.user import User
@@ -23,8 +23,6 @@ router = APIRouter(prefix="/api/sessions", tags=["sessions"])
async def _broadcast_session(db: AsyncSession, session: DailySession) -> None: async def _broadcast_session(db: AsyncSession, session: DailySession) -> None:
"""Build a snapshot dict and broadcast it to all connected TVs for this child.""" """Build a snapshot dict and broadcast it to all connected TVs for this child."""
blocks = [] blocks = []
day_start_time = None
day_end_time = None
if session.template_id: if session.template_id:
blocks_result = await db.execute( blocks_result = await db.execute(
@@ -56,14 +54,6 @@ async def _broadcast_session(db: AsyncSession, session: DailySession) -> None:
for b in blocks_result.scalars().all() for b in blocks_result.scalars().all()
] ]
template_result = await db.execute(
select(ScheduleTemplate).where(ScheduleTemplate.id == session.template_id)
)
template = template_result.scalar_one_or_none()
if template:
day_start_time = str(template.day_start_time) if template.day_start_time else None
day_end_time = str(template.day_end_time) if template.day_end_time else None
# Gather completed block IDs from timer events # Gather completed block IDs from timer events
events_result = await db.execute( events_result = await db.execute(
select(TimerEvent).where( select(TimerEvent).where(
@@ -105,8 +95,6 @@ async def _broadcast_session(db: AsyncSession, session: DailySession) -> None:
}, },
"blocks": blocks, "blocks": blocks,
"completed_block_ids": completed_ids, "completed_block_ids": completed_ids,
"day_start_time": day_start_time,
"day_end_time": day_end_time,
"morning_routine": morning_routine, "morning_routine": morning_routine,
"break_activities": break_activities, "break_activities": break_activities,
} }

View File

@@ -47,8 +47,6 @@ class ScheduleTemplateCreate(BaseModel):
name: str name: str
child_id: int | None = None child_id: int | None = None
is_default: bool = False is_default: bool = False
day_start_time: time | None = None
day_end_time: time | None = None
blocks: list[ScheduleBlockCreate] = [] blocks: list[ScheduleBlockCreate] = []
@@ -56,8 +54,6 @@ class ScheduleTemplateUpdate(BaseModel):
name: str | None = None name: str | None = None
child_id: int | None = None child_id: int | None = None
is_default: bool | None = None is_default: bool | None = None
day_start_time: time | None = None
day_end_time: time | None = None
class ScheduleTemplateOut(BaseModel): class ScheduleTemplateOut(BaseModel):
@@ -65,8 +61,6 @@ class ScheduleTemplateOut(BaseModel):
name: str name: str
child_id: int | None child_id: int | None
is_default: bool is_default: bool
day_start_time: time | None
day_end_time: time | None
blocks: list[ScheduleBlockOut] = [] blocks: list[ScheduleBlockOut] = []
model_config = {"from_attributes": True} model_config = {"from_attributes": True}

View File

@@ -1,4 +1,4 @@
from datetime import date, datetime, time from datetime import date, datetime
from pydantic import BaseModel from pydantic import BaseModel
from app.schemas.schedule import ScheduleBlockOut from app.schemas.schedule import ScheduleBlockOut
from app.schemas.child import ChildOut from app.schemas.child import ChildOut
@@ -44,7 +44,5 @@ class DashboardSnapshot(BaseModel):
completed_block_ids: list[int] = [] completed_block_ids: list[int] = []
block_elapsed_seconds: int = 0 # seconds already elapsed for the current block block_elapsed_seconds: int = 0 # seconds already elapsed for the current block
is_paused: bool = False # whether the current block's timer is paused is_paused: bool = False # whether the current block's timer is paused
day_start_time: time | None = None
day_end_time: time | None = None
morning_routine: list[str] = [] # text items shown on TV during greeting state morning_routine: list[str] = [] # text items shown on TV during greeting state
break_activities: list[str] = [] # text items shown on TV during break time break_activities: list[str] = [] # text items shown on TV during break time

View File

@@ -11,8 +11,6 @@ export const useScheduleStore = defineStore('schedule', () => {
const blockStartedAt = ref(null) // Date.now() ms when current counting period started const blockStartedAt = ref(null) // Date.now() ms when current counting period started
const blockElapsedOffset = ref(0) // seconds already elapsed before blockStartedAt const blockElapsedOffset = ref(0) // seconds already elapsed before blockStartedAt
const blockElapsedCache = ref({}) // blockId → total elapsed seconds (survives block switches) const blockElapsedCache = ref({}) // blockId → total elapsed seconds (survives block switches)
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 morningRoutine = ref([]) // list of text strings shown during greeting state
const breakActivities = ref([]) // list of text strings shown during break time const breakActivities = ref([]) // list of text strings shown during break time
// Break timer state (per-block break time at end of block) // Break timer state (per-block break time at end of block)
@@ -45,8 +43,6 @@ export const useScheduleStore = defineStore('schedule', () => {
completedBlockIds.value = snapshot.completed_block_ids || [] completedBlockIds.value = snapshot.completed_block_ids || []
isPaused.value = snapshot.is_paused || false isPaused.value = snapshot.is_paused || false
if (snapshot.child) child.value = snapshot.child if (snapshot.child) child.value = snapshot.child
dayStartTime.value = snapshot.day_start_time || null
dayEndTime.value = snapshot.day_end_time || null
morningRoutine.value = snapshot.morning_routine || [] morningRoutine.value = snapshot.morning_routine || []
breakActivities.value = snapshot.break_activities || [] breakActivities.value = snapshot.break_activities || []
// Restore elapsed time from server-computed value and seed the per-block cache // Restore elapsed time from server-computed value and seed the per-block cache
@@ -86,8 +82,6 @@ export const useScheduleStore = defineStore('schedule', () => {
blockStartedAt.value = null blockStartedAt.value = null
blockElapsedOffset.value = 0 blockElapsedOffset.value = 0
blockElapsedCache.value = {} blockElapsedCache.value = {}
dayStartTime.value = null
dayEndTime.value = null
isBreakMode.value = false isBreakMode.value = false
breakStartedAt.value = null breakStartedAt.value = null
breakElapsedOffset.value = 0 breakElapsedOffset.value = 0
@@ -322,8 +316,6 @@ export const useScheduleStore = defineStore('schedule', () => {
blockStartedAt, blockStartedAt,
blockElapsedOffset, blockElapsedOffset,
blockElapsedCache, blockElapsedCache,
dayStartTime,
dayEndTime,
morningRoutine, morningRoutine,
breakActivities, breakActivities,
isBreakMode, isBreakMode,

View File

@@ -229,24 +229,6 @@
<!-- Block editor --> <!-- Block editor -->
<div v-if="editingTemplate === template.id" class="block-editor"> <div v-if="editingTemplate === template.id" class="block-editor">
<!-- Day hours -->
<div class="day-hours-row">
<span class="day-hours-label">School day hours</span>
<input
type="time"
:value="template.day_start_time ? template.day_start_time.slice(0,5) : ''"
@change="e => saveDayHours(template, 'start', e.target.value)"
placeholder="Start"
/>
<span class="day-hours-sep">to</span>
<input
type="time"
:value="template.day_end_time ? template.day_end_time.slice(0,5) : ''"
@change="e => saveDayHours(template, 'end', e.target.value)"
placeholder="End"
/>
</div>
<div class="block-list"> <div class="block-list">
<template v-for="block in sortedBlocks(template.blocks)" :key="block.id"> <template v-for="block in sortedBlocks(template.blocks)" :key="block.id">
<!-- Edit mode --> <!-- Edit mode -->
@@ -661,13 +643,6 @@ async function deleteBlock(templateId, blockId) {
await loadTemplates() await loadTemplates()
} }
async function saveDayHours(template, which, value) {
const payload = which === 'start'
? { day_start_time: value || null }
: { day_end_time: value || null }
await api.patch(`/api/schedules/${template.id}`, payload)
await loadTemplates()
}
onMounted(async () => { onMounted(async () => {
await childrenStore.fetchChildren() await childrenStore.fetchChildren()
@@ -762,25 +737,6 @@ h2 { font-size: 1.1rem; color: #94a3b8; text-transform: uppercase; letter-spacin
.block-editor { margin-top: 1.25rem; border-top: 1px solid #334155; padding-top: 1.25rem; } .block-editor { margin-top: 1.25rem; border-top: 1px solid #334155; padding-top: 1.25rem; }
.day-hours-row {
display: flex;
align-items: center;
gap: 0.6rem;
margin-bottom: 1rem;
background: #0f172a;
padding: 0.6rem 0.85rem;
border-radius: 0.5rem;
}
.day-hours-label { font-size: 0.8rem; color: #64748b; flex: 1; }
.day-hours-sep { font-size: 0.8rem; color: #475569; }
.day-hours-row input[type="time"] {
padding: 0.35rem 0.5rem;
background: #1e293b;
border: 1px solid #334155;
border-radius: 0.4rem;
color: #f1f5f9;
font-size: 0.85rem;
}
.block-list { display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 1rem; } .block-list { display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 1rem; }
.block-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem; background: #0f172a; border-radius: 0.5rem; } .block-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem; background: #0f172a; border-radius: 0.5rem; }
.block-time { font-size: 0.8rem; color: #64748b; font-variant-numeric: tabular-nums; } .block-time { font-size: 0.8rem; color: #64748b; font-variant-numeric: tabular-nums; }