# FleetNow A single-file map console that **merges live vehicle positions and historical trips** into one view for the Fireside Communications / Tracksolid fleet. > **Status:** v2 — live at . Deployed from this > repo via Coolify (Dockerfile → nginx). Reads the `dashboard_api` read-API at > `fleetapi.rahamafresh.com`. > > **2026-06-08 additions:** fleet segmentation (specialist vehicle icons that > never cluster), per-department colour coordination + collapsible **Key** legend, > persistent POIs (Fireside HQ, Safaricom HQ), and a **toggleable map-overlay > layer system** (first layer: 232 Shell fuel stations). See *Map overlay layers*. > > **Deploy auto-fires on push to `main`** via Coolify (~2–3 min build); if it > lags, hit Redeploy in Coolify. ## What it does - Land on the **live fleet** — markers carry the **department (cost-centre) colour**, a heading arrow, the plate tail, and a hover popup (status, driver, reverse-geocoded address, heading, odometer, last fix, source). Markers **scale with the zoom level**. Shape encodes recency so stakeholders read activity at a glance: - **● circle** (full colour + heading arrow) — moving right now - **■ square** (pastel colour, no arrow, ~half the size of a moving marker) — active within the last 24h, now stopped - **grey ●** — offline (no fix in 24h) - **One pin per vehicle (tracker + camera dedup).** Every vehicle carries a GPS **tracker** (X3 / GT06E / AT4) *and* a **dashcam** (JC400P) that share the same number plate. FleetNow collapses the pair to a single marker/dropdown entry: the **tracker is primary**; if the tracker isn't reporting (>24h), it **falls back to the camera**; if both report, the tracker wins. Pairing is by normalised plate, so a stray space (`KDS 453 Y` vs `KDS 453Y`) still merges. - **Clustering (zoomed out).** Vehicles group into amber count-bubbles (Folium / Leaflet.MarkerCluster style, via `supercluster`); click a bubble to zoom and expand it. Clusters disband into individual pins at ~city zoom (z11). Clustering honours the active filter and applies to the live view only (not trip routes). - **Fleet segmentation & department colours.** Specialist vehicles (crane / motorbike / pick-up) get their own marker icons and are **never clustered** (always individually visible); every cost centre has a fixed, distinct colour, with a collapsible **Key** legend (bottom-left) listing only the centres on screen. Non-operational vehicles (personal / management / Uganda-MTN) are filtered out upstream in the read-API, so the live map shows the operational fleet only. - **Persistent POIs** — Fireside HQ and Safaricom HQ (Waiyaki Way) as labelled reference markers. - **Filters** (bottom-right card) apply to the live map *instantly*: - **Number plate** — multi-select, sorted A→Z; picking a vehicle auto-fills its cost centre + city. - **Cost centre** and **Assigned city** — narrow the live fleet (and the KPI bar recomputes to match). - **Time** (Today / 1 week / 1 month / Custom) — applies to **trips**, not live. - Drill into history: click a vehicle's dot → **Show trips**, or set plate / cost centre / city + period and hit **Show trips** for a fleet-wide pull. The map switches to **seq-coloured trip routes** with start/end markers and a click-to-animate replay; the **● Live** pill returns to the live snapshot. - In trips view a **context bar** (below the header) summarises the active filter (vehicle / cost centre / city) plus the **first trip** and **last trip** — each with its **reverse-geocoded location and timestamp** — alongside the KPI totals (trips, km, driving/idle hours, vehicles, drivers, date range). - **Full-width map + two-tier bottom dock (no floating/side panels).** All controls live in a bottom dock with two tiers: a **filter tier** on top and a **trip-card tier** beneath (selection → results hierarchy). In **live** mode the filter row is expanded and there are no cards; in **trips** mode the filters **collapse to a one-line summary** (`Filters: KCA 542Q · roll out · nairobi · Last 1 month`, with **Edit** to expand) and the trip cards show beneath — keeping the map tall. - **Plate picker is a searchable combobox** — type to filter, click to add a removable chip (multi-select), instead of a tall scrolling list. Cost centre / city / time stay single-line; date pickers appear only for a custom range. Trip cards scroll horizontally; click a card to fit + animate that route. Live: ## Architecture The whole app is **one self-contained `index.html`** (inline CSS + JS; MapLibre GL JS loaded from a CDN). It has no build step and no local assets. It reads from the existing dashboard read-API — it does **not** talk to the database directly. ``` index.html → the entire SPA layers/*.geojson → static overlay data (gas stations, …), served at /layers/ Dockerfile → bakes index.html + layers/ into an nginx:alpine image (port 80) nginx.conf → static serve + /healthz + no-cache on index.html ``` ### Backend it depends on `const API_BASE = 'https://fleetapi.rahamafresh.com';` (top of the `