Replaces the export button's placeholder (which only dbg!-printed the
payload) with a real implementation: one employee exports a single
.ods file, more than one bundles a .zip of one .ods per employee,
written wherever the user picks via the native "save" dialog
(tauri-plugin-dialog).
The "hours worked" column is a live ODF formula with a cached fallback
value, not just a static number, so the file stays correct if edited
afterward in LibreOffice; the formula and the cached value both treat
a day's own missing punches as 0 in the subtraction, matching how a
day with only an entry and an exit (no lunch break) still nets out to
the right span. Column headers are sent by the frontend (translated
via its own i18n), not hardcoded in Rust. A stacked slot (more than
one punch the device recorded in the same shift) exports every punch,
one per line in the cell, instead of only the first.
Also closes a real gap surfaced while building this: the new-employees
screen already blocked "Continue" for a blank name, but not for a name
the device sent as invalid UTF-8 (lossily converted to text containing
the Unicode replacement character on the Rust side) — such a name is
now flagged and blocks continuing too, so every employee reaching the
review/export flow is guaranteed to have a real, displayable name.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
The review screen now tracks whether there are edits not yet exported
and asks for confirmation before discarding them: clicking back, or
closing the app window entirely (intercepted via onCloseRequested,
which needed a core:window:allow-destroy capability that wasn't
granted by core:default). The confirmation dialog (ConfirmDialog.svelte)
is generic, built on a shared ModalFrame extracted from ExportModal so
the two don't duplicate the backdrop/box styling.
Also adds undo: a per-row button (icon-only, left of the note, shown
when that day has edits) reverts just that day instantly, and a
per-employee "undo all" (icon in a red box, last among the header's
tools) reverts every day for the current employee after a confirmation
dialog, since that one can't be undone itself.
ActionButton moves out of review/ next to IconButton/PillButton, since
ConfirmDialog (outside the review feature) now uses it too.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Employee names are now cached locally (employees.toml, keyed by reader
index then employee id) instead of being re-fetched from the time
clock on every month: load_employees only asks the device for ids not
already in the cache. When a month has employees the cache doesn't
know yet, a new screen (NewEmployees.svelte) lets the user name them
before continuing to the review screen; the chosen names are local to
Oxibadge only, never written back to the device. The same cache can be
managed afterwards from a drawer reachable from the time clock card in
Settings ("Gestisci dipendenti"), reusing the rename-in-row pattern
from the original mockup.
Settings.readers is a list again (was flattened to a single `reader`
object) so multiple time clocks can be supported later; only the first
one is used today.
Also:
- buildEmployees() now derives the employee list from the month's
records instead of from every cached name ever seen, so the review
screen no longer shows employees unrelated to the selected month.
- Renaming an employee from the review screen's pencil button now
actually persists to the cache (the callback existed but was never
wired up).
- Translated to English a number of Rust/TS/Svelte comments that had
been written in Italian; only the i18n locale files should contain
Italian.
- Minor dedup: extracted PillButton.svelte (was duplicated inline in
ReaderCard), promoted the repeated .mono CSS rule to app.css, moved
initials() out of the review feature into $lib/text.ts.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Add a settings screen (gear button on the month-confirmation screen)
to configure the expected shift times, the rounding step, the
interface language, and the time clock address — replacing the old
onboarding-only connection flow. Settings persist to the same
preferences.toml as the reader address, under a single load_settings/
save_settings command pair.
The review screen's "fix shifts" action now redistributes stacked
punches by nearest configured shift time instead of blind position,
and a stacked cell manually cleared down to one entry no longer
leaves a dangling empty input.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Add the month-review screen shown after records/employees load: an
editable per-employee timesheet table with issue/rounding detection,
bulk fixes (reorder stacked punches, add lunch break, round to step),
and an export flow (single employee or all) wired to a new Rust
export_timesheet command. Split into focused components under
review/ instead of one large file, with shared i18n keys and a new
export_timesheet IPC command.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
load_records/load_employees failures were only logged to the console
and silently reset the UI to the month picker. Errors now flow through
a shared store (src/lib/error.ts) into an ErrorBanner, reused by both
Onboarding and ConfirmMonth, with the raw message passed straight
through from Rust (no stable-code classification layer — these are
mostly plain I/O errors, not worth categorizing).
i18n stays grouped per screen as before, with error-related strings
split into their own top-level "errors" group.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Adds the screen after onboarding: pick a month/year (ConfirmMonth), show
loading screens while the device is queried, then download that month's
records and resolve each distinct employee's name, printing both to
stdout (debug pretty-print) before landing on the placeholder "done"
screen.
- r701: RecordIterator's boxed stream is now Send, required for it to
cross into a Tauri command's future.
- oxibadge: load_records/load_employees commands, sharing the existing
3-retry connect_with_retries helper. load_records uses skip_while/
take_while on the device's newest-first stream to grab only the
target month without walking its whole history, then a single
collect + in-place Vec::reverse() back to chronological order.
- Shared LoadingScreen/Intro components extracted so future screens
reuse the same look without duplicating markup/CSS.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Adds the first real screen: a setup form for the device's host/port,
tested against the real device (3 retries) and persisted to
preferences.toml before showing a placeholder "done" screen.
- Rust: test_connection/save_preferences/load_preferences commands;
preferences stored as [[reader]] in TOML so more readers can be
added later without a format change.
- Svelte: split the monolithic page into reusable components
(Screen, Column, Field, Button, ErrorBanner, Hint) under
src/lib/components, sharing design tokens from app.css.
- Automatic it/en i18n via svelte-i18n, locale detected from
navigator.language.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Scaffolds the oxibadge crate/frontend as a new workspace member, stripped
down to a bare hello-world, and
extends the CI pipeline
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>