Commit graph

112 commits

Author SHA1 Message Date
DEls
34017d8630 chore(tooling): ignore foreman in lint, relax script rules, force NODE_ENV=test in vitest
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:12:57 +02:00
DEls
18fb7ef99d feat(surveying): Maps move to a Surveying section; Sites is opt-in for site managers
Surveying → Maps is open to every dashboard user, scoped to their sites.
Sites (contracts and profitability) shows for admins, and for site managers
only when "Can see Sites" is ticked on their account. Old map links redirect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:31:53 +02:00
DEls
9f0fce988e feat(cash-floats): petty-cash cards on users, one log, clock Slips tab
Each cash float card lives on its holder's user record (card number, last
4-6 digits of the physical card, low limit, assigned date). Every movement
goes in one cash_float_log collection: opening, top-up, slip, handover and
correction rows with signed amounts. Balances are always summed from the log.

- Management -> Cash Floats: cards table (lowest balance first), add, edit,
  top up, hand over, correct balance, retire, movements sheet with slip
  photos and CSV export. Admin-only.
- Settings -> Users: card section in the edit form and the balance per holder.
- Clock app: Slips tab, shown only to a user who holds a card. Photo, site,
  supplier and amount; the slip comes off that user's own card.
- Sidebar needs-attention mark when a card is under its limit.
- Slips count as site spend ("Cash float slips" on the site page).
- Migration scripts/migrations/2026-10-24-cash-floats.py (already applied to
  production on 2026-10-24).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:17:06 +02:00
DEls
706d3bbd63 feat(workforce): payroll export, labour cost by site, all-sites totals in the site matrix
Adds an all-sites payroll (Excel/PDF/CSV, one row per worker, per-site split and
site wage cost), a period-selectable labour cost by site table on the Workforce
dashboard, and each worker's all-sites total plus site wage cost in the
timesheet matrix and its exports.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 07:00:48 +02:00
DEls
2933251b2e fix(site-map): Map 3 Progress tab Show in 3D flew to a blank spot
The section's height was passed as 0, so the camera target sat far below the map. Use the design surface height, and make the camera fly-to fall back to the surface height when given a wrong one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 05:43:57 +02:00
DEls
d6cf3d1144 feat(site-map): Map 3 Progress tab showing pavement-layer progress by section
Strips the map back to the road and tints each section by how many of the chosen build layers are done, with done/shown labels, a layer filter, a per-section checklist and a link into the tick-off popup.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 05:11:08 +02:00
DEls
5141f7783a feat(site-map): Cedar Gate stormwater and sewer pipes and manholes from the long sections
Extract 23 stormwater structures / 22 pipes and 18 sewer manholes / 17 pipes with sizes, inverts, ground levels and positions from the C3277 long-section tables (superseded sewer copies dropped by matching the issued PDFs). New model folder 2026-10-21. Map 3 draws them as round pipes and manholes, adds status popups for pipes and manholes, and reuses Map 2's services panel; water stays plan routes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 05:04:42 +02:00
DEls
1a67125b61 feat(site-map): Map 3 with ground survey, cut & fill, progress glow and service routes
New /dashboard/sites/[id]/map3: a 3D map forked from Map 2 (Map 1 and 2 untouched) with ground heat map, contours, cut & fill from the Chris survey, a longitudinal profile, yellow/green progress glow, a Services tab for stormwater/sewer/water routes, and status popups on earthworks strips and routes. Pure logic and tests in src/lib/site-map-v3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 04:43:56 +02:00
DEls
cb4a1ce461 feat(site-map): Maps page with previews, double-click progress popup, Cedar Gate cut & fill
- Sites → Maps in the sidebar replaces the per-site Site map / Map 2 links.
  The Maps page shows each site's model as a plan preview (built sections
  solid) with % of road layerworks built and links to Site map and Map 2.
- Double-clicking a road section opens its progress tick-boxes over the map
  (Site map saves to the server; Map 2 stays a local scenario).
- Cedar Gate: 40 drawn cross-sections extracted from C3277-912-001 Rev A
  (extract_cedar_gate_xs.py), cut/fill limited to Cedar Gate Road, a Cut &
  fill workspace in Map 2 with a chainage chart, and pavement layers shown
  in their material colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:55:51 +02:00
