Commit Graph

220 Commits

Author SHA1 Message Date
BOHA
313d9218c1 fix(offers,invoices): per-button save loading state (only the clicked button spins)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 20:41:25 +02:00
BOHA
c270cb22e9 fix(issued-orders): correct status display after create-redirect + per-button loading state
Bug 1: creating an issued order as 'sent' redirected to the detail which showed
'Koncept' until a refresh — the component is reused across create→edit, so the
one-shot hydration (gated by dataReady) never reloaded the real status. Now the
just-saved status is reflected into local form state on every save with a target.

Bug 2: both edit-draft buttons shared one 'saving' flag, so pressing 'Uložit
koncept' made 'Odeslat' show 'Ukládání...'. Track which action is in flight so
only the clicked button spins (all stay disabled to block double-submit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 20:36:55 +02:00
BOHA
113edd9a0d fix(numbering): release project number on order delete + harden split tests + accurate settings preview
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:44:54 +02:00
BOHA
753c16423c fix(settings): bank accounts saved blank — flatten payload + harden schema to strictObject
The bank form posted {id, bank:{...}} (nested) but the route/schema read fields
at the top level; lenient z.object silently stripped the wrapper, so every field
saved as null while returning success. Flatten the payload to top level and switch
the create/update schemas to strictObject so a future nesting 400s loudly instead
of saving a blank record. Affects both create and edit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:39:43 +02:00
BOHA
5c03570f07 feat(settings): separate numbering config for received orders, issued orders, and projects
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:31:36 +02:00
BOHA
52ff0a6ffa feat(drafts): show Koncept for null numbers in lists + fix filtered empty-state
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:52:34 +02:00
BOHA
a15fa68c3b fix(drafts): resync invoice number field after in-place draft→issued finalize
Mirrors the issued-order po_number resync so all three detail pages
surface the freshly-assigned number after finalize without a reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:50:18 +02:00
BOHA
e86c2e9a80 feat(drafts): two-button create (Vytvořit / Uložit koncept) + draft finalize UX across offers/invoices/issued-orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:44:10 +02:00
BOHA
ce636215dc feat(drafts): remove localStorage draft banner/hook; add Koncept status + filter (drafts are DB rows)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:25:24 +02:00
BOHA
28186397a0 fix(ui): line-item number inputs — allow clearing the 0 (store raw string, coerce at totals/save), unify value alignment, widen Množství column
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:38:28 +02:00
BOHA
a3948c7da1 fix(ui): hide invoice list delete button for paid invoices (consistent with detail guard)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:12:44 +02:00
BOHA
53c8359acc feat(ui): unify VAT rate+toggle layout (offers/invoices/orders); move offers secondary row actions into an overflow menu
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:04:44 +02:00
BOHA
c2d7a96718 feat(ui): invoice notes use RichEditor (sanitized render + PDF), matching offers/orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:59:43 +02:00
BOHA
61673247bf feat(ui): list parity — row tinting, received-orders status filter, more sortable cols, search-aware empties, skeleton suppression
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:55:50 +02:00
BOHA
9fe5113c5b fix(ui): cross-module mechanical unifications (line-item labels/align, back-tab targets, status-button colors, apply_vat checkbox, currency-from-settings, read-only issued_by, Celkem header, centered offer tabs)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:49:31 +02:00
BOHA
e868ecf769 refactor(ui): shared searchable CustomerPicker across offers/invoices/issued-orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:42:24 +02:00
BOHA
20177e8f87 fix(ui): guard delete on paid invoices / ordered-or-locked offers; add guarded delete to issued orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:36:32 +02:00
BOHA
39565767ef fix(ui): wire invoice draft restore (read-back) so the banner actually restores and autosave can't destroy the draft
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:32:20 +02:00
BOHA
c4668357aa feat(ui): shared record-scoped useDocumentDraft hook; fix half-wired invoice draft banner; dedupe draft keys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:24:22 +02:00
BOHA
b4f1b6ce2b refactor(ui): OrderDetail uses shared ORDER_STATUS (removes the last duplicate status map)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:17:52 +02:00
BOHA
66235ff567 refactor(ui): single shared documentStatus map across offers/invoices/orders (fix issued color drift, add offers status chip)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:12:44 +02:00
BOHA
430ec3e76b fix(ui): make item description a drag-resizable textarea (rows + resize:vertical) across invoice/order/offer
Replaces MUI autosize (no handle, mis-wraps in table cells) with a fixed 2-row textarea + resize:vertical so the box wraps and the user can drag it taller. Restores the dead '& input' sx as '& textarea'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:37:34 +02:00
BOHA
f295af4a14 fix(ui): auto-grow item detail-description box + offer money columns to 8rem (consistency across invoice/order/offer)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:28:17 +02:00
BOHA
db11999c85 fix(ui): widen 'Jedn. cena' column in invoice + issued-order line-item tables (5.5rem -> 8rem)
The editable unit-price number input was cramped (5.5rem, same as qty/unit); widened to 8rem to match the Celkem column and the read-only detail view, so real prices fit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:23:16 +02:00
BOHA
3d3df6db85 feat(orders): invoices-style landing — month selector + Vydane-first tabs, content-only tab bodies
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:33:36 +02:00
BOHA
2b7b480144 fix(orders): back button links to Vydané tab, not the nonexistent /orders/issued
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:53:23 +02:00
BOHA
e26fce092a fix(orders): authenticated PDF open in issued-orders list
The list opened the PDF route via raw window.open, an unauthenticated top-level navigation that 401s on the token-guarded endpoint. Now uses the apiFetch -> blob -> window pattern from InvoiceDetail/IssuedOrderDetail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:46:50 +02:00
BOHA
d7e0ba933d feat(orders): issued-order create/edit form + PDF export button
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:44:02 +02:00
BOHA
8b90cfed1f feat(orders): Orders page Přijaté|Vydané tabs + issued list
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:37:03 +02:00
BOHA
519edce373 fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace
the raw number|string transform idiom across every schema (the root-cause NaN bug
class); emailOrEmpty + lenient isoDateString/timeString.

Security: roles privilege-escalation closed; refresh-token family revocation on
reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices
gross VAT on all paths; orders-pdf custom-items authz.

Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse
confirm/cancel, attendance lockUserRow); uniqueness checks moved into create
transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision
timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO).

Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted
fields; dashboard invalidation gaps; stale-closure confirm bugs.

Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier;
tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit
fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes),
isolated on app_test via .env.test with a hard-throw setup guard.

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:26 +02:00
BOHA
d62abe81fd feat(odin): use the full page (full width + taller shell)
Drop the 1100px max-width cap and size the chat shell to fill the content
area (100dvh - 100px = top bar + main bottom padding), so Odin uses the whole
page now that it owns the route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:51:23 +02:00
BOHA
b7ebf04001 feat(odin): OdinChat orchestrator; render on /odin; remove DashAssistant
Wire OdinTabs/OdinThread/InvoiceReviewCard/OdinComposer into a single
OdinChat orchestrator; mount it on /odin (maxWidth 1100); delete the
old DashAssistant and its aiHistoryOptions query helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:39:15 +02:00
BOHA
e4ec08a5ef feat(ai): move the assistant to its own "Odin" sidebar page
Promote the AI chat from a dashboard widget to a dedicated page:
- new sidebar item "Odin" under the Přehled section, gated on ai.use, → /odin
- new /odin route + Odin page (reuses the assistant component, with a light
  ai.use guard)
