diff --git a/backend/app/main.py b/backend/app/main.py index 1a26cbe..4bfb81a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -9,7 +9,7 @@ from app.config import get_settings from app.database import engine from app.models import Base from app.routers import auth, users, children, subjects, schedules, sessions, logs, dashboard -from app.routers import morning_routine +from app.routers import morning_routine, break_activity from app.websocket.manager import manager settings = get_settings() @@ -33,6 +33,8 @@ async def lifespan(app: FastAPI): 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", "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, "children", "strikes", "INT NOT NULL DEFAULT 0") await _add_column_if_missing(conn, "users", "timezone", "VARCHAR(64) NOT NULL DEFAULT 'UTC'") yield @@ -64,6 +66,7 @@ app.include_router(schedules.router) app.include_router(sessions.router) app.include_router(logs.router) app.include_router(morning_routine.router) +app.include_router(break_activity.router) app.include_router(dashboard.router) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 6c05717..17f2411 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -7,6 +7,7 @@ from app.models.schedule import ScheduleTemplate, ScheduleBlock from app.models.session import DailySession, TimerEvent, TimerEventType from app.models.activity import ActivityLog from app.models.morning_routine import MorningRoutineItem +from app.models.break_activity import BreakActivityItem from app.models.strike import StrikeEvent __all__ = [ @@ -23,5 +24,6 @@ __all__ = [ "TimerEventType", "ActivityLog", "MorningRoutineItem", + "BreakActivityItem", "StrikeEvent", ] diff --git a/backend/app/models/break_activity.py b/backend/app/models/break_activity.py new file mode 100644 index 0000000..1e429b5 --- /dev/null +++ b/backend/app/models/break_activity.py @@ -0,0 +1,15 @@ +from sqlalchemy import ForeignKey, Integer, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from app.models.base import Base + + +class BreakActivityItem(Base): + __tablename__ = "break_activity_items" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False) + text: Mapped[str] = mapped_column(Text, nullable=False) + order_index: Mapped[int] = mapped_column(Integer, default=0) + + user: Mapped["User"] = relationship("User") # noqa: F821 diff --git a/backend/app/models/schedule.py b/backend/app/models/schedule.py index ccd97ba..6cf4140 100644 --- a/backend/app/models/schedule.py +++ b/backend/app/models/schedule.py @@ -43,6 +43,8 @@ class ScheduleBlock(Base): label: Mapped[str | None] = mapped_column(String(100), nullable=True) # override subject name notes: Mapped[str | None] = mapped_column(Text, nullable=True) order_index: Mapped[int] = mapped_column(Integer, default=0) + break_time_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) + break_time_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True) template: Mapped["ScheduleTemplate"] = relationship("ScheduleTemplate", back_populates="blocks") subject: Mapped["Subject | None"] = relationship("Subject", back_populates="schedule_blocks") # noqa: F821 diff --git a/backend/app/routers/break_activity.py b/backend/app/routers/break_activity.py new file mode 100644 index 0000000..bbf57b2 --- /dev/null +++ b/backend/app/routers/break_activity.py @@ -0,0 +1,97 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from pydantic import BaseModel +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from app.dependencies import get_db, get_current_user +from app.models.break_activity import BreakActivityItem +from app.models.user import User + +router = APIRouter(prefix="/api/break-activities", tags=["break-activities"]) + + +class BreakActivityItemOut(BaseModel): + id: int + text: str + order_index: int + model_config = {"from_attributes": True} + + +class BreakActivityItemCreate(BaseModel): + text: str + order_index: int = 0 + + +class BreakActivityItemUpdate(BaseModel): + text: str | None = None + order_index: int | None = None + + +@router.get("", response_model=list[BreakActivityItemOut]) +async def list_items( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(BreakActivityItem) + .where(BreakActivityItem.user_id == current_user.id) + .order_by(BreakActivityItem.order_index, BreakActivityItem.id) + ) + return result.scalars().all() + + +@router.post("", response_model=BreakActivityItemOut, status_code=status.HTTP_201_CREATED) +async def create_item( + body: BreakActivityItemCreate, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + item = BreakActivityItem(user_id=current_user.id, text=body.text, order_index=body.order_index) + db.add(item) + await db.commit() + await db.refresh(item) + return item + + +@router.patch("/{item_id}", response_model=BreakActivityItemOut) +async def update_item( + item_id: int, + body: BreakActivityItemUpdate, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(BreakActivityItem).where( + BreakActivityItem.id == item_id, + BreakActivityItem.user_id == current_user.id, + ) + ) + item = result.scalar_one_or_none() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + if body.text is not None: + item.text = body.text + if body.order_index is not None: + item.order_index = body.order_index + await db.commit() + await db.refresh(item) + return item + + +@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_item( + item_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(BreakActivityItem).where( + BreakActivityItem.id == item_id, + BreakActivityItem.user_id == current_user.id, + ) + ) + item = result.scalar_one_or_none() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + await db.delete(item) + await db.commit() diff --git a/backend/app/routers/dashboard.py b/backend/app/routers/dashboard.py index b223111..354bb2d 100644 --- a/backend/app/routers/dashboard.py +++ b/backend/app/routers/dashboard.py @@ -12,6 +12,7 @@ from sqlalchemy.orm import selectinload from app.dependencies import get_db from app.models.child import Child from app.models.morning_routine import MorningRoutineItem +from app.models.break_activity import BreakActivityItem from app.models.schedule import ScheduleBlock, ScheduleTemplate from app.models.subject import Subject # noqa: F401 — needed for selectinload chain from app.models.session import DailySession, TimerEvent @@ -86,6 +87,13 @@ async def get_dashboard(child_id: int, db: AsyncSession = Depends(get_db)): ) morning_routine = [item.text for item in routine_result.scalars().all()] + break_result = await db.execute( + select(BreakActivityItem) + .where(BreakActivityItem.user_id == child.user_id) + .order_by(BreakActivityItem.order_index, BreakActivityItem.id) + ) + break_activities = [item.text for item in break_result.scalars().all()] + return DashboardSnapshot( session=session, child=child, @@ -96,4 +104,5 @@ async def get_dashboard(child_id: int, db: AsyncSession = Depends(get_db)): day_start_time=day_start_time, day_end_time=day_end_time, morning_routine=morning_routine, + break_activities=break_activities, ) diff --git a/backend/app/routers/sessions.py b/backend/app/routers/sessions.py index 1208342..5fa04a4 100644 --- a/backend/app/routers/sessions.py +++ b/backend/app/routers/sessions.py @@ -8,12 +8,13 @@ from sqlalchemy.orm import selectinload from app.dependencies import get_db, get_current_user from app.models.child import Child from app.models.morning_routine import MorningRoutineItem +from app.models.break_activity import BreakActivityItem from app.models.schedule import ScheduleBlock, ScheduleTemplate from app.models.subject import Subject # noqa: F401 — needed for selectinload chain from app.models.session import DailySession, TimerEvent from app.models.user import User from app.schemas.session import DailySessionOut, SessionStart, TimerAction -from app.utils.timer import compute_block_elapsed +from app.utils.timer import compute_block_elapsed, compute_break_elapsed from app.websocket.manager import manager router = APIRouter(prefix="/api/sessions", tags=["sessions"]) @@ -49,6 +50,8 @@ async def _broadcast_session(db: AsyncSession, session: DailySession) -> None: "duration_minutes": b.duration_minutes, "label": b.label, "order_index": b.order_index, + "break_time_enabled": b.break_time_enabled, + "break_time_minutes": b.break_time_minutes, } for b in blocks_result.scalars().all() ] @@ -82,6 +85,15 @@ async def _broadcast_session(db: AsyncSession, session: DailySession) -> None: ) morning_routine = [item.text for item in routine_result.scalars().all()] + break_activities: list[str] = [] + if child: + break_result = await db.execute( + select(BreakActivityItem) + .where(BreakActivityItem.user_id == child.user_id) + .order_by(BreakActivityItem.order_index, BreakActivityItem.id) + ) + break_activities = [item.text for item in break_result.scalars().all()] + payload = { "event": "session_update", "session": { @@ -96,6 +108,7 @@ async def _broadcast_session(db: AsyncSession, session: DailySession) -> None: "day_start_time": day_start_time, "day_end_time": day_end_time, "morning_routine": morning_routine, + "break_activities": break_activities, } await manager.broadcast(session.child_id, payload) @@ -185,6 +198,35 @@ async def timer_action( if not session: raise HTTPException(status_code=404, detail="Session not found") + # Break-time events are handled separately — they don't switch blocks or + # trigger implicit pauses. Just record the event and broadcast. + BREAK_EVENTS = {"break_start", "break_pause", "break_resume", "break_reset"} + if body.event_type in BREAK_EVENTS: + block_id = body.block_id or session.current_block_id + event = TimerEvent( + session_id=session.id, + block_id=block_id, + event_type=body.event_type, + ) + db.add(event) + await db.commit() + await db.refresh(session) + + break_elapsed_seconds = 0 + if body.event_type in ("break_start", "break_reset") and block_id: + break_elapsed_seconds, _ = await compute_break_elapsed(db, session.id, block_id) + + ws_payload = { + "event": body.event_type, + "session_id": session.id, + "block_id": block_id, + "current_block_id": session.current_block_id, + "is_active": session.is_active, + "break_elapsed_seconds": break_elapsed_seconds, + } + await manager.broadcast(session.child_id, ws_payload) + return session + # When switching to a different block (start / select / reset), implicitly # pause the previous block so the activity log stays accurate. prev_block_id = None diff --git a/backend/app/schemas/schedule.py b/backend/app/schemas/schedule.py index 448bdfb..3db0d29 100644 --- a/backend/app/schemas/schedule.py +++ b/backend/app/schemas/schedule.py @@ -11,6 +11,8 @@ class ScheduleBlockCreate(BaseModel): label: str | None = None notes: str | None = None order_index: int = 0 + break_time_enabled: bool = False + break_time_minutes: int | None = None class ScheduleBlockUpdate(BaseModel): @@ -21,6 +23,8 @@ class ScheduleBlockUpdate(BaseModel): label: str | None = None notes: str | None = None order_index: int | None = None + break_time_enabled: bool | None = None + break_time_minutes: int | None = None class ScheduleBlockOut(BaseModel): @@ -33,6 +37,8 @@ class ScheduleBlockOut(BaseModel): label: str | None notes: str | None order_index: int + break_time_enabled: bool + break_time_minutes: int | None model_config = {"from_attributes": True} diff --git a/backend/app/schemas/session.py b/backend/app/schemas/session.py index 54c7353..aea771d 100644 --- a/backend/app/schemas/session.py +++ b/backend/app/schemas/session.py @@ -47,3 +47,4 @@ class DashboardSnapshot(BaseModel): day_start_time: time | None = None day_end_time: time | None = None morning_routine: list[str] = [] # text items shown on TV during greeting state + break_activities: list[str] = [] # text items shown on TV during break time diff --git a/backend/app/utils/timer.py b/backend/app/utils/timer.py index e28120f..238b920 100644 --- a/backend/app/utils/timer.py +++ b/backend/app/utils/timer.py @@ -42,3 +42,36 @@ async def compute_block_elapsed( is_paused = bool(tick_events) and tick_events[-1].event_type == "pause" return int(elapsed), is_paused + + +async def compute_break_elapsed( + db: AsyncSession, session_id: int, block_id: int +) -> tuple[int, bool]: + """Return (break_elapsed_seconds, is_break_paused) for a block's break timer.""" + tick_result = await db.execute( + select(TimerEvent) + .where( + TimerEvent.session_id == session_id, + TimerEvent.block_id == block_id, + TimerEvent.event_type.in_(["break_start", "break_resume", "break_pause", "break_reset"]), + ) + .order_by(TimerEvent.occurred_at) + ) + tick_events = tick_result.scalars().all() + + elapsed = 0.0 + last_start = None + for e in tick_events: + if e.event_type == "break_reset": + elapsed = 0.0 + last_start = e.occurred_at + elif e.event_type in ("break_start", "break_resume"): + last_start = e.occurred_at + elif e.event_type == "break_pause" and last_start: + elapsed += (e.occurred_at - last_start).total_seconds() + last_start = None + if last_start: + elapsed += (datetime.utcnow() - last_start).total_seconds() + + is_paused = bool(tick_events) and tick_events[-1].event_type == "break_pause" + return int(elapsed), is_paused diff --git a/frontend/src/stores/schedule.js b/frontend/src/stores/schedule.js index 311391b..cc24299 100644 --- a/frontend/src/stores/schedule.js +++ b/frontend/src/stores/schedule.js @@ -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, } }) diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index ad05500..3bfe5e8 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -148,6 +148,37 @@ + +
+
+

