Commit graph

352 commits

Author SHA1 Message Date
DEls
30a45f759d Merge branch 'feat/site-scope' into main
Global site scope: one switcher in the topbar, carried in the URL as
?site=, resolved server-side via the getSiteScope() seam, and applied as
a PocketBase site= filter across every dashboard module. Replaces the
four independent per-table project-code dropdowns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	docs/superpowers/plans/2026-09-11-site-scope.md
2026-09-11 23:57:51 +02:00
DEls
0c29049324 fix(site-scope): close two latent leaks and the stale scope pill
Three review findings, all of which break the moment `allowed` stops
being every site — i.e. as soon as PocketBase users land.

siteIdFilter(): the Overview spelled its `sites` filter out as
`mode === "all" ? "" : id="..."`, which drops the `unrestricted` check
that makes "all" safe and would load every site for a manager allowed
only a subset. Both field variants now go through one shared function so
the check cannot be forgotten again. Covered by six new tests.

Site pickers read scope.allowed: upload and create dialogs took their
options from unfiltered `sites` reads, so a restricted user could
attribute a PO, delivery note, timesheet or plant item to a site they
cannot even open — a write path, not just a display leak. View scope
must not narrow a picker, but permission scope must. Also drops one
redundant sites query on the equipment page.

useScopeFallback(): the pill fell back to a value the layout computes
once, and layouts do not re-render on navigation — so after any link
that doesn't carry ?site= forward (24 of them), the pill read
"All sites" over a single site's figures. It now reads the live cookie,
which the switcher keeps current, fixing every link rather than the four
scopeAwareHref already patched.

Verified against the running app: scope survives a client-side
navigation through a bare link, and the Overview reads 1/"The Hills
ext 12" scoped and 2/"Across every site" unscoped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:56:28 +02:00
DEls
c30b11135f docs(clock): add design spec for standalone MEGA Clock timeclock PWA
Scans a worker's existing QR badge to clock them in/out, writing
directly into timesheet_entries as a "Direct (QR)" entry — the
timekeeping app foreshadowed in the workers-timesheets-qr spec.
2026-09-11 23:54:39 +02:00
DEls
4ac5f3a220 fix(site-scope): preserve ?site= across sidebar navigation
Bug: picking a site in the topbar pill updated both URL and cookie,
but the sidebar nav Links were plain paths ('/dashboard/sites',
'/dashboard/financial/purchase-orders', ...). Clicking any of them
stripped the '?site=' query string. The page's getSiteScope() then
fell back to the still-set cookie and filtered the data correctly,
but the pill's useSearchParams() and the layout's frozen fallbackValue
both produced mode='all' — so the topbar pill showed 'All sites · N'
while every table was filtered to the picked site. The visual pill
and the data disagreed on every screen the user clicked.

Fix: extract scopeAwareHref() and route every sidebar Link through
it. The helper carries the current '?site=' (and nothing else) into
the destination URL, so the URL stays the single source of truth
for both the page filter and the topbar pill across navigation.

Layouts do not re-render on navigation, so preserving the URL is
the only viable fix — making the layout re-run getSiteScope() on
every navigation is what the plan explicitly avoided by design.

Tests: 5 new unit tests for scopeAwareHref covering the bare-path,
scope-only, drop-other-params, url-encoding, and 'all' sentinel
cases. Total: 651 tests passing, typecheck clean.
2026-09-11 23:41:11 +02:00
DEls
eeb4975df1 fix(site-scope): split server-only getSiteScope into site-scope-server.ts
The 'use server'/'use client' boundary for `next/headers` is whole-module:
the build fails when any client-reachable file transitively imports a
module that uses `next/headers`. The dashboard switcher is a 'use
client' component and was importing from `@/lib/site-scope`, which
pulled in `next/headers` via the inline `getSiteScope` server entry.

Move `getSiteScope` to a new `src/lib/site-scope-server.ts` so the
pure helpers (resolveActive, siteFilter, composeFilter, filterOpt, types)
and constants stay client-safe in `site-scope.ts`. Every page that
needed `getSiteScope` now imports it from `-server`; the pure imports
stay where they were.

Build verified: `npm run build` succeeds; typecheck and 646 tests pass.
2026-09-11 23:34:34 +02:00
DEls
14d1368f93 chore(site-scope): drop unused projectCodes prop from PurchasingView/PlantView 2026-09-11 23:22:54 +02:00
DEls
512a2fc2cb feat(site-scope): preserve ?site= through the legacy redirects 2026-09-11 23:21:26 +02:00
DEls
e00d347f7d feat(site-scope): gate record detail pages on the allowed site set 2026-09-11 23:21:00 +02:00
DEls
729118d5dd feat(site-scope): exempt the Sites list, add a per-row scope action 2026-09-11 23:20:05 +02:00
DEls
5edea14105 feat(site-scope): scope the supplier ledger, keep the list global 2026-09-11 23:18:38 +02:00
DEls
3cb34c5aad feat(site-scope): scope the Financial pages, drop the project dropdown 2026-09-11 23:18:17 +02:00
DEls
2f28ff2724 feat(site-scope): scope the Workforce pages, drop the project dropdown 2026-09-11 23:15:51 +02:00
DEls
77ec2ec697 feat(site-scope): scope the Plant pages, drop their project dropdowns 2026-09-11 23:14:08 +02:00
DEls
fe55a5d7df docs(site-scope): correct stale line refs, record tasks 1-5 as done
Re-verified every file:line against the current tree. Three were wrong:
the Overview ranges in task 6 (and it listed two sites.items uses where
there are four), the hamburger line in task 5, and a claim that
plant/page.tsx still had perPage: 5000 caps — it does not.