DEls
0dfb129696 feat: update management comparisons and Cedar Gate site model
Add planned vs actual turnover dashboards, monthly actual costs, editable retention forecasts and actuals, starting retention, payouts and running balances. Preserve manual values across workbook imports and include retention backfill tooling.

Add Cedar Gate model extraction and reports, measured service routes and ground surfaces, plus site-map section totals and quantity refinements.
2026-10-20 12:23:44 +02:00
DEls
a96f35366d feat(management): store planning data in PocketBase with editable income, add job and forecast vs actual chart
Move the Management planning snapshots into PocketBase collections
(plans, projects, per-year project plans, retention, edit log), loaded
with a bundled-JSON fallback. Admins can double-click an income cell to
enter forecast and actual, edit awarded jobs' planned amounts, change
status and costs, and add jobs, with revision checks and an audit trail.
Income chart now shows forecast and invoiced bars with not-yet-started
money stacked in red; Income is in the sidebar. Adds schema and import
scripts, and jsdom/Testing Library dev dependencies for UI tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 03:04:01 +02:00
DEls
0857e56cb3 fix(workforce): let site managers add and rename employers and brokers
The employer and broker API routes were never named in the path
classifier, so they fell through to its admin-only default and the
proxy rejected a site manager with a bare {"error":"Not allowed"}
before the route handler ran. The handler's own site scoping was
always correct — it just never got the chance to apply.

- classify /api/employers as dashboard and map it to the workforce
  module, mirroring /api/employees
- let the site dropdown be chosen when adding an employer; only a
  rename freezes the site, which preserves assignment history
- cover the rename path, which had no route test, and the proxy
  gate for the site-manager role
2026-10-19 16:33:41 +02:00
DEls
0a7138f60c fix: harden workforce, plant and financials
Improve workforce scanning, site transfers and timesheet exports.
Audit plant imports, duplicate identities, rates and dashboard totals.
Repair delivery PO selection, periodic rechecks and receiving guards.
Align financial ledgers, supplier periods and mobile layouts.
Add the site map preview, shared theme controls and audit evidence.

Verify 3,455 application tests, six fuel export tests, TypeScript and
an optimized production build. Clean up a leaked test timer and keep
local worktrees and generated reports out of ESLint traversal.

Changed-file lint passes; repository-wide lint retains 33 existing
errors in unchanged files.
2026-10-19 13:18:31 +02:00
DEls
4c4106fe8b fix(dev): prevent production service workers from controlling local builds 2026-10-16 07:13:39 +02:00
DEls
e01bb31c8c feat(ui): simplify dashboard table controls 2026-10-15 19:57:09 +02:00
DEls
757861c293 feat(workforce): simplify toolbar and add workers 2026-10-15 16:17:12 +02:00
DEls
bb2d15a39b feat(workforce): add employer and broker management 2026-10-15 15:58:06 +02:00
DEls
828e859455 feat(site-map): full screen map for phones and tablets
Full screen button fills the screen with the map (and asks the browser for real
full screen where allowed; iPhone falls back to filling the page). Tapping a road
section, pipe, peg etc. shows a floating card so progress can be ticked without
leaving full screen; section tick-offs moved into a shared SectionDetail component.
Exit with the button or Esc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:24:45 +02:00
DEls
03394c8005 feat(site-map): tablet touch navigation
One finger slides the map, pinch zooms smoothly towards the fingers, two-finger
drag turns and tilts, double-tap zooms in, tap selects (10 px tolerance for fingers).
On touch screens the arrow pad is hidden and the hint shows the gestures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:14:53 +02:00
DEls
ab20d681b2 fix(site-map): review fixes — batches of 50, survey CSV locales, save recovery
- write PocketBase batches in chunks of at most 50 (production limit)
- survey import: refuse files with no rows, write peg events before the import record
- read en-ZA Excel CSVs (decimal comma), quoted fields, CR line endings; no crash without pegs
- save queue: reset on model reload, drop 400-refused items instead of blocking,
  keep queued values on screen when a server state arrives, flush on leaving the page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:56:05 +02:00
