62 lines
2.8 KiB
Markdown
62 lines
2.8 KiB
Markdown
# Daily Zone Check-in Integration
|
|
|
|
## Purpose
|
|
|
|
Campus staff log a daily self-regulation "Emotional Zone" (blue/green/yellow/red).
|
|
The same state drives three surfaces: the dashboard check-in card, the
|
|
`/zones-of-regulation` page (reminder banner + card), and a notification-dropdown
|
|
nudge when an eligible user has not checked in today.
|
|
|
|
## Backend contract
|
|
|
|
`/api/zone_checkins` (requires `ZONE_CHECKIN` — the four campus staff roles). The
|
|
client never computes the date; "today" is the campus-local date computed
|
|
server-side from `campuses.timezone`.
|
|
|
|
- `GET /today` → `{ date, zone, isCheckedInToday }`
|
|
- `POST /` `{ data: { zone } }` → record today's zone (upsert)
|
|
- `DELETE /today` → clear today's zone
|
|
- `GET /?from=&to=` → history `{ rows: [{ date, zone }], count }`
|
|
|
|
## Frontend Structure
|
|
|
|
- API/types: `shared/api/zoneCheckins.ts`, `shared/types/zoneCheckins.ts`
|
|
- Business: `business/zone-checkin/hooks.ts` (`useTodayZoneCheckIn`,
|
|
`useZoneCheckInHistory`), `business/zone-checkin/selectors.ts`
|
|
(`canZoneCheckIn`, `shouldNudgeZoneCheckIn`)
|
|
- Components: `components/zone-checkin/ZoneCheckInCard.tsx` (shared card),
|
|
`ZoneCheckInReminder.tsx` (banner), `ZoneCheckInSection.tsx` (page section)
|
|
|
|
## Behavior
|
|
|
|
- **Eligibility/nudge gating** is role-based (`canZoneCheckIn` — the four campus
|
|
staff roles), mirroring the backend grant. The dashboard card and the zones-page
|
|
section render only for eligible roles; the nudge (red "Not checked in" badge,
|
|
reminder banner, and notification) shows when an eligible user hasn't checked in
|
|
today.
|
|
- **Dashboard**: the card is wired through `useDashboardPage` (which exposes
|
|
`showZoneCheckIn` + `needsZoneCheckIn`) with an optimistic shell value for snappy
|
|
selection.
|
|
- **Zones page**: `ZoneCheckInSection` is self-contained (`useTodayZoneCheckIn`)
|
|
and renders above the regulation content.
|
|
- **Notifications**: `business/top-bar` derives a single unread notification from
|
|
`shouldNudgeZoneCheckIn` (`buildTopBarNotifications`) — there is no backend
|
|
notifications store. The notification carries an `href`
|
|
(`APP_ROUTE_PATHS.zones`); clicking it navigates to `/zones-of-regulation`
|
|
(a react-router `Link`) and closes the dropdown.
|
|
- The `useTodayZoneCheckIn` `error` surfaces **only** save/clear failures; the
|
|
today-load query can 403 for an ineligible caller and must not render as an
|
|
error in the widget. React Query dedupes the `/today` fetch across all three
|
|
surfaces.
|
|
|
|
## Tests
|
|
|
|
- `business/zone-checkin/selectors.test.ts` (eligibility + nudge),
|
|
`business/top-bar/selectors.test.ts` (notification builder + zones `href`).
|
|
- Seeded e2e: `frontend/tests/e2e/zone-checkins.seeded.e2e.ts` (record /
|
|
read-back / clear today, invalid-zone rejection, external-role lockout).
|
|
|
|
## Verification
|
|
|
|
- `npm run typecheck`, `npm run lint`, `npm run test` pass.
|