Also records that tasks 1-5 have landed, so the next worker starts at 6,
and keeps the scope-aware Sites sub-label that was implemented in place
of the deletion this plan originally specified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:05:10 +02:00
DEls
1f3a0e5cef feat(site-scope): scope the Overview page, lift the 50-site cap 2026-09-11 23:05:05 +02:00
DEls
6609ce8e50 feat(site-scope): add the topbar scope switcher 2026-09-11 23:03:10 +02:00
DEls
63eb8ed931 feat(site-scope): load the allowed site list in the dashboard layout 2026-09-11 23:02:30 +02:00
DEls
c88ebb1715 feat(site-scope): add the getSiteScope server seam 2026-09-11 23:02:03 +02:00
DEls
38ad190083 feat(site-scope): build PocketBase filter fragments from a scope 2026-09-11 23:01:29 +02:00
DEls
8fe08fab3d feat(site-scope): resolve the active scope from URL, cookie, default 2026-09-11 23:00:55 +02:00
DEls
8f1feb6172 checkpoint before checking out feat/site-scope 2026-09-11 22:58:41 +02:00
DEls
ec9d4fe565 Merge branch 'feat/site-scope' into main
Adds the per-site project month/week feature: a configurable work-month
start day, a shared Month/Week badge, and a Today/This week/This
month/All time filter on Timesheets, Delivery Notes, Purchase Orders,
and Fuel Logs. Also includes the (not-yet-implemented) site-scope
switcher spec/plan docs.
2026-09-11 22:35:44 +02:00
DEls
58b47eafee docs(project-calendar): add the project month/week plan; fix a stale doc reference
The migration's docstring pointed at a spec file that was never
written for this feature (the design was worked out via conversation,
not a separate spec doc) — points at the plan instead.
2026-09-11 22:35:41 +02:00
DEls
85692dedec docs(site-scope): anchor dropdown removal on names, not line numbers
A concurrent project Month/Week feature is landing in the same view
files and has already shifted purchasing-view.tsx by ~10 lines, adding
a periodFilter beside projectFilter. The removal steps now match on
identifiers and snippets, and the plan header says to treat every
file:line as a hint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 22:30:08 +02:00
DEls
67785fe2b3 feat(fuel-logs): add project Month/Week badge and filter 2026-09-11 22:29:13 +02:00
DEls
4ae138686a feat(purchase-orders): add project Month/Week badge and filter 2026-09-11 22:28:28 +02:00
DEls
3cd7382115 docs(site-scope): add the implementation plan
16 TDD tasks: pure scope helpers, the getSiteScope seam, the topbar
switcher, then per-module server-side filtering. Task 15 records why
the chat agent's default-site change is deferred out of this plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 22:28:10 +02:00
DEls
cfa84e77d6 feat(delivery-notes): add project Month/Week badge and filter 2026-09-11 22:27:49 +02:00
DEls
c9a4d00455 feat(timesheets): add project Month/Week badge and filter 2026-09-11 22:27:06 +02:00
DEls
5195398a00 feat(sites): add work-month-start-day field to the site form 2026-09-11 22:25:32 +02:00
DEls
7a9a1ca555 feat(sites): accept work_month_start_day on create/update 2026-09-11 22:24:59 +02:00
DEls
41c4b41046 feat(project-calendar): add shared Month/Week badge component 2026-09-11 22:24:45 +02:00
DEls
3b370205bb feat(project-calendar): add work-month/week bucketing with tests 2026-09-11 22:24:37 +02:00
DEls
daf2344d99 feat(sites): add work_month_start_day field 2026-09-11 22:24:03 +02:00
DEls
d570352b76 docs(site-scope): spec the global site scope switcher
Replaces the four independent per-table project-code dropdowns with a
single global scope carried in the URL as ?site=, resolved server-side,
and applied as a PocketBase site= filter. Defines the getSiteScope()
seam so the separate PocketBase users work plugs in without touching
the 23 Topbar call sites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 22:20:05 +02:00
DEls
1b99d3dd8a chore(assets): resize apple-touch-icon to the new logo mark
The 180x180 apple-icon.png hadn't been regenerated from the new cube
mark yet; the other icon sizes already had been.
2026-09-11 21:51:49 +02:00
DEls
f80c30d369 feat(financial): wire up per-collection upload status pages
Adds the "Upload status" buttons on Purchase Orders and Delivery Notes,
the shared badge/KPI/filter UI they use, and removes the now-dead
Uploads sidebar entry and Financial landing card.
2026-09-11 21:44:12 +02:00
DEls
6829850ae0 feat(financial): split upload status per-collection, drop from sidebar
Purchase Orders and Delivery Notes each get their own unlinked
"upload status" page reached via a button next to the upload action,
mirroring the Timesheets pattern. Removes the combined
/dashboard/financial/uploads page and its sidebar entry, since its two
halves (PO status, DN status) are now scoped to the page they belong to.
2026-09-11 21:43:58 +02:00
DEls
607de98e89 feat(timesheets): move upload status to its own unlinked page
Timesheets now shows only entries + filters; an "Upload status" button
next to Import Timesheets opens the sheet-by-sheet processing view on
its own route, not listed in the sidebar.
2026-09-11 21:33:45 +02:00
DEls
c3914165af feat(timesheets): add upload-status section above the entries table 2026-09-11 20:58:55 +02:00
DEls
68dc88e4c5 feat(workforce): move workforce charts + richer KPIs to the landing page 2026-09-11 20:58:34 +02:00
DEls
02c1156e58 feat(workforce): add worker detail page with history and QR badge 2026-09-11 20:58:13 +02:00
DEls
73e19ad5d2 feat(workforce): workers roster slim + QR badge card + batch export 2026-09-11 20:57:52 +02:00
DEls
1300d77512 refactor(workforce): extract shared weekly-hours math with tests 2026-09-11 20:57:03 +02:00
DEls
5c47dac5d9 feat(workforce): add worker QR payload helper 2026-09-11 20:56:43 +02:00
DEls
045b6fe469 chore(workforce): add qrcode dependency for worker badges 2026-09-11 20:56:29 +02:00
DEls
7c0925f787 Merge branch 'feat/workers-timesheets-qr' into main
Adds the Workers/Timesheets split + worker QR badges spec and implementation plan, plus updated app icons/logo.
2026-09-11 20:55:41 +02:00
DEls
8e72acc8a5 chore(assets): update application icons and logo with new designs 2026-09-11 20:54:44 +02:00
DEls
d5e3b2ce2b docs(spec): Workers/Timesheets split and worker QR badges
Workers becomes a lean roster (name, employee number, position, type,
site, rate) with filters unchanged. Everything currently cluttering the
list row -- total hours, cost, the 8-week sparkline, per-employee entry
history -- moves to a new worker detail page that doesn't exist yet
(today's "detail" is a dialog). Same pattern already used for Suppliers
and Vehicles/Equipment this session: lean list, detail page carries
depth.

Timesheets keeps its flat entries table but gains a genuinely separate
upload-status section -- the timesheets document collection already has
this data, it's just currently squeezed into a per-row badge instead of
being its own thing. The duplicated Import-timesheets button (currently
on both pages) moves here only.

QR badges: encodes employee_no directly, no new PocketBase field --
every worker already has a valid code the moment they exist, with no
generation step. Explicitly a deliberate tradeoff (guessable numbers,
no revocation) the user chose for now, pending seeing how the separate
timekeeping PWA wants identity handled -- moving to a stored opaque
token later is additive, not a rework. Card layout confirmed via the
visual companion (centered, QR-forward). Both single-worker and
batch-sheet print confirmed in scope. Rendering via the qrcode npm
package + the browser's own print dialog (@media print), not a PDF
library -- CSS handles real-world card dimensions more reliably than
fighting a PDF engine's coordinate system.

The timekeeping PWA itself is explicitly out of scope -- a separate
project this spec only needs to stay compatible with.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 19:04:34 +02:00
DEls
5f4adcba6c fix(ui): clip table headers to their card's rounded corners
Card is rounded-xl with no overflow-hidden. TableHeader is sticky with
its own opaque background (bg-card/95) that sits flush against the
card's edge -- with nothing clipping it, the header's square background
corners poke past the card's rounded ones. Visible on every page built
with the shared <Card><CardContent className="p-0"><Table> pattern:
Suppliers, Supplier Detail, Uploads, Delivery Notes, Fuel Logs,
Timesheets. Purchase Orders is unaffected -- it uses a card-per-row
layout, not an HTML table.

Added overflow-hidden to each affected Card. Standard, deterministic
Tailwind behavior -- reliably clips child content to the parent's
border-radius, no visual regression risk.

Also added click-to-sort on the Suppliers list (Supplier/POs/Ordered/
Delivered/Outstanding, toggling asc/desc) -- the page previously only
supported a fixed server-side default (outstanding descending) with no
way to change it. No existing sort-header convention exists elsewhere
in the app, so this is self-contained rather than a premature shared
abstraction for what is still a single use site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 18:36:42 +02:00