Commit graph

27 commits

Author SHA1 Message Date
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
68f3e1eea6 feat(scanner): crop and review delivery and PO photos 2026-10-12 20:20:05 +02:00
DEls
f72397259c feat(auth): add Office access and restrict Clock users 2026-10-07 14:22:57 +02:00
DEls
bcbd3167e4 feat(clock): unify scanners and document capture 2026-09-30 06:44:36 +02:00
DEls
758999ce3b fix(clock): keep fleet dialogs above phone keyboard 2026-09-30 01:07:11 +02:00
DEls
b92449d337 fix(clock): pause QR decoding and fit search above keyboard 2026-09-30 01:03:23 +02:00
DEls
32d4a82cdf feat: add employee search and save pending fleet updates 2026-09-30 00:42:38 +02:00
DEls
71499f5222 Allow assigned-site users to perform operational actions 2026-09-29 23:58:40 +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
0edd8503b1 refactor(clock): update ConfirmDialog layout and timing constants
Removed the DialogFooter component and restructured the ConfirmDialog layout for improved usability. Increased AUTO_DISMISS_MS from 5 seconds to 30 seconds and adjusted DOUBLE_CONFIRM_WINDOW_MS from 4 seconds to 6 seconds to enhance user experience during the scanning process. Updated button styles for better visibility and interaction. This change aims to streamline the confirmation flow and reduce accidental dismissals.
2026-09-23 00:08:32 +02:00
DEls
97fe75931d feat(clock): implement two-step QR scanning flow for employee clocking
Enhanced the PersonScanner component to support a two-step flow for scanning employee badges. The new process includes a lookup phase to identify the employee and their last scan status, followed by a confirmation phase where the operator can verify the scan before recording the clock event. This change addresses issues with accidental double scans and improves user feedback with warnings for recent scans. Updated state management and error handling to reflect the new flow, ensuring a smoother user experience.
2026-09-22 23:50:59 +02:00
DEls
a589b14ac9 feat(clock): enhance vehicle scan API with GPS support
Updated the vehicle scan API to include optional GPS coordinates (latitude, longitude) and location accuracy. The API now validates these fields, ensuring both latitude and longitude are provided together and within valid ranges. Enhanced error handling for invalid inputs and updated the API reference documentation accordingly. Additionally, modified the FleetScanner component to capture GPS data and reflect its status in the UI.
2026-09-22 19:59:44 +02:00
DEls
3077223d38 feat(clock): add MEGA Clock API endpoints and update documentation
Introduced three new server-only API endpoints for the MEGA Clock PWA:
- `POST /api/clock/scan` for clocking workers in and out via badge scanning.
- `POST /api/clock/vehicle-lookup` for looking up vehicle details using QR codes.
- `POST /api/clock/vehicle-scan` for logging odometer readings from scanned vehicles.

Updated the README and API reference documentation to include these new endpoints and their functionalities. Replaced the placeholder for fleet scanning in the ClockView component with the new FleetScanner component.
2026-09-22 12:42:39 +02:00
DEls
068f622c0a feat(clock): photograph several pages, send them as one document
A delivery note that runs to two or three sheets has to arrive as ONE
batch. Sent a page at a time it became separate batches, and then
nothing could tell that page 2's line items belonged to page 1's note —
the totals sat on one batch and the items on another, with no way back.

Pages now accumulate. Take one, then Retake, Add page, or Send all.
A thumbnail strip shows what has been taken so far, ringed green or red
by how each page measured, so a mis-shot can be dropped without starting
the document over and nobody has to remember whether they got page 2.

Several pages are bundled into a single PDF and enter the pipeline
exactly like a scanned multi-page document: split.ts rasterises each
page, extract.ts reads each in isolation, and segment.ts decides from
the printed document numbers whether it is one note over three pages or
three separate notes. The person holding the phone does not have to know
which, which matters, because standing next to a truck they often can't.

The bundle is verified against the pipeline's own tools rather than
assumed compatible: the test writes one and runs pdfinfo and pdftoppm
over it — the exact binaries split.ts shells out to — checking the page
count, that every page rasterises, that portrait stays portrait, and
that page order survives. A bundle the pipeline could not open would
otherwise fail at the far end of an upload, on site, after the truck had
gone.

One page still uploads as its own JPEG: smaller, and it never loads the
PDF library at all. pdf-lib is imported dynamically and webpack keeps it
in a 412K chunk of its own, leaving the clock page's bundle at 24K —
checked in the build output, not hoped for.

Size is checked before sending, mirroring MAX_FILE_BYTES on the upload
route, so an oversized bundle is caught while the document is still in
front of the person rather than as a 400 afterwards.

1027 tests, typecheck clean, production build clean. The capture path
itself still needs a real phone — the browser pane here has no camera
and cannot reach localhost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:10:16 +02:00
DEls
5bf8036210 feat(clock): photograph a delivery note on site, and check it can be read
Adds a third tab to the clock PWA: point it at a delivery note or
invoice, take the photo, send it. It produces one dn_batches row per
photograph — the same thing the office upload button produces — so
splitting, PO matching, the checks and the board all already handle it.
One photograph per send, which sidesteps the multi-note splitting that
causes most of the trouble with scanned stacks.

Focus is measured, not hoped for. A soft photo does not fail loudly; it
extracts almost right — a 3 that reads as 8, a decimal comma lost — and
those become numbers a supplier gets paid on. Variance of the Laplacian
runs on the live preview four times a second and again on the frame
actually captured, because the shutter frame is not the one measured
250 ms earlier.

