40227-vm/backend/docs/zone-checkin.md
2026-06-12 10:56:13 +02:00

3.0 KiB

Daily Zone Check-in

Workstream 16 — campus staff log a daily self-regulation "zone" (Zones of Regulation: blue/green/yellow/red). History is retained per day, and an eligible user who has not checked in today is nudged on the dashboard, the /zones-of-regulation page, and in the notification dropdown.

"Today" is server-computed in the campus timezone

The check-in date is not decided by the client. Each campus carries a required IANA timezone (campuses.timezone); the service computes the campus-local date (localDateInTimezone, native Intl, DST-correct) so "today" is independent of the caller's device clock/zone and correct across organizations, campuses, and timezones.

Storage (reuses user_progress)

No new table. A check-in is a user_progress row with progress_type = zone_checkin and item_id = the campus-local date (YYYY-MM-DD), value = the zone color. Because the per-user upsert key is (userId, progress_type, item_id), this yields one row per user per day, and the set of rows is the history (item_id sorts chronologically). The thin services/zone-checkin.ts wraps UserProgressService and owns the timezone/date logic, keeping the generic user_progress endpoint generic.

Routes (/api/zone_checkins)

All require ZONE_CHECKIN (the four campus staff roles).

  • GET /today{ date, zone, isCheckedInToday } (campus-local date).
  • POST / → record today's zone. Body { data: { zone } } (blue|green|yellow|red).
  • DELETE /today → clear today's check-in.
  • GET /?from=&to= → the caller's history ({ rows: [{ date, zone }], count }).

Authorization

  • ZONE_CHECKINdirector (full access), office_manager (via ...MODULE_ACTIONS), teacher, support_staff (explicit grants). Other roles (owner/superintendent/student/guardian/system) are not granted it; the frontend also gates the nudge to the four campus roles (canZoneCheckIn). Reads/writes are scoped to the caller's own userId by UserProgressService.
  • A user with no campus has no campus-local "today" — the service rejects with a validation error (only campus staff reach these routes).

Tests

  • Unit (npm test): shared/constants/timezone.test.ts (campus-local date incl. Phoenix no-DST + a DST zone; isValidIanaTimezone) and shared/constants/zone-checkin.test.ts (isZoneCheckinColor).
  • Frontend unit (vitest): business/zone-checkin/selectors.test.ts (eligibility + nudge) and the top-bar notification builder (incl. the zones href).
  • Seeded e2e (frontend/tests/e2e/zone-checkins.seeded.e2e.ts, npm run test:e2e:content): a campus-staff record/read-back/clear of today's zone, invalid-zone rejection, and external-role lockout.

Open / deferred

  • A manager-facing aggregate (campus self-regulation trends across staff) would need a cross-user report endpoint (user_progress is self-scoped) — deferred.
  • Editing a campus timezone is part of the (design-gated) campus admin UI; for now it is seeded and validated at the API.