The Aug 2026 pump sheet from The Hills (and any future sheet from
the same source) uses different column names and a different date
format than the legacy CSV the import route was built for. Adapts
the route so the same endpoint accepts either Excel (.xlsx) or CSV,
maps the new column names onto the existing FUEL_LOGS schema, and
persists their 'Latest Consumption' column verbatim.
What's new
• SheetJS (xlsx) replaces PapaParse for parsing. Single code path
handles both .xlsx and .csv uploads.
• Resolves columns case-insensitively against an alias table so
legacy CSVs (vehicle_description, odo_reading) still work
alongside the new shape (Date Logged, Vehicle QR, Km's/ Hours,
Litres Pumped, Current Site, Latest Consumption).
• Date parser: DD/MM/YYYY first (with leading-space tolerance),
falls back to YYYY-MM-DD, then to Excel serial numbers.
• Vehicle match: QR code first (clean identifier in the source
data), falls back to description text for legacy CSV.
• Site match: by sites.name (one round-trip, cached for the
import). project_code is derived from the resolved site; falls
back to the vehicle's project_code.
• Latest Consumption persisted as a new FUEL_LOGS field
(latest_consumption, number, optional). Empty source cells
stay empty rather than getting coerced to 0.
PocketBase change
• scripts/migrations/2026-08-24-fuel-logs-latest-consumption.py
adds the new field. Idempotent; follows the pattern of the
2026-08-19 migration. Run it with PB_ADMIN_EMAIL +
PB_ADMIN_PASSWORD before importing the new sheets, or the
import will fail with 'latest_consumption is not a field'.
UI
• Import button now accepts .xlsx and CSV; SAMPLE_HEADERS +
SAMPLE_OPTIONAL show the new column expectations and the
DD/MM/YYYY date format hint.
• fuel-view.tsx shows a 'Consumption (src)' column on the row
table so operators can eyeball what their fuel team sent
against the app's recomputed variance.
Out of scope (intentionally untouched)
• vehicles, sites, data_import_batches and every other
collection — no schema changes there.
• PO / timesheets / delivery-notes imports.
• fuel/page.tsx business logic (variance calc, vehicle summaries).
Verified
• Dry-run against 'The Hills Diesel 1-24 August.xlsx': 134/134
rows parse cleanly, 0 errors. 12/134 rows correctly carry an
empty Latest Consumption (matches the source).
• vitest: 20/20 fuel-import-parse + 35/35 PO-schema tests pass.
• tsc --noEmit clean.
• One pre-existing failure in
src/app/api/purchase-orders/process-batch/route.test.ts
unrelated to fuel; confirmed failing on main before this change.
|
||
|---|---|---|
| .opencode/openwork/inbox/chat-attachments | ||
| docs | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.local.example | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| cooler.env | ||
| COOLIFY.md | ||
| deployment-vptid1uo0jnz2q4t1mcbqjj9-all-logs-2026-08-19-00-23-06.txt | ||
| docker-compose.yml | ||
| Dockerfile | ||
| next.config.js | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
MEGA INFRASTRUCTURE — Operations Dashboard
A Next.js 16 + TypeScript + Tailwind v4 dashboard for a construction company. Tracks fuel consumption per hour/day/week, delivery notes & outstanding items per purchase order, and weekly workforce hours with attendance flags.
The project is frontend-first with mock data, ready to be wired up to PocketBase (or any backend) for live data.
Modules
| Path | What it does |
|---|---|
/login |
Numeric PIN gate. PIN lives in NEXT_PUBLIC_APP_PIN. Append ?demo=1 to any URL to skip the PIN during development. |
/dashboard |
Operations overview — KPI cards, fuel chart, order status donut, "needs attention" rail, top suppliers. |
/dashboard/fuel |
Per-vehicle consumption, theft detection, CSV ingest (papaparse), anomaly log. |
/dashboard/orders |
Purchase-order table with filters, pagination, sort. Click any PO to see line items, delivery notes, outstanding items. |
/dashboard/employees |
Weekly hours per employee with progress bar, overtime flag, role/site/attendance filters, mini daily-hours chart. |
APIs
GET /api/health— counts and site list.GET /api/mcp?module=all|fuel|orders|employees— compact JSON for an AI agent / MCP server / n8n workflow.POST /api/upload— multipart upload of PDF / image / CSV (delivery notes, fuel attachments). 20 MB limit per file.
PWA
The app is installable on phone and desktop. Service worker is generated by
@ducanh2912/next-pwa at build time (public/sw.js), manifest at
public/manifest.json, icons at public/icons/.
Note:
@ducanh2912/next-pwauses the Webpack plugin, sonext devandnext buildmust run with--webpack. Next 16 ships Turbopack by default; thepackage.jsonscripts already pass--webpack.
Run
cp .env.local.example .env.local # change NEXT_PUBLIC_APP_PIN
npm install
npm run dev # http://localhost:3000
Default PIN is 2468.
Production
npm run build
npm run start
How the theft detection works
src/lib/fuel.ts defines EXPECTED_LITRES_TOLERANCE_PCT = 12. For every
reading we compute:
expectedLitres = hoursWorked * expectedConsumptionPerHour
deltaPct = (litresFilled - expectedLitres) / expectedLitres * 100
Severity buckets:
| Over expected | Severity |
|---|---|
| ≤ 12 % | ok |
| 12 – 24 % | watch |
| 24 – 48 % | alert |
| > 48 % | critical |
Adjust EXPECTED_LITRES_TOLERANCE_PCT to suit on-site practice.
CSV format for fuel upload
Required columns:
vehicleRegistration,date,hoursWorked,odometer,litresFilled,unitPrice,expectedConsumptionPerHour
MI-EX-01,2026-08-01,9.5,12480,90,24.50,9
The "Template" button on the Fuel page downloads a populated example.
Wiring a backend
Replace the imports in src/app/dashboard/*/page.tsx and
src/app/api/mcp/route.ts with calls into PocketBase / your API. The shape
of every type is in src/lib/types.ts.
Recommended PocketBase collections:
vehicles(registration, type, model, tankCapacityL, assignedSite)fuel_readings(vehicleId, date, hoursWorked, odometer, litresFilled, unitPrice, expectedConsumptionPerHour, attachment?)orders(reference, supplier, site, status, priority, placedAt, expectedAt, totalValue, notes)order_lines(orderId, description, quantityOrdered, quantityDelivered, unit, unitPrice)delivery_notes(orderId, scannedBy, file, mimeType, receivedAt)employees(fullName, role, site, status, phone, hourlyRate, weeklyTargetHours)daily_hours(employeeId, date, hours)