Commit Graph
38 Commits
Author SHA1 Message Date
nicolabelluti d661680539 feat(oxibadge): implement the review screen's export to ODS
Check, format and test / build (push) Successful in 11m23s
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>
2026-10-10 08:48:05 +02:00
nicolabelluti 5dbb3f51f1 feat(oxibadge): consolidate review toolbar and add a "shift times" action
Check, format and test / build (push) Successful in 11m56s
The per-employee correction buttons (Fix shifts, Add lunch break,
Round) and the day filter used to be scattered across the employee
header. They're now grouped into a single toolbar attached to the top
of the timesheet table: a "Corrections" dropdown (with per-item counts
and an "Apply all corrections" entry) and a "Shift times" panel, plus
the existing day filter and "Undo all changes" button.

"Shift times" adds a fixed offset (in steps of the configured rounding
granularity, ±12h) to every punch in a chosen day range, for either
the current employee or everyone — for when the time clock didn't
follow a daylight-saving change. EmployeeHeader is now just the name
editor and stats; TimesheetTable no longer owns the table's outer
border/background, which moved to the new shared card wrapper.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-09 15:21:47 +02:00
nicolabelluti da5daf5bca fix(oxibadge): accept hostnames and FQDNs for the time clock address
Check, format and test / build (push) Successful in 11m10s
The "Edit address" field in Settings only accepted an IPv4 address
(IPV4.test), rejecting a bare hostname (e.g. "timbratore") or FQDN
(e.g. "timbratore.lan") even though the underlying connection (Rust's
ToSocketAddrs) already resolves those fine. Onboarding had no such
restriction. Replaced the IPv4-only check with isValidHost (IPv4 OR a
standard DNS hostname), dropped the numeric-only keyboard hint on that
field, and reworded the labels/placeholder error to not imply IP-only.

Also makes "Edit address" test the connection before accepting it,
instead of saving unconditionally: ActionButton gained a `disabled`
prop so the confirm/cancel buttons can be locked (with a spinner on
confirm) while the test is in flight, and the edit form now only
closes on a successful test, staying open with the raw error shown
otherwise.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-09 14:05:12 +02:00
nicolabelluti 0c320c39b0 feat(oxibadge): warn before losing unexported review edits, add undo
Check, format and test / build (push) Successful in 10m25s
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>
2026-10-09 13:46:31 +02:00
nicolabelluti 25127bee96 feat(oxibadge): cache employee names locally and let readers be a list
Check, format and test / build (push) Successful in 10m27s
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>
2026-10-08 20:04:37 +02:00
nicolabelluti b65e85f908 feat(oxibadge): add settings screen for shifts, rounding and language
Check, format and test / build (push) Successful in 10m22s
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>
2026-10-08 18:29:57 +02:00
nicolabelluti acd3efeac6 feat(oxibadge): add timesheet review and export screen
Check, format and test / build (push) Successful in 10m16s
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>
2026-10-07 15:56:25 +02:00
nicolabelluti b4cd8e1796 fix(oxibadge): show connection errors instead of failing silently
Check, format and test / build (push) Successful in 10m4s
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>
2026-10-07 13:26:01 +02:00
nicolabelluti 28186c7745 feat(oxibadge): add month-confirmation flow and fetch records/employees
Check, format and test / build (push) Successful in 10m0s
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>
2026-10-07 12:47:36 +02:00
nicolabelluti 99772b9e4a feat(oxibadge): add onboarding flow with i18n and reader preferences
Check, format and test / build (push) Successful in 9m59s
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>
2026-10-07 11:08:02 +02:00
nicolabelluti 268a917a69 [skip ci] Updated the root README.md 2026-10-06 20:31:24 +02:00
nicolabellutiandClaude Sonnet 5 a96561d5f0 Added Oxibadge, a minimal Tauri + Svelte desktop client
Check, format and test / build (push) Successful in 14m6s
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>
2026-10-06 20:26:24 +02:00
nicolabellutiandClaude Sonnet 5 cc8181ebdc Turned the repo into a Cargo workspace, with r701 as a member
Check, format and test / build (push) Successful in 44s
Moving the library into its own r701/ directory makes room for the
Tauri GUI client (Oxibadge) to be added as a sibling workspace member
in a following commit. edition/license/repository are now inherited
from the new workspace root instead of being duplicated per member.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 12:49:54 +02:00
nicolabellutiandClaude Sonnet 5 c2471ed548 Bumped version to 0.2.0
Check, format and test / build (push) Successful in 48s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.2.0
2026-10-04 15:27:43 +02:00
nicolabellutiandClaude Sonnet 5 9a637190b5 Expanded the e2e test suite and fake device data
Check, format and test / build (push) Successful in 46s
- fake_records now generates a full year (2026) of classic workday
  punches (09:00-13:00, 14:00-18:00) for the employees in fake_users;
  this spans many 1024-byte blocks, exercising the multi-block
  streaming path
- fake_users/fake_records are now public so tests can assert against
  them instead of hardcoded expectations
- scoped #[allow(clippy::unwrap_used)] to only the functions that
  actually use unwrap, instead of a blanket file-level allow
