WFDASH-WEBAPP/docs/tv-dashboard.md
Dave 43de97501c feat(tv): add TV dashboard + refine workflows
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
2026-08-13 20:55:22 +02:00

5.3 KiB

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:

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.