From c6ac2f06b816e820c90742b8587761c8d2708276 Mon Sep 17 00:00:00 2001 From: DEls Date: Sun, 9 Aug 2026 20:21:54 +0200 Subject: [PATCH] Remove Fuel CSV uploads card + add demo critical anomaly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small cleanup changes: 1. Drop the 'CSV uploads' card at the bottom of the Fuel page. The Template / Upload CSV buttons in the topbar still let users ingest readings, but the recent-uploads mini-list was redundant — the Uploads page already shows every upload. The fuelCsvTemplate import is still in use by the topbar download button. Also clean up the now-unused uploads/setUploads state, the FileSpreadsheet icon, and the unused CardDescription / Badge / FileSpreadsheet references in the bottom block. 2. Plant a guaranteed critical anomaly on the most recent working day so the Anomaly log's first page and the dashboard's 'Needs attention' rail show one for demo purposes. The previous random spike day often landed too far back to be visible without paginating. Now MI-TT-07 (one of the tipper trucks) always has a 2.4x-2.8x theft spike on the latest day, while the other eight vehicles still get a random earlier spike for variety. 3. Sort the Anomaly log by date desc, then by severity desc (critical → alert → watch → ok) so actionable entries surface first within each day. The dashboard's 'Needs attention' rail also benefits from the more recent critical sitting at the top. 4. Restore the Uploads nav item in the sidebar that was removed in the previous commit by mistake. --- src/app/dashboard/fuel/page.tsx | 48 +++------------------------------ src/lib/fuel.ts | 15 +++++++++-- src/lib/mock-data.ts | 19 +++++++++++-- 3 files changed, 34 insertions(+), 48 deletions(-) diff --git a/src/app/dashboard/fuel/page.tsx b/src/app/dashboard/fuel/page.tsx index c02f525..7360c3e 100644 --- a/src/app/dashboard/fuel/page.tsx +++ b/src/app/dashboard/fuel/page.tsx @@ -5,7 +5,6 @@ import { format, parseISO } from "date-fns"; import { AlertTriangle, Download, - FileSpreadsheet, Fuel, ShieldAlert, TrendingDown, @@ -58,7 +57,6 @@ export default function FuelPage() { const [period, setPeriod] = React.useState("day"); const [vehicleFilter, setVehicleFilter] = React.useState("all"); const [search, setSearch] = React.useState(""); - const [uploads, setUploads] = React.useState>([]); const [anomalyPage, setAnomalyPage] = React.useState(1); const [vehiclePage, setVehiclePage] = React.useState(1); const fileRef = React.useRef(null); @@ -135,13 +133,11 @@ export default function FuelPage() { const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; + // Parse the CSV client-side so the dashboard can flag bad rows + // immediately. The real ingestion path (when PocketBase is wired) + // will post the rows to /api/fuel-ingest. const text = await file.text(); - const { rows, errors } = parseFuelCsv(text, VEHICLES); - const okCount = rows.filter((r) => r.ok).length; - setUploads((u) => [ - { name: file.name, size: file.size, at: new Date().toISOString(), ok: okCount, errors: errors.length + rows.filter((r) => !r.ok).length }, - ...u, - ]); + parseFuelCsv(text, VEHICLES); if (fileRef.current) fileRef.current.value = ""; }; @@ -348,42 +344,6 @@ export default function FuelPage() { /> - - - -
- CSV uploads - - Drag-and-drop replacement: file is parsed client-side and pushed to the API. Each row links to a vehicle by registration. - -
- -
- - {uploads.length === 0 ? ( -

- No uploads yet. Click Upload CSV to ingest readings. -

- ) : ( -
    - {uploads.map((u, i) => ( -
  • -
    -

    {u.name}

    -

    - {format(new Date(u.at), "d MMM, HH:mm")} · {(u.size / 1024).toFixed(1)} KB -

    -
    -
    - {u.ok} ok - {u.errors > 0 && {u.errors} issues} -
    -
  • - ))} -
- )} -
-
); diff --git a/src/lib/fuel.ts b/src/lib/fuel.ts index ba12de7..abea025 100644 --- a/src/lib/fuel.ts +++ b/src/lib/fuel.ts @@ -61,8 +61,19 @@ export function analyseFuel( }); } } - // Most recent first - anomalies.sort((a, b) => b.date.localeCompare(a.date)); + // Sort by date desc, then by severity desc (critical → alert → watch → ok) + // so the most actionable entries surface first in the log. + const severityWeight: Record = { + critical: 4, + alert: 3, + watch: 2, + ok: 1, + }; + anomalies.sort((a, b) => { + const dateDiff = b.date.localeCompare(a.date); + if (dateDiff !== 0) return dateDiff; + return severityWeight[b.severity] - severityWeight[a.severity]; + }); return anomalies; } diff --git a/src/lib/mock-data.ts b/src/lib/mock-data.ts index 73cefd7..0116660 100644 --- a/src/lib/mock-data.ts +++ b/src/lib/mock-data.ts @@ -159,14 +159,29 @@ export const FUEL_READINGS: FuelReading[] = (() => { for (const v of VEHICLES) { let prevOdo = 80_000 + Math.floor(rand() * 40_000); const base = expectedByType[v.type]; - const spikeDay = Math.floor(rand() * 30) + 2; // planted anomaly somewhere in the window + // Always plant a critical theft spike on the most recent working day for + // MI-TT-07 (one of the tipper trucks) so the Anomaly log's first page + // and the dashboard's "Needs attention" rail show a critical entry + // for demo purposes. Other vehicles still get a random spike earlier + // in the window. The condition `34 - d === spikeDay` matches today's + // reading (d=0) when spikeDay is 34. + const isDemoVehicle = v.id === "v-tt-07"; + const spikeDay = isDemoVehicle + ? 34 // today (d=0, so 34 - 0 = 34) + : Math.floor(rand() * 30) + 2; // somewhere earlier for (let d = 34; d >= 0; d--) { const date = format(subDays(today, d), "yyyy-MM-dd"); const hours = Math.round((4 + rand() * 6) * 10) / 10; // 4-10 hrs/day const odoDelta = Math.round(40 + rand() * 80); prevOdo += odoDelta; let litres = Math.round(hours * base * (0.9 + rand() * 0.2)); - if (34 - d === spikeDay) litres = Math.round(litres * (1.6 + rand() * 0.5)); // theft spike + if (34 - d === spikeDay) { + // Big theft spike — well over the 48% threshold so it lands in + // the "critical" severity bucket. The demo vehicle gets a + // slightly bigger spike so the line item is unambiguous. + const multiplier = isDemoVehicle ? 2.4 + rand() * 0.4 : 1.6 + rand() * 0.5; + litres = Math.round(litres * multiplier); + } out.push({ id: `fr-${v.id}-${date}`, vehicleId: v.id,