DEls
0b04613ae1 feat(site-map): Site map entry on the site page and in the sidebar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:31:24 +02:00
DEls
3770830e86 feat(site-map): progress, services, earthworks, signs, rain and accuracy panels
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:29:35 +02:00
DEls
de727e7129 feat(site-map): map page with layers panel, navigation pad and saving 2026-10-14 07:19:54 +02:00
DEls
f583814776 feat(site-map): services, earthworks, signs and rain layers; client save queue 2026-10-14 07:15:46 +02:00
DEls
220ca7bc45 feat(site-map): three.js scene engine, navigation and road layers 2026-10-14 07:12:32 +02:00
DEls
4493bbc0db feat(clock): pay against the real work schedule
Mon-Thu 07:00-16:30, Friday 07:00-15:00, last Friday of the month
07:00-12:00 (weekends use the Mon-Thu bands). Each shift end has a
10-minute band either side; arrivals up to 07:10 pay from 07:00.
Lunch now phases in from 5h to 5h30 instead of a 30-minute cliff, so
a normal day pays 9 / 7.5 / 5 hours.

The worker calendar judges each day against that day's expected hours,
so a normal Friday is green, and the worker page shows the schedule.
The recalc script recognises rows already rewritten by the first rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:51:24 +02:00
DEls
75b178dd33 feat(clock): store paid hours, not the raw scan span
A scan in up to 07:10 pays from 07:00; a scan out between 16:20 and
16:40 pays to 16:30; a session over five hours loses a 30-minute lunch.
A normal day is therefore exactly 9 hours (green on the calendar and
matrix); late arrivals, early exits and overtime use the real times.
Applied on clock-out, in missed-scan corrections and their preview.
The calendar keeps the raw scan times and labels hours "Paid hours".

Includes a dry-run-first script to recalculate existing QR entries; it
only rewrites rows still equal to their raw scan total.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:01:17 +02:00
DEls
b32ad0bc8b feat(clock): fix a missing clock-in from Needs attention
When nobody scanned a worker in, the evening scan was stored as the IN
and the correction dialog only allowed a leaving time after it. The
dialog now shows an editable arrival and leaving time, with a one-click
"this scan was actually the clock-out" swap. Late scans (16:00+) get a
hint on the card. A corrected arrival is stored as the optional
actual_in_at on the correction row (migration script included, not
applied); the original QR scan is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:57:09 +02:00
DEls
9a6aed50a4 feat(management): add admin planning dashboards
Add turnover, project, retention, and year comparison pages from the supplied workbook, protected by named admin access. Include the local chart gallery, grouped monthly hover, smooth comparison toggle, and monthly income minus cost series.

Require named PocketBase sign-in in every environment and remove the shared PIN bypass. Update access documentation and business-route test fixtures.
2026-10-09 10:16:50 +02:00
DEls
01239c6633 fix(dn): drag to pan a zoomed page in Check page grouping
The grouping preview zoomed by widening the image inside a scroll box with
no way to move it on a Mac (hidden horizontal scrollbar). Adds a shared
mouse drag-to-pan hook, a grab cursor and a one-line hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 06:40:49 +02:00
DEls
cb463c0faa feat(workforce): add timesheet exports and duplicate protection
Add per-site CSV and PDF matrix reports, editable sick-note date ranges, and weekend calendar styling. Preserve verified duplicate uploads for audit while excluding their entries from hours and costs, and prevent repeat files and overlapping daily imports.
2026-10-07 23:53:47 +02:00
DEls
a21c728d61 feat(workforce): add calendar and sick notes 2026-10-07 14:27:44 +02:00
DEls
f72397259c feat(auth): add Office access and restrict Clock users 2026-10-07 14:22:57 +02:00
DEls
8d53e3688c feat(workforce): add active and inactive workers 2026-10-07 13:41:05 +02:00
DEls
6824d2c62e Add Workforce attention alerts and 24-hour correction times 2026-10-04 23:38:43 +02:00
DEls
ed69b517e8 Integrate agent work and add dashboard attention indicators
Remove repeated Plant, Financial and Workforce shortcut cards. Add site-scoped Plant and Financial sidebar attention status with automatic refresh.

