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>
102 lines
2.7 KiB
Svelte
102 lines
2.7 KiB
Svelte
<script lang="ts">
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { _ } from 'svelte-i18n';
|
|
import { error, setError, clearError } from '$lib/error';
|
|
import type { Settings } from '$lib/settings';
|
|
import Column from './Column.svelte';
|
|
import Intro from './Intro.svelte';
|
|
import Field from './Field.svelte';
|
|
import Button from './Button.svelte';
|
|
import ErrorBanner from './ErrorBanner.svelte';
|
|
import Hint from './Hint.svelte';
|
|
|
|
let { onconnected }: { onconnected: (cfg: { host: string; port: number }) => void } = $props();
|
|
|
|
let host = $state('');
|
|
let port = $state(5005);
|
|
let testing = $state(false);
|
|
|
|
const trimmedHost = $derived(host.trim());
|
|
const empty = $derived(trimmedHost === '');
|
|
const disabled = $derived(empty || testing);
|
|
const btnLabel = $derived(
|
|
testing ? $_('onboarding.button.testing') : $error ? $_('onboarding.button.retry') : $_('onboarding.button.idle')
|
|
);
|
|
|
|
async function testAndSave() {
|
|
if (disabled) return;
|
|
testing = true;
|
|
clearError();
|
|
try {
|
|
await invoke('test_connection', { host: trimmedHost, port });
|
|
const settings = await invoke<Settings>('load_settings');
|
|
await invoke('save_settings', { settings: { ...settings, readers: [{ host: trimmedHost, port }] } });
|
|
onconnected({ host: trimmedHost, port });
|
|
} catch (err) {
|
|
setError(err);
|
|
} finally {
|
|
testing = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<Column>
|
|
<Intro title={$_('onboarding.title')} subtitle={$_('onboarding.subtitle')} />
|
|
|
|
<form
|
|
class="form"
|
|
onsubmit={(event) => {
|
|
event.preventDefault();
|
|
testAndSave();
|
|
}}
|
|
>
|
|
<div class="row">
|
|
<Field
|
|
id="ox-host"
|
|
label={$_('onboarding.host_label')}
|
|
bind:value={host}
|
|
placeholder="192.168.1.100"
|
|
oninput={clearError}
|
|
>
|
|
{#snippet icon()}
|
|
<svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
|
|
<rect x="3" y="4" width="18" height="12" rx="2" /><path d="M8 20h8M12 16v4" />
|
|
</svg>
|
|
{/snippet}
|
|
</Field>
|
|
|
|
<Field
|
|
id="ox-port"
|
|
label={$_('onboarding.port_label')}
|
|
bind:value={port}
|
|
type="number"
|
|
placeholder="5005"
|
|
oninput={clearError}
|
|
/>
|
|
</div>
|
|
|
|
<Button type="submit" label={btnLabel} faded={empty} busy={testing} {disabled} />
|
|
|
|
{#if $error}
|
|
<ErrorBanner title={$_('errors.title')} message={$error} />
|
|
{/if}
|
|
|
|
<Hint text={$_('onboarding.hint')} />
|
|
</form>
|
|
</Column>
|
|
|
|
<style>
|
|
.form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.row {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) 108px;
|
|
gap: 12px;
|
|
align-items: start;
|
|
}
|
|
</style>
|