Added export/import features

This commit is contained in:
2026-09-19 13:21:43 +02:00
parent f61b9025eb
commit 870457955b
14 changed files with 3363 additions and 14 deletions

View File

@@ -15,13 +15,14 @@ import {
getEntries,
getHistory,
getLastClosedDay,
importBackup,
patchDay,
patchEntry,
putDay,
setEntryStart,
} from './db.js';
import { buildModel, suggestClients, suggestTasks, TRANSPORT_TASK } from './predict.js';
import { settings } from './settings.svelte.js';
import { loadSettings, settings } from './settings.svelte.js';
import { atLocalTime, HOUR, localDateKey, roundTs, weekday } from './time.js';
export const dayState = $state({
@@ -37,6 +38,8 @@ export const dayState = $state({
/** True once the configured end time has passed and the worker has not answered. */
needsClosePrompt: false,
loaded: false,
/** Blocks clock reconciliation while an imported snapshot is installed. */
importing: false,
});
export const running = () => dayState.entries.find((e) => e.endTs == null) ?? null;
@@ -45,12 +48,24 @@ export const running = () => dayState.entries.find((e) => e.endTs == null) ?? nu
/** Retention runs once per session, not on every 30 s tick. */
let cleanedUpFor = null;
const pendingReconciliations = new Set();
/**
* Bring stored state in line with the clock. Safe to call often — it is invoked on
* load, on tab focus, and from a 30 s tick.
*/
export async function reconcile(now = Date.now()) {
export function reconcile(now = Date.now()) {
if (dayState.importing) return Promise.resolve();
const pending = reconcileDay(now);
pendingReconciliations.add(pending);
pending.then(
() => pendingReconciliations.delete(pending),
() => pendingReconciliations.delete(pending),
);
return pending;
}
async function reconcileDay(now) {
dayState.today = localDateKey(now);
// Applying retention here is what makes the setting mean anything; leaving it to
@@ -203,6 +218,34 @@ export async function removeEntry(id) {
await refresh();
}
/**
* Apply an imported dataset, then rebuild everything that was derived from the old
* one: the settings rune (replace mode adopts the file's settings), the prediction
* model, and the resume.
*
* @param {{days: Array, entries: Array, settings: object|null}} payload
* @param {'merge'|'replace'} mode
*/
export async function applyImport(payload, mode) {
if (dayState.importing) throw new Error('Un import est déjà en cours.');
dayState.importing = true;
try {
// Let an already-running clock operation finish before replacing its data.
// The guard above prevents subsequent ticks/focus events during the import.
await Promise.allSettled([...pendingReconciliations]);
const result = await importBackup(payload, mode);
if (mode === 'replace') await loadSettings();
// Do not immediately purge old imported days or create a new working day.
// Normal retention resumes next session/day; manual purge remains available.
dayState.today = localDateKey();
cleanedUpFor = dayState.today;
await refresh();
return result;
} finally {
dayState.importing = false;
}
}
export async function endDay(now = Date.now()) {
if (!dayState.day) return;
const date = dayState.day.date;