WFDASH-WEBAPP/docs/tv-dashboard.md
Dave 43bbdfc458 feat(tv-dashboard): enhance TV dashboard with driver stops and performance metrics
- Updated the TV dashboard to include a new section for driver stops, displaying active deliveries and pickups with GPS coordinates.
- Refactored the dashboard data structure to incorporate year-over-year performance metrics alongside existing monthly and weekly stats.
- Replaced the supplier map with a driver stops map for improved clarity on driver activities.
- Enhanced the user performance metrics to include detailed counts for quotes and orders processed by each representative.
- Removed the obsolete supplier map component to streamline the dashboard's functionality.
2026-08-19 10:16:49 +02:00

123 lines
No EOL
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TV Dashboard — install + use
PIN-gated office TV display showing live orders, quotes, customers, and
revenue from PocketBase.
## Quick start
1. Open `https://<your-coolify-host>/tv` from the TV browser (or any
browser pointed at the display).
2. Type the **6-digit PIN** on the on-screen keypad with the TV remote
(D-pad + Enter) or a wireless keyboard.
3. Dashboard lands on `/board` and stays unlocked for **12 hours**.
4. Tap **Lock** (top right) to bounce back to the PIN screen.
The PIN lives in `.env.local` as `TV_DASHBOARD_PIN`. The session
cookie is signed with `TV_SESSION_SECRET` so rotating that env var
invalidates every existing TV session (forces re-PIN).
## What it shows
| Section | Tiles |
|---|---|
| **Period matrix** | Today / Week / Month counts: orders received, orders placed, quotes received, items processed |
| **Driver stops** | Map + numbered list of the driver named by `TV_DRIVER_ID` — both active deliveries (`order_items.item_status = "assigned"`) and supplier pickups (`stock_purchase_items.status = "assigned_to_driver"`). Stops drop off as he marks them collected in the driver app. |
| **Rep activity** | Per-rep table with today / week / month × quotes / orders counts. Falls back to `customer.rep` when `quote.rep` is empty. |
The same `getDashboardData()` powers the main CRM `/dashboard`; the
TV view adds `getTvDashboardData()` on top with the day / week / month
counters, per-rep quotes, user performance, and driver stops.
## How it stays fresh
- **Initial fetch** on mount.
- **Polling** every 30s as a safety net.
- **PB realtime** — subscribes to `orders`, `quotes`, `order_items`,
and `customers`; any change triggers an immediate refetch.
## File map
```
src/app/(tv)/
layout.tsx dark shell, no sidebar
pin/page.tsx on-screen PIN keypad (auto-submit on 6 digits)
board/
page.tsx server guard → if no valid session, redirect to /pin
tv-view.tsx client component, all the rendering
components/
driver-stops-map.tsx numbered colour-coded pins per stop
tv-map-inner.tsx the actual Leaflet map (browser-only)
src/app/api/
tv/login/route.ts POST { pin } → sets tv_session cookie or 401
tv/logout/route.ts POST → clears cookie
dashboard/route.ts GET → public TV dashboard JSON (no auth)
src/lib/server/
tv-session.ts HMAC-signed cookie helpers
tv-dashboard-service.ts day/week/month counters, quotesPerRep,
userPerformance, driverStops
driver-stops.ts shared listDriverStops + update (driver app)
src/middleware.ts no-op (matcher in place for future)
```
## Setting up a new TV
1. Open the URL on the TV's browser.
2. Open Chrome's auto-start settings (or the equivalent on the TV's
browser) so the dashboard comes back automatically after a
reboot.
3. Disable screen savers / sleep — the dashboard will stay on
indefinitely.
## Troubleshooting
- **No data on the dashboard.** Confirm `PB_ADMIN_EMAIL` /
`PB_ADMIN_PASSWORD` are set in `.env.local` on the server. The
`/api/dashboard` route uses admin PB credentials to read
aggregated data.
- **"Invalid PIN" forever.** Check `TV_DASHBOARD_PIN` in
`.env.local` on the server. The PIN must match exactly
(case-sensitive, no spaces).
- **TV keeps asking for PIN every reload.** Sessions default to 168
hours (`TV_SESSION_HOURS`). Either bump it up or accept re-entry.
- **Stale data even though realtime is wired.** PB realtime requires
the page to be connected to the PB server. If the TV is behind
a corporate proxy that strips WebSockets, realtime is dead and
the 30s polling picks up the slack.
- **`/dashboard` (existing CRM) errored after deploying.** There
was a route collision — the TV view lives at `/board`, not
`/dashboard`, so the two don't clash.
- **Driver map shows no pins.** The map needs GPS coordinates on
each customer's `coordinates` geoPoint (or fall back to the
parent company's `coordinates`). Null Island `{0,0}` (PB's
default for un-geocoded rows) is dropped on purpose. Geocode
the customers via the main CRM and pins will appear.
- **Quotes per rep shows "Unassigned".** Quotes auto-fall back to
`customer.rep` when `quote.rep` is empty. If both are empty the
row groups under "Unassigned". Set the rep on the customer /
quote in the CRM to populate the table.
| Var | Purpose | Required |
|---|---|---|
| `TV_DASHBOARD_PIN` | 4-8 digit PIN staff enter on the TV | yes |
| `TV_SESSION_SECRET` | HMAC secret for signing the cookie | yes |
| `TV_SESSION_HOURS` | TTL (default 12, currently set to **168 = 1 week**) | no |
| `TV_DRIVER_ID` | PB `users.id` of the driver whose active stops appear on the stops tile. When unset the tile collapses to a "set TV_DRIVER_ID" placeholder so the rest of the dashboard still renders. | no |
| `TV_REP_ROLES` | Comma-separated `user_type` values that mark a user as a rep for the activity list (default `REP`). A user is counted as a rep when their `user_type` array contains any of these values. Example: `TV_REP_ROLES=REP,QUOTER` to also include the QUOTER role. | no |
| `PB_ADMIN_EMAIL` / `PB_ADMIN_PASSWORD` | PB admin auth for `/api/dashboard` | yes (reuses existing vars) |
Generate a session secret with:
```bash
node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"
```
## Out of scope (intentionally)
- No multi-screen layouts. Single screen tuned for 1080p.
- No clickable order rows. Read-only display; the office can click
through to the main CRM from a desktop.
- No custom dashboards per user. The TV shows the same things every
shift.