- remove the widget from the dashboard
- rebrand the assistant header "Asistent" → "Odin" and give the thread more
  height now that it owns the page

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 18:38:35 +02:00
BOHA
f4bc038f8b fix(received-invoices): treat amount as gross (VAT-inclusive), back-calc VAT
The amount on a received invoice is the GROSS total the user pays (VAT
included), and VAT is the portion contained within it — not a net base with
VAT added on top. Switch the formula from amount*rate/100 to the
VAT-inclusive amount*rate/(100+rate) via a shared, tested vatFromGross()
helper used by both the create (manual upload + AI import share this
endpoint) and edit paths. Rate 0 → no VAT.

- AI extraction now asks Claude for the total INCLUDING VAT (was "základ bez
  DPH"), reinforced in the structured-output schema description.
- Label the amount field "Částka s DPH" in the AI review card and the manual
  upload/edit forms so the gross convention is explicit.
- Add unit tests with the user-confirmed figures (22 542,91 @ 21% → 3 912,41,
  base 18 630,50) plus other rates and the no-VAT case.

Existing rows keep their stored (gross) amounts; their vat_amount recomputes
on next edit. A one-shot backfill can correct historical vat_amount if wanted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:27:56 +02:00
BOHA
b665ea1d9b feat(ai): dashboard chat widget with invoice import + budget indicator 2026-06-08 15:01:52 +02:00
BOHA
13d1fc2be3 feat(plan): bulk assignment modal on /plan-work
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 12:11:20 +02:00
BOHA
23ac924472 fix(plan): edit/add the clicked record + layer-aware add in exception mode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:29:52 +02:00
BOHA
9370423fb0 feat(plan): day panel — list/add/edit up to 3 records per cell
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:16:52 +02:00
BOHA
a146fc26a3 feat(plan): grid renders up to 3 stacked records per cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:08:48 +02:00
BOHA
72888bf9cd feat(plan): resolveCell/resolveGrid return arrays; dashboard shows up to 3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:53:26 +02:00
BOHA
512f1fd92b feat(dashboard): show the user's work plan for today
Adds a "Můj plán na dnešek" card near the clock-in: for the logged-in
user it shows today's work-plan entry — category (coloured dot + label),
the project/site (the "where"), the note, and a "součást rozsahu …" badge
only for genuinely multi-day ranges. A muted "Pro dnešek nemáte
naplánováno." empty state when nothing is scheduled.

Server: GET /api/admin/dashboard now returns today_plan, reusing
plan.service resolveCell (override-beats-range precedence) for the current
user + today's local (Prague) date, enriched with the category label +
colour so the widget needs no extra query. Gated by attendance.record /
attendance.manage (the plan permission); omitted otherwise. No migration.

Verified in Chrome: renders today's entry (Práce · OBJ-2026-006 — koko)
with the category colour. tsc -b --noEmit, npm run build, vitest 152/152.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:22:07 +02:00
BOHA
decadd895e fix(ui): stack detail-page header action buttons one-per-row on mobile
Detail-page headers put their action buttons in a flex row with
flexWrap:"wrap", so on a phone the 2-4 buttons (Potvrzení / Zahájit
realizaci / Smazat, etc.) wrapped into a ragged grid.

New shared `headerActionsSx` (ui/PageHeader): on xs it's a full-width
column (each button its own full-width row); from sm up it's the usual
right-aligned wrapping row — desktop unchanged. A StatusChip kept in the
same group stays its natural size (alignSelf) instead of stretching.

Applied to PageHeader's own actions slot and every detail-page header
action group: Order/Offer/Invoice (both views)/Project + the Warehouse
Receipt/Issue/Item/Inventory detail headers.

Verified in Chrome at 430px on OrderDetail: the group is flexDirection
column with all three buttons at the same x, full-width, stacked. tsc -b
--noEmit, npm run build, vitest 152/152 clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:44:22 +02:00
BOHA
54f3c414f5 fix(ui): unify icon badges — solid tile + white glyph via shared helper
The labelled icon-badge tiles were inconsistent: some used a same-hue
light tile + colored glyph (e.g. the Settings 2FA row: success.light tile
+ success.main glyph — low contrast, the reported "badly visible" icon),
and the solid-tile ones used the bright .main fill in dark mode without
darkening, so a white glyph on bright green/amber was low-contrast too
(DashProfile's success badge was ~1.9:1).

Add a single `iconBadgeSx(color)` helper (theme.ts): solid `<color>.main`
fill + white glyph, darkened in dark mode (FILLED_DARK_BG) so white stays
legible; `default` = neutral grey. Applied to every badge so they can't
drift: StatCard, DashActivityFeed, DashProfile (2FA badge), Dashboard
(2FA banner icon), AttendanceHistory (month tile), and the Settings 2FA
row tile (now solid green when required / grey when optional + white lock,
instead of the low-contrast light-green tile). Also switched DashProfile's
backup-codes warning callout from warning.light to a subtle warning wash
so its amber text stays readable in dark mode.

tsc -b --noEmit, npm run build, vitest 152/152 all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:34:06 +02:00
BOHA
ca092c6166 fix(ui): unify colored-control text — white on all filled, both themes
The text color on filled colored controls was inconsistent: contained
primary resolved to white in both schemes, but error/success/warning/info
used a per-scheme contrastText (#fff light, #1a1a1a dark). So in DARK mode
a primary (red) button showed white text while an error (red) button —
e.g. every ConfirmDialog delete confirm, the contained "Smazat", and the
dashboard quick-action tiles — showed near-black text. "Black text on one
red button, white on another."

Unify on a single rule: every FILLED colored surface gets WHITE text/glyph
in BOTH themes. The palette .main stays bright in dark mode (it drives text,
outlined buttons and row tints), but a bright fill makes white illegible, so
filled surfaces drop to a darker fill in dark mode (new FILLED_DARK_BG, ≈ the
light-scheme shades; all ≥4.5:1 with white):
- theme MuiButton: contained error/success/warning/info -> white text +
  darker dark-mode fill (disabled state preserved).
- theme MuiChip: filled error/success/warning/info -> white label + darker
  dark-mode fill (covers StatusChip + status pills).
- StatCard icon badges: white glyph + darker dark-mode tile.

Plus two consistency cleanups:
- Detail-page "Smazat" unified to variant=outlined color=error (Offer +
  Invoice now match Order/Project/Warehouse; the contained->confirm pattern
  stays: subtle trigger, strong contained confirm dialog).
- "Zrušit filtry" reset buttons -> color=inherit (neutral), matching the
  rest of the secondary-button family (were default outlined-primary).

Neutral (color=inherit) text/outlined buttons were already correct (label =
ambient text color) and are untouched. tsc -b --noEmit, npm run build and
vitest 152/152 all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:41:50 +02:00
BOHA
2e4fe2a8fe fix(offers): stop heartbeat 401 spam on dead session + de-dup item keys
Three console issues observed while idle on an offer:

1. Heartbeat flooded the console with 401s. The lock-keepalive interval
   fired apiFetch(...).catch(() => {}) every 10s; apiFetch RESOLVES with a
   401 (it doesn't throw), so .catch never saw it and the interval never
   stopped. Once the refresh token had also expired, every tick 401'd
   forever. Now we inspect the response and clearInterval on a 401 (which
   only occurs after a failed refresh = session truly gone); the healthy
   path still returns 200 and keeps ticking (verified in Chrome).

2. Duplicate React key "item-1" in the line-items table. The form/items/
   sections useState initializers restored the create-mode draft
   UNCONDITIONALLY — even in edit mode — and reused the draft's stale
   item _key values while itemKeyCounter restarts at 0 each load, so an
   added row collided. Now the draft is only restored when !isEdit, and
   restored items are re-keyed with fresh unique _keys.

3. framer-motion "motion() is deprecated" warning — PageEnter used the
   deprecated motion(Box) factory; switched to motion.create(Box).

tsc -b --noEmit, npm run build, vitest 152/152 clean. Console verified
clean in Chrome (only benign dev notices remain).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:28:49 +02:00
BOHA
27c690285a fix(theme): white icon glyphs on solid tiles + single theme storage key
Two issues:

1. Icon badges read as a coloured glyph on a same-hue LIGHT tile
   (bgcolor X.light + color X.main) — low contrast, "badly visible".
   The AttendanceHistory month tile and the Dashboard 2FA-banner icon now
   use a SOLID tile (X.main) + white glyph, matching the StatCard badge
   convention. Readable in both schemes (verified: info.main #1d4ed8 light
   / #3b82f6 dark, error.main, white glyph).

2. Theme reverted to light on F5 while the toggle still showed dark.
   Cause: two independent stores — our ThemeContext key (boha-theme) and
   MUI's own cssVariables mode key (mui-mode). The toggle only wrote
   boha-theme, so on mount MUI restored its stale mui-mode and overrode
   data-theme. Collapsed to a SINGLE source of truth: ThemeContext now
   reads/writes MUI's `mui-mode` key directly (with a one-time migration
   from the legacy boha-theme, which is then removed). Verified: toggle
   dark -> F5 -> stays dark; only `mui-mode` remains in localStorage.

tsc -b --noEmit, npm run build, vitest 152/152 clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:12:20 +02:00
BOHA
8e7ab9158c fix(ui): readable status row tints — subtle washes, not solid .light fills
The per-status row backgrounds used MUI's solid `.light` palette shades
(error.light/success.light/warning.light). Those are LIGHT colors in both
schemes, so in dark mode the row's white text sat on a light fill and was
effectively invisible; in light mode they read as garish saturated blocks.

Replaced with low-alpha washes via the palette channel vars
(rgba(var(--mui-palette-X-mainChannel) / 0.12), 0.18 on hover), which keep
the row's normal theme text colour fully readable in BOTH schemes:

- Offers: completed = subtle green wash; invalidated = faded/muted
  (opacity + secondary text, the original "voided" look) instead of a
  solid red block; expired = subtle amber wash.
- DataTable rowDanger (kit-wide — Warehouse, WarehouseReports): subtle
  error wash for both the desktop row and the mobile card (card keeps its
  error.main border as the danger cue).
- Invoices overdue rows: subtle amber wash.
- Dashboard 2FA banner Card: subtle error wash (keeps the error.main
  border) so the banner copy stays readable in dark mode.

Verified live in Chrome on Offers (completed + invalidated, light + dark):
faint tint + normal readable text, channel switches per scheme. tsc -b
--noEmit, npm run build, vitest 152/152 all clean. (Left untouched: small
icon tiles / callout boxes that pair a .light bg with an explicit dark or
coloured text/glyph — those are readable in both schemes by construction.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:57:58 +02:00
BOHA
03285c6ff6 refactor(css): migrate Login + file icons off legacy vars, delete variables.css
Phase 4 (final) of the "fully MUI" cleanup — no custom stylesheets remain
in src/admin (only the third-party quill.snow.css + leaflet.css imports).

- Login.tsx: every var(--glass-*/bg-*/text-*/accent-*/orb-*/border-*/
  font-*/transition) in its sx/inline styles is now an MUI token
  (bgcolor/color/borderColor: "background.paper" / "text.secondary" /
  "divider" / "primary.main"), a channel-alpha (rgba(var(--mui-palette-
  primary-mainChannel) / a)) for tints, or theme.applyStyles("dark", …)
  for the glass-card shadow. The card sx is now a (theme)=>({}) callback.
- Restores the orb drift animation via the MUI keyframes() helper — the
  @keyframes float had been dropped in Phase 1 (the dead-CSS scan missed
  its use inside an sx animation string); recovered verbatim from git.
- ProjectFileManager: the fallback file-icon color uses
  var(--mui-palette-text-secondary).
- variables.css deleted; its import removed from AdminApp (last one).

tsc -b --noEmit + npm run build clean. applyStyles dark-scheme output and
the channel-alpha pattern were both verified live in Chrome via the Quill
editor, which uses the identical constructs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:45:36 +02:00
BOHA
d424ef8c15 refactor(css): scope Quill editor into styled() and delete offers.css
Phase 3 of the "fully MUI" cleanup.

- New src/admin/ui/RichText.tsx exports two theme-aware styled() wrappers:
  RichEditorRoot (the .admin-rich-editor Quill overrides) and RichTextView
  (the .admin-rich-text-view read-only output). All colors resolve via
  theme.vars; picker/tooltip shadows use theme.applyStyles for the
  light/dark difference; the active-button tint uses the primary channel.
- Dropped the ~170 dead font/size picker rules from the old stylesheet —
  the editor toolbar exposes neither picker. Kept the .ql-font-* content
  classes (shared by editor + view) so legacy stored HTML still renders.
- RichEditor uses <RichEditorRoot> (min-height still via --re-min-height);
  InvoiceDetail + OrderDetail use <RichTextView> for the sanitized output.
- offers.css deleted; its import removed from AdminApp.

tsc -b --noEmit and npm run build clean. Verified the editor in Chrome
(light + dark): toolbar, editor surface, text, icon strokes and min-height
all correct and theme-reactive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:37:11 +02:00
BOHA
2a228ea659 refactor(css): migrate base.css to MUI GlobalStyles and Leaflet to styled()
Phase 2 of the "fully MUI" cleanup — eliminates two more stylesheets.

- base.css -> src/admin/GlobalStyles.tsx: the reset, typography,
  scrollbar/::selection, theme cross-fade timing and all utility
  classes now live in a single theme-aware MUI <GlobalStyles>, rendered
  inside MuiProvider so every rule resolves against theme.vars (verified
  reactive in both schemes). base.css deleted.
- attendance.css -> a styled("div") LocationMap in AttendanceLocation;
  attendance.css deleted.
- App.tsx bootstrap loader is now fully self-contained (inline spinner
  + keyframes, theme-aware background read from the data-theme attr),
  since it renders before MuiProvider/GlobalStyles mount — it no longer
  depends on the removed .admin-spinner / var(--bg-primary).

tsc -b --noEmit and npm run build clean; verified in Chrome (light +
dark): body bg, text, fonts, .text-warning/.link-accent all correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:29:16 +02:00