Added edit mode

This commit is contained in:
2026-09-19 21:53:10 +02:00
parent 9bb9260c09
commit d46a5f10af
8 changed files with 458 additions and 64 deletions

View File

@@ -0,0 +1,122 @@
<script>
import { insertRecordedEntry, localInput, removeRecordedEntry, saveRecordedEntry } from '../lib/editing.js';
import { dayState, refresh } from '../lib/day.svelte.js';
import { fmtTime, MINUTE } from '../lib/time.js';
import Sheet from './Sheet.svelte';
import Timeline from './Timeline.svelte';
let { day, entries = [], now = Date.now(), onchange = () => {} } = $props();
let mode = $state('');
let original = $state(null);
let draft = $state({ kind: 'work', task: '', client: '', start: '', end: '', lunchType: 'panier', restaurant: '' });
let busy = $state(false);
let error = $state('');
let confirmingDelete = $state(false);
const ordered = $derived([...entries].sort((a, b) => a.seq - b.seq));
function select(entry, operation = mode) {
original = { ...entry };
mode = operation;
error = '';
confirmingDelete = false;
const insertion = operation === 'insert';
draft = {
kind: insertion ? 'work' : entry.kind,
task: insertion ? '' : entry.task,
client: entry.client ?? '',
start: localInput(entry.startTs),
end: localInput(insertion ? Math.min(entry.startTs + 30 * MINUTE, entry.endTs ?? now) : entry.endTs ?? now),
lunchType: day.lunchType ?? 'panier', restaurant: day.restaurant ?? '',
};
}
function close() { if (!busy) { mode = ''; original = null; } }
async function commit(remove = false) {
if (busy || dayState.importing) return;
busy = true; error = '';
try {
// Preserve seconds when only text changes; typed times have minute precision.
const startTs = draft.start === localInput(original.startTs) ? original.startTs : new Date(draft.start).getTime();
const endTs = mode === 'edit' && original.endTs == null ? null :
mode === 'edit' && draft.end === localInput(original.endTs) ? original.endTs : new Date(draft.end).getTime();
const values = { ...draft, startTs, endTs };
if (remove) await removeRecordedEntry(original);
else if (mode === 'insert') await insertRecordedEntry(original, values);
else await saveRecordedEntry(original, values);
await refresh();
onchange();
mode = ''; original = null;
} catch (cause) { error = cause.message; }
finally { busy = false; }
}
</script>
<div class="card stack">
<h3>Postes de la journée</h3>
<p class="dim hint">Touchez un poste pour corriger ses horaires, sa tâche ou son client.</p>
<p class="dim hint">Horaires à la minute ; le récapitulatif applique l’arrondi choisi dans les réglages.</p>
<Timeline {entries} rounding={1} {now} onselect={(entry) => select(entry, 'edit')} />
<button type="button" disabled={!ordered.length || dayState.importing} onclick={() => select(ordered[0], 'insert')}>Ajouter un poste oublié</button>
</div>
<Sheet open={Boolean(mode)} title={mode === 'insert' ? 'Ajouter un poste' : 'Modifier le poste'} onclose={close}>
{#if original}
<form onsubmit={(event) => { event.preventDefault(); commit(); }}>
<fieldset disabled={busy || dayState.importing} class="stack">
{#if mode === 'insert'}
<label class="stack">Plage à corriger
<select value={original.id} onchange={(event) => select(ordered.find((e) => e.id === Number(event.currentTarget.value)), 'insert')}>
{#each ordered as entry (entry.id)}
<option value={entry.id}>{fmtTime(entry.startTs)}–{entry.endTs == null ? 'en cours' : fmtTime(entry.endTs)} · {entry.task}</option>
{/each}
</select>
</label>
<p class="dim hint">Le poste oublié sera inséré dans cette plage. Les portions avant et après seront conservées.</p>
{:else}
<p class="dim hint">Changer une limite ajuste aussi le poste voisin. Le premier début et la dernière fin définissent la durée de la journée.</p>
{/if}
<label class="stack">Type de poste
<select bind:value={draft.kind}>
<option value="work">Travail</option><option value="travel">Transport</option><option value="lunch">Repas</option>
</select>
</label>
<label class="stack">Début du poste<input type="datetime-local" required bind:value={draft.start} /></label>
{#if mode === 'edit' && original.endTs == null}
<p class="dim hint">Poste en cours : sa fin sera enregistrée au prochain changement de tâche.</p>
{:else}
<label class="stack">Fin du poste<input type="datetime-local" required bind:value={draft.end} /></label>
{/if}
{#if draft.kind === 'lunch'}
<label class="stack">Type de repas<select bind:value={draft.lunchType}><option value="panier">Panier</option><option value="restaurant">Restaurant</option></select></label>
{#if draft.lunchType === 'restaurant'}
<label class="stack">Restaurant<input type="text" bind:value={draft.restaurant} /></label>
{/if}
{:else}
<label class="stack">Tâche<input type="text" required bind:value={draft.task} /></label>
<label class="stack">Client<input type="text" bind:value={draft.client} /></label>
{/if}
{#if day.status === 'closed'}<p class="dim hint">La journée restera terminée après la correction.</p>{/if}
{#if error}<p role="alert">{error}</p>{/if}
<button type="submit" class="btn-primary">{busy ? 'Enregistrement…' : 'Enregistrer'}</button>
<button type="button" onclick={close}>Annuler</button>
{#if mode === 'edit' && original.endTs != null && entries.length > 1}
{#if confirmingDelete}
<p class="hint">Le poste voisin absorbera cette durée. Le total de la journée restera inchangé.</p>
<button type="button" class="btn-danger" onclick={() => commit(true)}>Confirmer la suppression</button>
{:else}
<button type="button" onclick={() => (confirmingDelete = true)}>Supprimer ce poste</button>
{/if}
{/if}
</fieldset>
</form>
{/if}
</Sheet>
<style>
fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.hint { font-size: 0.85rem; }
input, select { width: 100%; min-width: 0; }
input[type='datetime-local'] { font: inherit; color: var(--fg); background: var(--bg); border: var(--border) solid var(--line); border-radius: var(--radius); min-height: var(--tap); padding: 8px; }
[role='alert'] { color: var(--danger); }
</style>