Files
pardis/src/lib/editing.js
2026-09-19 21:53:10 +02:00

106 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { db, deleteEntry, getDay, getEntries } from './db.js';
import { localDateKey } from './time.js';
const conflict = () => new Error('Ce poste a changé. Fermez puis ouvrez à nouveau la correction.');
const fields = ['id', 'date', 'seq', 'kind', 'task', 'client', 'startTs', 'endTs'];
async function readOriginal(original) {
const entry = await db.entries.get(original.id);
if (!entry || fields.some((key) => entry[key] !== original[key])) throw conflict();
const day = await getDay(entry.date);
if (!day) throw conflict();
return { entry, day, entries: await getEntries(entry.date) };
}
function content(values) {
if (!['work', 'travel', 'lunch'].includes(values.kind)) throw new Error('Choisissez un type de poste.');
const task = values.kind === 'lunch' ? 'Pause déjeuner' : String(values.task ?? '').trim();
if (!task) throw new Error('Renseignez le nom de la tâche.');
return { kind: values.kind, task, client: values.kind === 'lunch' ? null : String(values.client ?? '').trim() || null };
}
function validateTimes(start, end, now) {
if (!Number.isFinite(start) || !Number.isFinite(end) || start >= end) {
throw new Error('La fin doit être postérieure au début.');
}
if (end > now) throw new Error('Les horaires ne peuvent pas être dans le futur.');
}
async function saveLunch(date, values) {
if (values.kind !== 'lunch') return;
if (!['panier', 'restaurant'].includes(values.lunchType)) throw new Error('Choisissez le type de repas.');
await db.days.update(date, {
lunchType: values.lunchType,
restaurant: values.lunchType === 'restaurant' ? String(values.restaurant ?? '').trim() || null : null,
});
}
/** Correct both boundaries and their neighbours without changing day status. */
export function saveRecordedEntry(original, values, now = Date.now()) {
return db.transaction('rw', db.days, db.entries, async () => {
const { entry, day, entries } = await readOriginal(original);
const index = entries.findIndex((e) => e.id === entry.id);
const prev = entries[index - 1], next = entries[index + 1];
const { startTs, endTs } = values;
if ((entry.endTs == null) !== (endTs == null)) throw conflict();
validateTimes(startTs, endTs ?? now, now);
if (prev && startTs < prev.startTs || next && endTs > (next.endTs ?? now)) {
throw new Error('Ces horaires dépassent le poste voisin. Corrigez d’abord ce voisin.');
}
if (!prev && localDateKey(startTs) !== day.date) throw new Error('Le début doit rester à la date de cette journée.');
await db.entries.update(entry.id, { ...content(values), startTs, endTs });
if (prev && startTs !== entry.startTs) await db.entries.update(prev.id, { endTs: startTs });
if (next && endTs !== entry.endTs) await db.entries.update(next.id, { startTs: endTs });
if (!prev) await db.days.update(day.date, { startedTs: startTs });
if (!next && day.status === 'closed') await db.days.update(day.date, { endedTs: endTs });
await saveLunch(day.date, values);
});
}
/** Carve a forgotten interval out of a selected entry, preserving both remnants. */
export function insertRecordedEntry(original, values, now = Date.now()) {
return db.transaction('rw', db.days, db.entries, async () => {
const { entry, entries } = await readOriginal(original);
const { startTs, endTs } = values;
validateTimes(startTs, endTs, now);
if (startTs < entry.startTs || endTs > (entry.endTs ?? now)) {
throw new Error('Le poste ajouté doit rester dans la plage choisie.');
}
const { id, ...rest } = entry;
const pieces = [];
if (startTs > entry.startTs) pieces.push({ ...rest, endTs: startTs });
const insertedIndex = pieces.length;
pieces.push({ ...rest, ...content(values), startTs, endTs });
if (entry.endTs == null || endTs < entry.endTs) pieces.push({ ...rest, startTs: endTs });
// Keep the original id on a surviving portion, or on the replacement when full.
const keepIndex = startTs > entry.startTs ? 0 : pieces.length - 1;
const shift = pieces.length - 1;
for (const later of entries.filter((e) => e.seq > entry.seq)) {
await db.entries.update(later.id, { seq: later.seq + shift });
}
let insertedId;
for (let i = 0; i < pieces.length; i++) {
const piece = { ...pieces[i], seq: entry.seq + i };
const pieceId = i === keepIndex ? (await db.entries.put({ ...piece, id }), id) : await db.entries.add(piece);
if (i === insertedIndex) insertedId = pieceId;
}
await saveLunch(entry.date, values);
return insertedId;
});
}
export function removeRecordedEntry(original) {
return db.transaction('rw', db.days, db.entries, async () => {
const { entry, entries } = await readOriginal(original);
if (entries.length < 2) throw new Error('Le dernier poste de la journée ne peut pas être supprimé.');
if (entry.endTs == null) throw new Error('Le poste en cours ne peut pas être supprimé.');
await deleteEntry(entry.id);
});
}
/** Native datetime-local inputs use wall time, never UTC. */
export function localInput(ts) {
const d = new Date(ts);
return `${localDateKey(ts)}T${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}