Include accumulated delivery processing, purchase order recovery, supplier reconciliation, plant data and user management changes and audit records.
2026-10-04 21:53:32 +02:00
DEls
4ad262b508 Let site managers create clock users for assigned sites 2026-09-30 00:11:24 +02:00
DEls
11a227bb4e Allow clock users to upload purchase orders for assigned sites 2026-09-29 13:33:46 +02:00
DEls
f5259d852f feat(auth): add PocketBase logins and site access controls 2026-09-28 23:51:47 +02:00
DEls
da9dca7c6e feat(plant): add "Add vehicle" button to the Fleet & Fuel page
Generalizes the Plant page's add-dialog into a shared AddVehicleButton
component parameterized by unit_type, and wires it into the vehicles
page against the existing POST /api/vehicles endpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 08:33:53 +02:00
DEls
1899ed8e2c fix(dn): unstick long OCR runs, preserve OCR data through manual saves
Tick path
- Bump STALE_CLAIM_MS from 10 to 30 minutes. A 29-source OCR batch
  hit ~29 min and was being reclaimed mid-run by the 10-minute cap,
  leaving the batch in inbox with last_error='reclaimed after a
  stalled run' even though nothing was stalled. MAX_ATTEMPTS still
  bounds a truly stuck batch.
- After each dn_batch_pages create, the staged runner bumps
  dn_batches.page_count in the same lease-fenced batch. Final
  assembly write at run-staged.ts:223 still sets the authoritative
  value, so any drift from a reclaim-then-restart cycle is corrected.
  Inbox tiles now show live progress instead of 0 pages for the
  entire reading window.

PocketBase batch transport
- sendRecordBatch chunks across pb.createBatch() calls at the
  server 50-request cap. A 71-candidate assembly sent as one
  request was returning Invalid batch request data. and 500ing —
  the assembly route caught it and surfaced as the generic Grouping
  could not be saved. 73 mutations now split into 50 + 23 and land.

Assembly save UX
- The dismiss dialog now renders directly under the open-pages alert
  instead of at the bottom of the section (which was hidden behind 71
  document tiles on big batches). Clicking Dismiss page opens the
  reason input where the user is looking.
- saveGrouping surfaces the server-side error code
  (stale_revision / posted_candidate / dismissed_candidate /
  assembly_in_progress / invalid_page_coverage) instead of a generic
  Try again. On success, the page now redirects to /inbox so the
  documents land in their columns.
- The Technical details disclosure is gone from the grouping screen.
  Open pages already surface their reason in the alert above; the
  duplicate disclosure just competed with the decision UI.

Assembly route correctness
- After a successful manual save the route now writes
  stage=complete, status=needs_attention, notes_detected,
  processing_completed_at and clears claimed_at/last_error. The
  runner advances stage on its own runs but a manual save was
  never followed by a runner pass — without this the batch stayed
  in Check page grouping forever even though assembly was done.
- For existing candidates the route only updates grouping metadata
  (pages, page_from, page_to, assembly_revision, status, stage,
  checks). OCR-derived fields (doc_number, supplier_name_raw,
  items, raw_text) are preserved on the row — the runner only
  re-fills those for batches in status=inbox, and post-save the
  batch moves to needs_attention and never reaches the runner
  again. New candidates still get a createdCandidateFields row
  with the OCR fields blank, ready for the runner or operator
  entry.
- A Re-run batch button was added to the document detail header
  so the operator can refill the wiped OCR data (or re-resolve
  checks after a manual override) without first navigating to the
  batch detail page.

Customer identity check
- A Skip this check toggle on the document detail page records
  manual_overrides.skip_customer_identity. run-staged reads the
  override and turns the customer_identity check into passed=null
  (skip) instead of passed=false (fail), so OCR false-positives
  on the customer VAT no longer block posting while still
  requiring an explicit operator decision.
- The PATCH route accepts skip_customer_identity as an editable
  field so the toggle saves through the standard note route.

Tests
- 180 files / 1767 tests pass; typecheck clean.
2026-09-27 13:40:27 +02:00
DEls
0f022d5ffa fix(ask): keep the Asking-as bar pinned under the navbar
The Ask page sized itself as 100svh-4rem, but the Topbar is 89px on
desktop and 167px on phones, so the page overflowed and the bar scrolled
under the header. The Topbar now publishes its live height as
--topbar-h and the Ask page fills exactly the space below it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:56:40 +02:00
DEls
3feddebcaa fix(mobile): audit and fix layout overflow across dashboard pages at 375/768px
Audited /dashboard, /dashboard/sites (+ detail), /dashboard/workforce/workers,
/dashboard/workforce/on-site, /dashboard/plant/vehicles (+ detail),
/dashboard/plant/equipment (+ detail), /dashboard/plant/fuel-logs,
/dashboard/financial/purchase-orders (+ detail),
/dashboard/financial/delivery-notes/inbox, /dashboard/financial/suppliers,
and /dashboard/chat at 375x812 and 768x1024 using a scroll-right test to
catch real horizontal page overflow (as opposed to a table's own,
intentional internal scroll).