Break Activities

+
+
+

These items appear in the Activities panel on the TV during break time.

+
+ +
No items yet.
+
+
+ + +
+
+
+
@@ -238,6 +269,17 @@ style="width:130px" /> + + @@ -248,6 +290,9 @@ {{ blockDurationLabel(block) }} + + ☕ {{ block.break_time_minutes ? `${block.break_time_minutes}min` : '' }} break +
@@ -271,6 +316,17 @@ style="width:130px" /> + + @@ -475,12 +531,48 @@ async function deleteRoutineItem(id) { await loadMorningRoutine() } +// Break Activities +const breakActivities = ref([]) +const newBreakText = ref('') +const editingBreakItem = ref(null) + +async function loadBreakActivities() { + const res = await api.get('/api/break-activities') + breakActivities.value = res.data +} + +async function addBreakItem() { + await api.post('/api/break-activities', { + text: newBreakText.value, + order_index: breakActivities.value.length, + }) + newBreakText.value = '' + await loadBreakActivities() +} + +function startEditBreakItem(item) { + editingBreakItem.value = { ...item } +} + +async function saveBreakItem() { + await api.patch(`/api/break-activities/${editingBreakItem.value.id}`, { + text: editingBreakItem.value.text, + }) + editingBreakItem.value = null + await loadBreakActivities() +} + +async function deleteBreakItem(id) { + await api.delete(`/api/break-activities/${id}`) + await loadBreakActivities() +} + // Schedules const templates = ref([]) const showCreateForm = ref(false) const editingTemplate = ref(null) const newTemplate = ref({ name: '', child_id: null, is_default: false }) -const newBlock = ref({ subject_id: null, time_start: '', time_end: '', duration_minutes: null, label: '', order_index: 0 }) +const newBlock = ref({ subject_id: null, time_start: '', time_end: '', duration_minutes: null, label: '', order_index: 0, break_time_enabled: false, break_time_minutes: null }) const editingBlock = ref(null) function childName(id) { @@ -540,7 +632,7 @@ async function addBlock(templateId) { order_index: templates.value.find((t) => t.id === templateId)?.blocks.length || 0, } await api.post(`/api/schedules/${templateId}/blocks`, payload) - newBlock.value = { subject_id: null, time_start: '', time_end: '', duration_minutes: null, label: '', order_index: 0 } + newBlock.value = { subject_id: null, time_start: '', time_end: '', duration_minutes: null, label: '', order_index: 0, break_time_enabled: false, break_time_minutes: null } await loadTemplates() } @@ -552,6 +644,8 @@ function startEditBlock(block) { time_end: block.time_end ? block.time_end.slice(0, 5) : '', duration_minutes: block.duration_minutes ?? null, label: block.label || '', + break_time_enabled: block.break_time_enabled || false, + break_time_minutes: block.break_time_minutes ?? null, } } @@ -577,7 +671,7 @@ async function saveDayHours(template, which, value) { onMounted(async () => { await childrenStore.fetchChildren() - await Promise.all([loadSubjects(), loadTemplates(), loadMorningRoutine()]) + await Promise.all([loadSubjects(), loadTemplates(), loadMorningRoutine(), loadBreakActivities()]) selectedTimezone.value = authStore.timezone }) @@ -824,4 +918,25 @@ h2 { font-size: 1.1rem; color: #94a3b8; text-transform: uppercase; letter-spacin .btn-sm.btn-danger { border-color: #7f1d1d; color: #fca5a5; } .btn-sm.btn-danger:hover { background: #7f1d1d; } .btn-primary.btn-sm { padding: 0.4rem 0.8rem; font-size: 0.85rem; } + +.break-badge { + font-size: 0.72rem; + background: #451a03; + color: #fdba74; + border: 1px solid #92400e; + padding: 0.15rem 0.5rem; + border-radius: 999px; + white-space: nowrap; +} + +.break-check-label { + display: flex; + align-items: center; + gap: 0.35rem; + font-size: 0.85rem; + color: #94a3b8; + cursor: pointer; + white-space: nowrap; +} +.break-check-label input[type="checkbox"] { cursor: pointer; } diff --git a/frontend/src/views/DashboardView.vue b/frontend/src/views/DashboardView.vue index 6157fa6..882a16b 100644 --- a/frontend/src/views/DashboardView.vue +++ b/frontend/src/views/DashboardView.vue @@ -72,6 +72,52 @@ :block-elapsed-offset="scheduleStore.blockElapsedOffset" /> + + +
+
+ + Break Time + {{ scheduleStore.currentBlock.break_time_minutes }} min +
+
+ +
+
+ + + + +
+
+
-
- {{ currentSubjectIcon }} {{ currentSubjectName }} -
- -
- {{ scheduleStore.currentBlock.notes }} -
+ + + +
- +
-
Activities
+ + -