- added gets_total_record_count, streams_records, and
  gets_none_for_unknown_employee tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-04 15:25:18 +02:00
nicolabellutiandClaude Sonnet 5 9977db0e46 Added an e2e test harness with a fake R701 device
Check, format and test / build (push) Successful in 47s
tests/mock/mod.rs emulates the device's TCP protocol (ping, get_name,
get_total_record_count, get_record_bytes), grounded in the reverse
engineering notes at
https://nicolabelluti.me/posts/2024/05/studying-a-communication-protocol/,
so tests/e2e.rs can exercise the real R701 client against it instead of
needing a physical reader. Starts with one test (connect + ping);
more scenarios (get_name, record streaming, error cases) to follow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-04 14:47:29 +02:00
nicolabellutiandClaude Sonnet 5 f697660ff4 Documented the public API and removed the missing_errors_doc allows
Check, format and test / build (push) Successful in 42s
Added rustdoc comments to every public item (R701, Record, Clock,
RecordIterator and their methods/fields/variants), each with an
# Errors section where applicable, so the clippy::missing_errors_doc
allows are no longer needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 15:07:40 +02:00
nicolabelluti d8c6440a59 Re-added npm to the CI/CD pipeline
Check, format and test / build (push) Successful in 33s
2026-10-01 14:49:02 +02:00
nicolabellutiandClaude Sonnet 5 e0e453c9c5 Cleaned up the protocol layer and metadata
Check, format and test / build (push) Failing after 5s
- request() now reads exactly the expected response length with
  read_exact instead of a single non-guaranteed fill_buf, fixing a
  latent bug where a response split across multiple TCP packets could
  be mistaken for malformed data; the per-method length checks are now
  redundant and were removed
- sequence_number wraps explicitly via wrapping_add instead of
  relying on implicit (and debug-vs-release inconsistent) overflow
  behavior after 65536 requests
- get_name validates UTF-8 strictly instead of silently lossy-decoding
  invalid bytes; on invalid data it now returns the FromUtf8Error
  itself (byte-level \0 trimming moved after the UTF-8 check), letting
  the caller choose to fail or fall back to a lossy rendering
- removed the unused Default derive from Clock and Record
- removed npm from the CI image (no JS/Node in this repo), fixed a
  typo in a CI step name
- fixed the Cargo.toml license field to the actual SPDX identifier
  (AGPL-3.0-or-later, per the notice at the end of LICENSE) instead of
  the free-text "GNU AGPLv3.0"

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 14:43:50 +02:00
nicolabellutiandClaude Sonnet 5 9af4a82e39 Made R701 shareable via an internal Mutex, allowing get_name to be called while streaming records
Check, format and test / build (push) Successful in 34s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 14:02:47 +02:00
nicolabellutiandClaude Sonnet 5 1745ecc05e Made the library async using tokio and the iterator return a Result
Check, format and test / build (push) Successful in 33s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 13:28:32 +02:00
nicolabellutiandClaude Sonnet 5 93f9e9d3af Updated the Rust version used by the CI/CD and silenced clippy's missing_errors_doc lint
Check, format and test / build (push) Successful in 1m13s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 12:44:07 +02:00
nicolabelluti 07ef2a96bc Updated the README.md and the tests
Check, format and test / build (push) Failing after 20s
v0.1.0
2024-07-31 13:13:07 +02:00
nicolabelluti b2b5f0c66c Removed Nix and the sample-main, added a CI/CD pipeline and updated the README.md
Check, format and test / build (push) Failing after 49s
2024-07-31 12:00:34 +02:00
nicolabelluti 987af19ec8 Added a sample main function to get the data 2024-06-16 17:57:32 +02:00
nicolabelluti 35e75745db Put the lib into a workspace 2024-06-16 17:48:15 +02:00
nicolabelluti 0dd05c0d91 Avoided two overflows 2024-06-16 14:06:13 +02:00
nicolabelluti 78248eb2a5 Renamed a variable 2024-05-13 13:25:17 +02:00
nicolabelluti 4d9361e383 Return a None if the name is an empty String 2024-05-13 13:05:27 +02:00
nicolabelluti 85e07886e9 Added Nix Flake 2024-05-06 12:27:05 +02:00
nicolabelluti ba20f457f8 Reversed the direction of the iterator 2024-05-06 11:31:41 +02:00
nicolabelluti f0ac5fe766 Added the RecordIterator struct 2024-05-05 22:16:48 +02:00
nicolabelluti e2d1f141d6 Added serde as an optional dependency 2024-05-05 21:55:52 +02:00
nicolabelluti 6f0d2587b4 Added the Record struct 2024-05-05 21:49:00 +02:00
nicolabelluti 31117fb479 Updated the README.md 2024-05-05 19:10:06 +02:00
nicolabelluti bc75ec6ebe Added the R701 struct 2024-05-05 19:06:12 +02:00
nicolabelluti b13051d408 Added the Rust base 2024-05-05 16:45:55 +02:00
nicolabelluti 5166b8dabd Initial commit 2024-05-05 16:36:41 +02:00