views/SettingsView.qml — the dashboard's Settings tab — plus the full
omatrack.py command reference.
views/SettingsView.qml organizes the settings into four sections —
Billing (currency, hourly rate), Company (company name, address),
Tax & numbering (tax rate, no. prefix, next number), and Footer
(footer text) — each introduced by a PanelSectionHeader under a
PanelSeparator. Every field is a shared components/LabeledField.qml
(body-size label above the kit TextField, muted helper line below),
laid out on an equal-width grid (2/2/3/1 columns, content capped at
720px) so all inputs in a row are the same size. The section stack
scrolls when the window is short; the save row, status texts, and the
data file path stay pinned at the bottom.
Local field copies sync from the service on load and whenever
settings changes (only while the tab is not dirty, so edits are never
clobbered). Any edit marks the tab dirty (unsaved changes chip);
Save validates locally, then pushes one patch via
service.saveSettings(patch) → settings-set --json; a successful save
shows a Settings saved flash for two seconds.
| Field | Local validation | Notes |
|---|---|---|
| Currency | — (helper: non-empty, ≤ 8 chars, trimmed) | empty field saves EUR |
| Hourly rate | finite, ≥ 0 | number |
| Company name | — | printed in the invoice header |
| Address | — | printed under the company name |
| Tax rate % | finite, 0–100 | helper accepts ≥ 0; the UI caps at 100 |
| No. prefix | — | default INV- |
| Next number | integer ≥ 1 | helper: integer ≥ 0; the UI requires ≥ 1 so numbering never restarts at 0 |
| Footer | — | optional line at the bottom of every invoice |
Helper-side settings-set --json '{...}' accepts a partial patch over
the top-level keys and/or the invoice object; unknown fields error.
The data file path is printed at the bottom of the tab
(service.statePath).
python3 omatrack.py <command> [options] — one JSON line on stdout,
{"ok": true, ...} (exit 0) or {"ok": false, "error": "..."} (exit 1).
Every command takes the flock (shared for reads, exclusive for writes);
mutation commands return the compact view (state minus entries +
lastUsed, day totals, entryCount).
| Command | Arguments | Notes |
|---|---|---|
init |
[--print-dir] |
create state file + directories (idempotent); --print-dir prints the state dir |
state |
— | ensure state; print the current state view |
start |
--client-id --project-id --description [--billable 0|1] |
--description is mandatory (blank/whitespace → description is required); error if a timer is already running; persists active immediately (billable defaults 1) |
stop |
[--at ISO-8601] |
closes active into entries; the stored duration excludes paused segments (stopping while paused closes at the pause moment); naive --at is UTC |
pause |
— | freezes active: paused: true, pauseStart = now; rejects no timer running / timer is already paused |
resume |
— | banks the pause segment into pausedSeconds and clears the flags; rejects no timer running / timer is not paused |
entries |
[--from --to] [--client-id] [--project-id] [--billable 0|1] [--search] [--offset N] [--limit N] |
start-DESC list; limit clamped 1..500 (default 50); plus total, totalSeconds, billableSeconds, nextOffset (null on last page) |
report |
--group-by day|client|project [--from --to] [--client-id] [--project-id] [--offset N] [--limit N] |
aggregated rows, paginated (limit clamped 1..500, default 50); billable/search are not accepted (all matched entries count); totalSeconds/billableSeconds cover the whole matched set, not just the page |
client-add |
--name |
case-insensitive duplicate rejected |
client-update |
--id --name |
same duplicate check |
client-delete |
--id |
blocked while referenced by projects/entries |
project-add |
--client-id --name |
unique per client |
project-update |
--id [--name] [--client-id] |
--client-id moves the project |
project-delete |
--id |
blocked while referenced by entries |
entry-add |
--start YYYY-MM-DD --time HH:MM[:SS] --minutes N --client-id --project-id [--description] [--billable 0|1] |
local date/time → UTC start; end = start + minutes |
entry-update |
--id [--start YYYY-MM-DD] [--time] [--minutes] [--client-id] [--project-id] [--description] [--billable 0|1] |
missing date/time fall back to the entry's current values; errors if nothing changed |
entry-delete |
--id |
unconditional |
settings-set |
--json '{...}' |
partial patch (see table above) |
export |
--format csv|html --out PATH [--from --to] [--client-id] [--project-id] [--billable 0|1] |
read-only; CSV/HTML timesheet (see reports-export.md) |
invoice |
--client-id --from --to [--out PATH] |
billable entries, per project, at the configured rate (see invoices.md) |
The service declares the plugin's single IpcHandler { target: "omatrack" }
(the popup runs manageIpc: false so it never collides):
| Call | Returns |
|---|---|
ping |
"ok" |
status |
{ running, paused, client, project, description, billable, elapsedSeconds, daySeconds, dayBillableSeconds, entryCount, lastResult } — elapsedSeconds excludes paused time; lastResult is the previous start/stop/pause/resume's response (optimistic IPC: those calls return before the helper finishes; the outcome lands in the next status) |
start / stop / pause / resume / toggle |
optimistic; start takes no arguments — it starts lastUsed (its description is required: an empty last-used description returns no description on file (start once from the UI)), else the first client's first project; stop/pause/resume return not running / not paused / already paused as applicable; toggle = start when idle, else pause/resume |