The thresholds were read off a real delivery note rather than invented.
The fixture's three pages rasterised at 800 px score 4825/4863/4801 as
scanned, then 322, 88, 41 and 16 through one to five box-blur passes.
Two passes is where the print starts to fight you, three is unreadable,
so the bands sit at 120 and 45. Those are flatbed scans and a handheld
photo will score well below 4800, which is why the bands come from the
degradation ladder and not from the top figure.

One check had to be deleted rather than tuned: "washed out by glare"
fired on every good document. A clean delivery note is 95.2% pure white
and reads 246 mean luminance, so any upper brightness threshold that
catches a blown-out photo also condemns the real thing. Real glare is a
local hotspot, not a global mean, and detecting it needs samples we do
not have. Measured, not assumed — see the test.

The frame is cyan at rest, not the app's orange: a delivery note is warm
white and orange on cream disappears exactly where it needs to be seen.
It turns green when the photo will read and red when it will not, so red
means something instead of being the colour of the frame. A bad photo
can still be sent — the truck does not wait, and a soft record of a real
delivery beats no record of it — the button just stops looking like the
obvious thing to press.

Site comes from geolocation because it has to: badge scanning reads the
site off the employee record and a delivery note has no employee to ask.
The fix proposes and the dropdown disposes; it is never submitted
unseen, because a delivery filed against the wrong site lands on another
site's purchase orders. A site stored at 0,0 is treated as unplaced —
the live collection has one, and null island is a real point in the Gulf
of Guinea that a bad fix would otherwise match.

1023 tests, typecheck clean, production build clean. Not verified in a
browser: the pane cannot reach localhost here (net::ERR_FAILED on every
resource), and it has no camera to drive the capture path with. curl
confirms the route renders and ships both sites with their coordinates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 18:36:41 +02:00
DEls
eba3f8c89a feat(clock): in-app PWA install prompt with Chrome + iOS branches
The kiosk phone needs the app installed for the full-screen
standalone experience, but no browser auto-prompts on day one —
Chrome's mini-infobar waits for an engagement heuristic that's
too slow for the kiosk use case, and iOS Safari never fires
beforeinstallprompt at all.

New InstallPrompt component mounted in the clock layout (so it
only appears on /clock, not the dashboard). Three branches:

  * Chrome / Edge / Android: defer beforeinstallprompt, surface
    our own Install button that calls prompt() on tap.
  * iOS Safari: detect iOS user agent and show 'Tap Share then
    Add to Home Screen' (Apple doesn't fire the install event).
  * Already installed: detect display-mode:standalone +
    navigator.standalone and render nothing — silent on the
    kiosk itself.

Dismissal is per-session (sessionStorage), so a fresh visit on
a new device re-shows the prompt. The banner sits above the
clock footer (bottom: 6rem) so it doesn't fight the Person /
Fleet tabs for taps.
2026-09-14 11:17:54 +02:00
DEls
059e32cabb feat(clock): add orange viewfinder + scanning state to the QR screen
The kiosk phone is mounted on a wall with no physical UI to confirm
'this is the scanner' — without the overlay the camera feed alone
is a confusing blank screen. Now the scanner paints:

  * a centred orange-cornered viewfinder (the QR alignment frame)
  * a black vignette dimming everything outside that frame
    (achieved via a 9999px box-shadow on the viewfinder, the
    cleanest way to punch a rounded hole in an overlay without SVG
    mask math)
  * an orange scan line sweeping top-to-bottom inside the frame
    (transform-based keyframes, pauses under prefers-reduced-motion)
  * a pulsing 'Scanning for badge' status pill at the top
  * a one-line 'Hold the QR badge steady inside the frame' hint
    at the bottom

Everything is pointer-events-none so touches still reach the
camera. The overlay hides during the green/blue/red result banners
so it doesn't compete with the success message.
2026-09-14 00:28:47 +02:00
DEls
5f44d6a926 feat(clock): replace icon with clock-on-cube design + OG share preview
The new PWA icon matches the supplied reference: dark background, a
clock face with an orange upper-right arc and hands at 12 and 4
o'clock, sitting on an isometric cube whose two visible faces each
have the MEGA 'M' carved into the background colour, with two small
orange base wedges under the cube.

When the /clock URL is shared (Slack, iMessage, etc.) the static
1200x630 opengraph-image.png is picked up automatically and renders
the icon alongside the 'MEGA Clock' wordmark and tagline, via
openGraph + twitter metadata added to the route.
2026-09-13 21:55:40 +02:00
DEls
3ec2db9b2a feat(clock): bigger footer tabs, session scan counter, wake lock + haptics
Footer buttons gain icons and more padding for easier tapping on a
wall-mounted kiosk phone. The header now shows a running count of
badges scanned since the app was opened. PersonScanner also requests a
screen wake lock while its camera is active (re-acquired on
visibilitychange, since the OS revokes it when backgrounded) and adds
vibration feedback per scan outcome, so a kiosk phone doesn't need to
be watched closely to confirm a scan landed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 00:51:53 +02:00
DEls
a7ac99d934 feat(clock): add vertical divider between Person and Fleet tabs 2026-09-12 00:34:50 +02:00
DEls
b212cd49f6 feat(clock): add visible divider above the Person/Fleet footer tabs 2026-09-12 00:28:14 +02:00
DEls
d8ae2ba094 feat(clock): add MEGA Clock icon set 2026-09-12 00:17:25 +02:00
DEls
3d211e9121 feat(clock): add /clock page with its own installable manifest 2026-09-12 00:17:04 +02:00
DEls
3236ba32ac feat(clock): add ClockView with Person/Fleet footer tabs 2026-09-12 00:16:30 +02:00
DEls
af6f7153fb feat(clock): add PersonScanner camera + QR decode component 2026-09-12 00:16:22 +02:00
DEls
13c29eb93d feat(clock): add /clock layout (PIN gate, no sidebar) 2026-09-12 00:16:07 +02:00