feat(oxibadge): cache employee names locally and let readers be a list
Check, format and test / build (push) Successful in 10m27s
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>
This commit is contained in:
@@ -11,18 +11,32 @@
|
||||
import TimesheetReview from '$lib/components/TimesheetReview.svelte';
|
||||
import SettingsButton from '$lib/components/SettingsButton.svelte';
|
||||
import Settings from '$lib/components/Settings.svelte';
|
||||
import NewEmployees from '$lib/components/NewEmployees.svelte';
|
||||
import { buildEmployees, type AttendanceRecord } from '$lib/components/review/fromRecords';
|
||||
import type { NewEmployee } from '$lib/components/new-employees/logic';
|
||||
import type { Shifts } from '$lib/shifts';
|
||||
import type { Settings as SettingsData } from '$lib/settings';
|
||||
import { PRIMARY_READER_INDEX, type EmployeeCache, type EmployeeNames } from '$lib/employees';
|
||||
|
||||
type Step = 'loading' | 'setup' | 'confirm-month' | 'loading-records' | 'loading-employees' | 'review' | 'settings';
|
||||
type Step =
|
||||
| 'loading'
|
||||
| 'setup'
|
||||
| 'confirm-month'
|
||||
| 'loading-records'
|
||||
| 'loading-employees'
|
||||
| 'new-employees'
|
||||
| 'review'
|
||||
| 'settings';
|
||||
|
||||
let step = $state<Step>('loading');
|
||||
let selectedYear = $state(0);
|
||||
let selectedMonth = $state(0);
|
||||
let records = $state<AttendanceRecord[]>([]);
|
||||
let fullEmployeeCache = $state<EmployeeCache>({});
|
||||
let employeeCache = $state<EmployeeNames>({});
|
||||
let employeeNames = $state<Record<string, string>>({});
|
||||
// Placeholder: sovrascritti da load_settings prima che la revisione sia raggiungibile.
|
||||
let newEmployees = $state<NewEmployee[]>([]);
|
||||
// Placeholder: overwritten by load_settings before the review screen is reachable.
|
||||
let shifts = $state<Shifts>({ in1: '', out1: '', in2: '', out2: '', round_step_min: 0 });
|
||||
|
||||
const period = $derived(`${monthName($locale, selectedMonth)} ${selectedYear}`);
|
||||
@@ -31,7 +45,7 @@
|
||||
onMount(async () => {
|
||||
const settings = await invoke<SettingsData>('load_settings');
|
||||
shifts = settings.shifts;
|
||||
step = settings.reader.host ? 'confirm-month' : 'setup';
|
||||
step = settings.readers[0] ? 'confirm-month' : 'setup';
|
||||
});
|
||||
|
||||
function openSettings() {
|
||||
@@ -52,13 +66,50 @@
|
||||
try {
|
||||
records = await invoke('load_records', selection);
|
||||
step = 'loading-employees';
|
||||
employeeNames = await invoke('load_employees', { records });
|
||||
step = 'review';
|
||||
|
||||
fullEmployeeCache = await invoke<EmployeeCache>('load_employee_cache');
|
||||
employeeCache = fullEmployeeCache[PRIMARY_READER_INDEX] ?? {};
|
||||
const distinctIds = [...new Set(records.map((r: AttendanceRecord) => r.employee_id))];
|
||||
const knownIds = distinctIds.filter((id) => String(id) in employeeCache);
|
||||
const newIds = distinctIds.filter((id) => !(String(id) in employeeCache));
|
||||
|
||||
if (newIds.length > 0) {
|
||||
const fetched = await invoke<Record<string, string>>('load_employees', {
|
||||
records,
|
||||
known: knownIds
|
||||
});
|
||||
newEmployees = newIds.map((id) => ({ id, name: fetched[String(id)] ?? '' }));
|
||||
step = 'new-employees';
|
||||
} else {
|
||||
employeeNames = employeeCache;
|
||||
step = 'review';
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
step = 'confirm-month';
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges `updates` into the employee cache (local state + on disk) and returns the merged names. */
|
||||
async function mergeEmployeeNames(updates: Record<string, string>): Promise<Record<string, string>> {
|
||||
const merged = { ...employeeCache, ...updates };
|
||||
employeeCache = merged;
|
||||
employeeNames = merged;
|
||||
const nextFullCache = { ...fullEmployeeCache, [PRIMARY_READER_INDEX]: merged };
|
||||
fullEmployeeCache = nextFullCache;
|
||||
await invoke('save_employee_cache', { cache: nextFullCache });
|
||||
return merged;
|
||||
}
|
||||
|
||||
async function onNewEmployeesContinue(list: { id: number; name: string }[]) {
|
||||
const updates = Object.fromEntries(list.map((e) => [String(e.id), e.name]));
|
||||
step = 'review';
|
||||
await mergeEmployeeNames(updates);
|
||||
}
|
||||
|
||||
async function onEmployeeRenamed(id: number, name: string) {
|
||||
await mergeEmployeeNames({ [String(id)]: name });
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if step === 'review'}
|
||||
@@ -69,7 +120,10 @@
|
||||
shift={shifts}
|
||||
onback={() => (step = 'confirm-month')}
|
||||
onexport={(payload) => invoke('export_timesheet', { payload })}
|
||||
onrename={onEmployeeRenamed}
|
||||
/>
|
||||
{:else if step === 'new-employees'}
|
||||
<NewEmployees employees={newEmployees} oncontinue={onNewEmployeesContinue} />
|
||||
{:else if step === 'settings'}
|
||||
<Settings
|
||||
onback={closeSettings}
|
||||
|
||||
Reference in New Issue
Block a user