84 lines
4.3 KiB
Markdown
84 lines
4.3 KiB
Markdown
# Daily Zone Check-in
|
|
|
|
Workstream 16 — 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). Users
|
|
without a campus use UTC for the daily key, so organization- and school-scope
|
|
staff can still complete the same daily workflow.
|
|
|
|
## 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`)
|
|
|
|
Personal routes require explicit `ZONE_CHECKIN`; `globalAccess` alone does not
|
|
imply this workflow permission. The scoped completion route requires
|
|
`READ_ZONE_CHECKIN_REPORTS`.
|
|
|
|
- `GET /today` → `{ date, zone, isCheckedInToday }` (campus-local date).
|
|
- `POST /` → record today's zone. Body `{ data: { zone } }` (blue|green|yellow|red).
|
|
Parent-scope users acting through a drilled child scope receive `403 Forbidden`
|
|
instead of creating reportable rows in the child scope.
|
|
- `DELETE /today` → clear today's check-in.
|
|
- `GET /?from=&to=` → the caller's history (`{ rows: [{ date, zone }], count }`).
|
|
- `GET /completion` → scoped staff report for leaders with
|
|
`READ_ZONE_CHECKIN_REPORTS`; returns `{ summary, rows }` for today's server
|
|
computed dates per staff member.
|
|
|
|
## Authorization
|
|
|
|
- `ZONE_CHECKIN` is seeded for staff roles that must complete the daily workflow,
|
|
including organization, school, campus, and class scope staff (`owner`,
|
|
`superintendent`, `principal`, `registrar`, `director`, `office_manager`,
|
|
`teacher`, and `support_staff`). Users can receive or lose it only through
|
|
effective permissions (`custom_permissions` / `custom_permissions_filter`). The
|
|
frontend and backend both gate this workflow by permission, not by role name.
|
|
Reads/writes are scoped to the caller's own `userId` by `UserProgressService`.
|
|
- `READ_ZONE_CHECKIN_REPORTS` is seeded for scope leaders and report readers. The
|
|
completion report applies the caller's effective organization/school/campus/class
|
|
scope and includes pending rows for staff without today's check-in.
|
|
- Check-ins persist only when the active scope is the user's own scope. Parent users drilled into a
|
|
child school/campus/classroom do not see the personal check-in UI there, and the backend does not
|
|
create reportable `user_progress` rows for that child scope.
|
|
- Users with no campus use UTC for "today"; campus users use their campus
|
|
timezone.
|
|
|
|
## 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), top-bar notification builder (incl. the zones `href`),
|
|
profile unified result rows, and director-dashboard unified result/risk rows.
|
|
- **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.
|
|
|
|
## Content
|
|
|
|
Regulation zone cards and page copy are organization-preset content catalog
|
|
records (`regulation-zones` and `zones-of-regulation-page-content`). New
|
|
organizations receive those records from the content-catalog seeder/backfill; the
|
|
daily check-in result history remains in `user_progress`.
|
|
|
|
Existing deployments receive report grants through
|
|
`20260619070000-grant-zone-checkin-report-permission.ts` and the registrar
|
|
personal workflow grant through
|
|
`20260619071000-grant-registrar-zone-checkin-permission.ts`; both migrations are
|
|
idempotent.
|