WFDASH-WEBAPP/docs/tv-dashboard.md

123 lines
5.6 KiB
Markdown
Raw Permalink Normal View History

# 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.