- 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.
5.6 KiB
5.6 KiB
TV Dashboard — install + use
PIN-gated office TV display showing live orders, quotes, customers, and revenue from PocketBase.
Quick start
- Open
https://<your-coolify-host>/tvfrom the TV browser (or any browser pointed at the display). - Type the 6-digit PIN on the on-screen keypad with the TV remote (D-pad + Enter) or a wireless keyboard.
- Dashboard lands on
/boardand stays unlocked for 12 hours. - 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, andcustomers; 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
- Open the URL on the TV's browser.
- Open Chrome's auto-start settings (or the equivalent on the TV's browser) so the dashboard comes back automatically after a reboot.
- Disable screen savers / sleep — the dashboard will stay on indefinitely.
Troubleshooting
- No data on the dashboard. Confirm
PB_ADMIN_EMAIL/PB_ADMIN_PASSWORDare set in.env.localon the server. The/api/dashboardroute uses admin PB credentials to read aggregated data. - "Invalid PIN" forever. Check
TV_DASHBOARD_PINin.env.localon 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
coordinatesgeoPoint (or fall back to the parent company'scoordinates). 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.repwhenquote.repis 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:
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.