Two repeating bug patterns accounted for every overflow found:

1. A page-specific `right={<div className="flex items-center gap-2">...}`
   passed to Topbar can carry two or three labelled buttons (edit/import/
   merge/back etc.). Without `flex-wrap` they don't fit a 375px screen in
   one row and push the whole page sideways. Fixed on the vehicles,
   equipment, and site detail pages, plus their list-page counterparts
   (vehicles fuel-view, equipment plant-view) — all five instances of
   this exact pattern. Same fix applied to a delivery-note card's button
   cluster on the PO detail page, and to Topbar's own right-side
   container as a general backstop.

2. `Intl.NumberFormat("en-ZA", ...)` joins currency/number groups with
   non-breaking spaces, so a wide value like "R 24 988 663" can't wrap.
   In a 3-up grid (site cards' Contract/Spent/Remaining) that overflowed
   the column; fixed by stacking those to 1-up below `sm`. In a 2-up grid
   (PO detail KPI tiles) added `min-w-0` so the tile clips instead of
   growing the grid track past the viewport.

Also: the shared Dialog now caps at `max-h-[90dvh]` with internal
scroll (a dialog taller than the phone viewport had no way to reach
its close button or footer before), and the sidebar hamburger button
was overlapping the Topbar's title on every page below `lg` — reserved
space for it instead.

Tables already scroll horizontally inside their own card via the
shared `<Table>` component or an `overflow-auto` wrapper, and KPI
grids already collapse responsively — both were fine everywhere checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:44:19 +02:00
DEls
efc1ed61d0 fix(layout): keep the Topbar pinned with iOS safe-area padding
The Topbar was already `sticky top-0`, which tracks the whole-page
scroll fine on both phones and desktop since neither the dashboard
layout nor any page wraps it in a clipping scroll container. What was
missing was the iOS PWA notch: add `paddingTop: env(safe-area-inset-top)`
so the bar's own background paints under the status bar/notch in
home-screen standalone mode instead of leaving a transparent gap.

z-30 stays below the z-40/z-50 used by drawers, dropdowns and dialogs,
and below the Leaflet map page's isolated stacking context (1760f77),
so the Topbar's dropdowns still float above the map and dialogs still
float above the Topbar.

Also fixes a real overlap bug surfaced while auditing at 375px: the
sidebar's fixed hamburger button sits at the same top-left corner as
the Topbar's title below `lg`, and nothing reserved room for it, so
the page title rendered partially underneath the button. Reserve that
space with padding instead, and truncate the title/subtitle so long
site names don't overflow the header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:00:27 +02:00
DEls
bdb2b2361b feat(ask): log app change requests for the developer
file_change_request (tools-actions.ts) logs an ask_log row
(type "request", status "open") for a change to the app itself — a new
page, column, button or report layout — distinct from propose_changes,
which changes data; the prompt now routes between the two.
list_change_requests (tools.ts, read-only) lists them, which the
existing MCP server picks up automatically since it iterates the whole
tools array. /dashboard/change-requests lists and re-statuses them
(open/planned/done/declined) via new GET/PATCH routes under
/api/ask/change-requests, linked from the sidebar under Ask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 21:07:27 +02:00
DEls
af71b8534b feat(suppliers): green-row tint, period filter, and KPI tiles
Green when a supplier's ordered > 0 and outstanding === 0 -- the whole
row, not just the number, so a fully-paid-up supplier stands out at a
glance.

Period filter next to the search:
  All time, This week, This month,
  Week 1-4 (of current calendar month, last absorbs any remainder),
  Month 1 (current) / Month 2 (last) / Month 3 (month before),
  Custom range with two date inputs.

Window anchors on delivery_notes.date_received -- liability accrues when
the goods arrive, not when the PO is uploaded ('we only pay what we
received this month'). computeSupplierBalances gains lastDeliveryISO per
row; a pure helper in supplier-period.ts resolves windows in UTC so the
boundary does not drift with the server's timezone.

Four KPI tiles at the top of the page:
  - Total ordered / delivered / outstanding (cumulative, always).
  - Payable this period (delivery value inside the active window).
    Subtitle is honest: 'subtract anything already paid' -- we do not
    track supplier payments, so this is the upper bound.

ui/select.tsx exports a SelectLabel so the period dropdown can group
options.
2026-09-26 17:38:39 +02:00
DEls
e83a0d302c fix: overview counts, wrong-unit fuel limits, meter guard, en-ZA numbers, vehicle merge, docs
- Overview/Plant tiles and pages share one vehicle bucket rule (blank unit_type = fleet)
- Plant landing equipment figures were always 0; hour diffs now reject 0 baselines and >24h/day jumps
- km/L limits on plant (and L/h on vehicles) report unit-mismatch instead of false alerts
- formatPercent/formatDecimal replace display toFixed (en-ZA)
- POST /api/vehicles/[id]/merge + Merge duplicate dialog (dry run first)
- Attention links go to equipment vs vehicles by unit type; PO upload view link fixed
- Conditional hook fixed in DN document view; ESLint flat config (next lint removed in 16)
- Docs: current modules/routes/env vars; login + Ask reports plans

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:04:08 +02:00
DEls
47ec4f1d1b feat(purchase-orders): enhance PO handling with improved validation and item management
Updated the purchase order processing to include enhanced validation for item edits, ensuring that all required fields are checked and properly formatted. Introduced new functions for computing item amounts, taking into account discounts and VAT. Refactored the PO detail view to streamline item management, including a new ItemsCard component for better organization of line items. Additionally, improved the handling of PO number sanitization to strip leading and trailing characters, enhancing matching accuracy. This update aims to improve the overall robustness and user experience of the purchase order system.
2026-09-23 23:37:37 +02:00
DEIs
87f75dd98f feat(plant): fleet map page with GPS-denormalised vehicles, clustering, and tile switcher
Adds /dashboard/plant/map: a Leaflet view of every vehicle scanned from
the MEGA Clock kiosk, plotted at its last known position.

Schema
- vehicles.last_latitude / last_longitude / last_scanned_at /
  last_location_accuracy_m denormalised off fuel_logs so the map page
  renders every vehicle in a single query. Same best-effort pattern as
  attention_dismissed_at; updated by /api/clock/vehicle-scan after every
  successful scan. PB init defaults new number cols to 0 (not null) —
  filter uses 'last_latitude > 0 || last_latitude < 0' to mean 'real fix'.
- Indexed via idx_vehicles_last_scanned.

Map UI
- Custom SVG markers per unit_type: car silhouette for fuel, tractor
  silhouette for plant, 24x24, drop-shadowed.
- Clustering via react-leaflet-cluster over Leaflet.markercluster.
  Cluster icons colour-ramp with size (green->sky->amber->rose) and
  scale 32->54px.
- Three-button base-layer switcher: Street (OSM), Dark (ESRI Dark
  Gray Canvas — CartoDB watermarks tiles without a key), Satellite
  (ESRI World Imagery). Last choice persists in localStorage.
- Site pins: small purple map-marker divIcon, click flies the map to
  zoom level 15.
- Empty-state is a floating chip on the map rather than a full-page
  card so the map is always visible.

APIs
- /api/clock/vehicle-scan: stores the GPS fix on the fuel_logs row
  AND denormalises onto the vehicle when present; missing-coord
  scan leaves last_* untouched.

Sidebar: new Map entry under Plant group.
2026-09-22 21:39:25 +02:00
DEls
da756f5224 feat(worker-qr): real PDF + CSV batch export
Replace the off-screen CSS grid + window.print() in the worker QR
batch dialog with two on-click exports:

  - Download PDF — A4 portrait, 2x5 grid of CR80 (85x54mm) cards per
    page, brand orange header, generated with pdf-lib (dynamically
    imported, same pattern as src/lib/photo-pdf.ts).
  - Download CSV — UTF-8 BOM, columns employee_no, name, position,
    worker_type, project_code, qr_payload via Papa.unparse.

The single-card print on the worker detail page still uses
window.print() — one card has no pagination problem, only the batch
view did.

Spec §4.4 updated to match the new output mechanism and explain the
mobile failure mode that prompted the change.

New tests (8) cover the CSV builder (header order, qr_payload ==
employee_no per spec §4.1, RFC 4180 quoting, empty input) and the PDF
builder (empty input throws, valid %PDF output, 11 workers paginate
onto 2 pages, fake PNG injection so layout is testable without qrcode).
2026-09-20 20:43:10 +02:00