Drop the gradient "Odin" wordmark from the new-conversation hero and make the
personalized greeting the centerpiece in Fraunces (optical-sized serif), with
an italic Fraunces description — a refined, literary, claude-like treatment.
Loads Fraunces (upright + italic) via the existing Google Fonts link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Time-based Czech greeting + the user's first name (e.g. "Dobrý večer, Admin")
above the big gradient "Odin" wordmark, with a stylish description line. Drop
the redundant mark from the hero (it lives on the nav item + thinking state).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mark already lives on the nav menu item; the conversation sidebar's brand
no longer duplicates it — just a larger gradient "Odin" wordmark above the new
chat button.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the small empty-state with a large centred hero — the animated Odin
mark + a big gradient "Odin" wordmark (responsive clamp font) and the helper
line beneath.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the chat-bubble glyph on the "Odin" nav item with the OdinMark, and
make the mark's SVG sizing !important so the nav's `& svg { width:18 }` rule
can't shrink it (its `size` prop stays authoritative everywhere).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A self-contained animated SVG mark in BOHA red: a 4-point AI spark (Odin) that
slow-rotates and breathes, a data node orbiting a faint ring (the assistant at
the centre of the business system). An "idle" and a "thinking" state (faster
orbit + red glow); honours prefers-reduced-motion. Used as the sidebar brand,
the empty-state hero, and the busy/thinking indicator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace top tabs with a left sidebar (New chat + scrollable conversation
list, per-row rename/delete menu, active highlight), chat on the right.
- Fix duplicate-empty-conversations bug: "Nová konverzace" now just opens an
empty composer; a DB row is created only on the first successful message
(and a failed AI call no longer leaves an orphan conversation). Repeated
clicks never spawn empties, and nothing empty survives a refresh.
- Land on a fresh new chat on mount; deleting the open conversation returns
to new chat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Final review caught two minor issues:
- saveInvoice invalidated ["received-invoices"], which matches no query (the
received list/stats are keyed ["invoices","received",…]); use ["invoices"]
so the list/stats actually refresh after an import.
- The Save button hit /received-invoices (requires invoices.create) while Odin
is gated only on ai.use; disable Uložit (with a hint) when the user lacks
invoices.create so the UI never offers a 403 action. Dormant today (ai.use is
admin-only and admin bypasses checks) but correct going forward.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Add four presentational components + shared types under
src/admin/components/odin/ as props-driven building blocks for the
multi-conversation Odin page (Task 6 wires state/queries).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove single-thread getChatHistory/appendChatMessages/clearChatHistory and the
three /history routes; add listConversations, createConversation, getConversation-
Messages, appendConversationMessages, renameConversation, deleteConversation with
ownership checks and auto-title; wire six /conversations[/:id] routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
Server-side, per-user chat history (the thread now survives reload):
- new ai_chat_messages table + migration; getChatHistory/appendChatMessages/
clearChatHistory; GET/POST/DELETE /api/admin/ai/history (ai.use-guarded);
service + HTTP tests (isolation, ordering, clear, perm, validation).
- DashAssistant loads the thread on mount and persists each turn; "Vymazat"
clears it. gcTime:0 so each mount re-reads the DB (no stale-cache resurrection);
composer gated until history settles + seed flag flipped on submit (no
seed/submit race clobbering a freshly-sent turn).
UX + correctness fixes from review/feedback:
- review cards moved OUT of the scrollable thread into a "Faktury k potvrzení"
section so Uložit is always reachable; chat auto-scroll no longer fires on
field edits.
- chat bubble text colour set on the Typography (GlobalStyles pins `p` to
text.secondary, which was overriding the inherited bubble colour → unreadable).
- never clear input/staged PDFs on a failed submit (rollback + keep), so a
budget error can't wipe staged invoices; roll back the optimistic user turn.
- stable attachment ids (no crypto.randomUUID — undefined over plain HTTP).
- editable Datum splatnosti + Popis fields; client-side extraction summary.
Security: DashProfile clipboard copy now uses an execCommand fallback (works
over plain HTTP) and only shows the success toast when the copy actually
succeeded — previously it falsely claimed to copy 2FA codes / the TOTP secret.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final review flagged that the multipart limits set only fileSize, so a
single /extract-invoices request could carry unbounded PDF parts. The
per-batch budget re-check already bounds total spend, but this bounds the
work (one vision call per file) of any single request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Admin-only Claude (Sonnet 4.6) chat embedded on the dashboard under the
welcome text. Attach received-invoice PDFs → extract (vision + structured
output) → confirm/edit → save via the existing received-invoices endpoint.
$50/month admin-editable budget enforced via an ai_usage table + pre-call
guard. New @anthropic-ai/sdk dep, ANTHROPIC_API_KEY, ai.use permission;
one migration. No system-data tools (Phase 2). Ships as v2.1.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The attendance card listed only employees with a record today (present /
break / clocked-out / on-leave). Now every active employee appears;
those with no record today show as "absent" (Nepřítomen), so the card
reflects the whole team. Loads active users (was a bare count) and
appends the unrecorded ones.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously notes were hidden when a cell held 2-3 records (a density
choice). Show each record's note regardless of count — drop the
now-unused showNote prop on PlanRangeChips.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Assign one project/category to many employees over a date range in one
action. "Hromadné přiřazení" on /plan-work: employee checklist +
include-weekends toggle, mirrors the single-create form. Creates range
entries grouped by contiguous eligible days per employee (weekends-off →
per-work-week chunks); per-day cap skips days already at 3; 92-day range
cap. Reuses createEntry; summary audit + result counts. No migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Assign one project/category to many employees over a date range in one
action. Modal mirrors single-create + employee checklist + "include
weekends" toggle. Stores range entries grouped by contiguous eligible
days per employee (weekends-off → per-work-week chunks); per-day cap
skips days already at 3. POST /plan/entries/bulk + bulkCreateEntries
(reuses createEntry); summary audit + result counts. No migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A plan cell now holds up to 3 records: additive assignments + day
exceptions, via the layered entries/overrides model (no migration).
resolveCell/resolveGrid return arrays; cap enforced per layer on create;
grid renders up to 3 stacked; cell editor is a day panel (list/add/edit,
layer-aware add); dashboard "today" card shows up to 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Five tasks: (1) backend cap + additive overrides, (2) resolve->arrays +
coordinated frontend shape change + dashboard, (3) grid stacked render,
(4) day-panel cell editor, (5) v2.0.5 release. Backend tasks are TDD
against the real test DB; frontend tasks gate on tsc/build/Chrome (no
component-test harness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feature A of 2: lets a plan cell hold up to 3 records (additive
assignments + day exceptions) via the layered entries/overrides model,
no migration. resolveCell/resolveGrid return arrays; cap enforced per
layer on create; grid shows up to 3 stacked; cell editor becomes a
day panel; dashboard today-card shows up to 3. Feature B (bulk create)
decisions captured as deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The order delete modal sends { delete_files }, but the route ignored the
body and deleteOrder never touched the NAS — so the project folder of an
order-spawned project always stayed on the share (the only way to delete
such a project is via its order, since deleteProject blocks order-linked
projects). Project delete (standalone projects) already worked.
- orders route: read delete_files from the body, pass to deleteOrder.
- orders.service deleteOrder(id, deleteFiles): select project_number and,
after the DB transaction commits, best-effort deleteProjectFolder for
each linked project when the flag is set. Non-fatal; idempotent.
- tests: NasFileManager.deleteProjectFolder coverage — removes by number
prefix, no-op when absent, false when not mounted (158 -> 161 tests).
Bump version to 2.0.4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Manual project creation already creates the 02_PROJEKTY/<number>_<name>
folder (projects.service.createProject). Projects created from an order —
both from an accepted offer (createOrderFromQuotation) and a manual
offer-less order (createOrder) — did not, so they had no NAS folder until
a user opened the project and clicked "create folder" or uploaded a file.
- orders.service: best-effort, non-fatal createProjectFolder after each
project-creating transaction commits (mirrors createProject; gated on
isConfigured(), logs on failure, never rolls back the order).
- projects.service: log when the existing auto-create returns false
(was silently discarded — CLAUDE.md known-issue #4).
- nas-file-manager: optional constructor basePath seam for tests.
- tests: NasFileManager.createProjectFolder coverage — naming, idempotency,
Czech diacritics, empty name, not-mounted (152 -> 158 tests).
Bump version to 2.0.3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New "Vaše dnešní zařazení" dashboard card: shows the logged-in user's
work-plan entry for today (category + project/site + note, multi-day
range badge), with a muted empty state.
No DB migrations. tsc -b --noEmit, npm run build, vitest 152/152 clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>