diff --git a/backend/app/main.py b/backend/app/main.py index bbba44f..bea932a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -32,6 +32,7 @@ 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, "children", "strikes", "INT NOT NULL DEFAULT 0") yield diff --git a/backend/app/models/child.py b/backend/app/models/child.py index 7883287..1d16d6e 100644 --- a/backend/app/models/child.py +++ b/backend/app/models/child.py @@ -1,5 +1,5 @@ from datetime import date -from sqlalchemy import String, Boolean, ForeignKey, Date +from sqlalchemy import String, Boolean, ForeignKey, Date, Integer from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base, TimestampMixin @@ -13,6 +13,7 @@ class Child(TimestampMixin, Base): birth_date: Mapped[date | None] = mapped_column(Date, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) color: Mapped[str] = mapped_column(String(7), default="#4F46E5") # hex color for UI + strikes: Mapped[int] = mapped_column(Integer, default=0, nullable=False) user: Mapped["User"] = relationship("User", back_populates="children") # noqa: F821 daily_sessions: Mapped[list["DailySession"]] = relationship( # noqa: F821 diff --git a/backend/app/routers/children.py b/backend/app/routers/children.py index 9033659..9a9507a 100644 --- a/backend/app/routers/children.py +++ b/backend/app/routers/children.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, Depends, HTTPException, status +from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select @@ -6,6 +7,7 @@ from app.dependencies import get_db, get_current_user from app.models.child import Child from app.models.user import User from app.schemas.child import ChildCreate, ChildOut, ChildUpdate +from app.websocket.manager import manager router = APIRouter(prefix="/api/children", tags=["children"]) @@ -70,6 +72,30 @@ async def update_child( return child +class StrikesBody(BaseModel): + strikes: int = Field(..., ge=0, le=3) + + +@router.patch("/{child_id}/strikes", response_model=ChildOut) +async def update_strikes( + child_id: int, + body: StrikesBody, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(Child).where(Child.id == child_id, Child.user_id == current_user.id) + ) + child = result.scalar_one_or_none() + if not child: + raise HTTPException(status_code=404, detail="Child not found") + child.strikes = body.strikes + await db.commit() + await db.refresh(child) + await manager.broadcast(child_id, {"event": "strikes_update", "strikes": child.strikes}) + return child + + @router.delete("/{child_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_child( child_id: int, diff --git a/backend/app/schemas/child.py b/backend/app/schemas/child.py index 3221356..baed2b9 100644 --- a/backend/app/schemas/child.py +++ b/backend/app/schemas/child.py @@ -1,5 +1,5 @@ from datetime import date -from pydantic import BaseModel +from pydantic import BaseModel, Field class ChildCreate(BaseModel): @@ -13,6 +13,7 @@ class ChildUpdate(BaseModel): birth_date: date | None = None color: str | None = None is_active: bool | None = None + strikes: int | None = Field(None, ge=0, le=3) class ChildOut(BaseModel): @@ -21,5 +22,6 @@ class ChildOut(BaseModel): birth_date: date | None is_active: bool color: str + strikes: int = 0 model_config = {"from_attributes": True} diff --git a/frontend/src/stores/schedule.js b/frontend/src/stores/schedule.js index 2fcef44..802e661 100644 --- a/frontend/src/stores/schedule.js +++ b/frontend/src/stores/schedule.js @@ -55,6 +55,10 @@ export const useScheduleStore = defineStore('schedule', () => { applySnapshot(event) return } + if (event.event === 'strikes_update') { + if (child.value) child.value = { ...child.value, strikes: event.strikes } + return + } // Session ended if (event.is_active === false) { session.value = null diff --git a/frontend/src/views/DashboardView.vue b/frontend/src/views/DashboardView.vue index 3e85ea7..62c31de 100644 --- a/frontend/src/views/DashboardView.vue +++ b/frontend/src/views/DashboardView.vue @@ -79,6 +79,28 @@ + +