Add full PIN-protected TV dashboard stack including auth, redirects, map view, APIs, and server services. Also include these critical refinements: - Overhaul PO number generation to use per-company monthly sequences - Rework quote rejection to bounce quotes back to operator queues instead of marking them terminal - Add post_processing order tab group for clearer workflow visibility - Fix collection pool tooltip positioning, overflow clipping, and default collapsed state - Refactor customer list API, clean up unused form props, and fix item intel popover target ID handling - Add comprehensive tests for new components and update existing test expectations
125 lines
No EOL
5.3 KiB
Markdown
125 lines
No EOL
5.3 KiB
Markdown
# 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 |
|
|
|---|---|
|
|
| **Existing KPIs** | Orders this month · Active quotes · Customers |
|
|
| **Today / Week / Month strips** | Orders received · Orders placed (POs) · Quotes received · Items processed |
|
|
| **Driver map + stops list** | Today's `order_items` in `collection_pool` / `assigned` / `ready_delivery`, with the customer / company geoPoint used as the map pin |
|
|
| **Quotes loaded per rep** | Per-rep table with `today` · `week` · `month` columns. Falls back to `customer.rep` when `quote.rep` is empty. |
|
|
| **Today's rep activity** | Per-rep strip with Q (quotes today), O (orders today), ✓ (order_items completed today) |
|
|
| **Rotating chart** | Revenue / Orders / Quotes — 8s rotation |
|
|
| **Top customers** | Quotes per customer in the 12-week sample |
|
|
| **Follow-ups** | First 8 quotes with a non-empty `follow_up_due` |
|
|
|
|
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-map.tsx ssr:false wrapper around Leaflet
|
|
driver-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 the new day/week/month counters,
|
|
quotesPerRep, userPerformance, driverStops
|
|
|
|
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 |
|
|
| `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. |