Added edit mode
This commit is contained in:
122
src/components/DayEditor.svelte
Normal file
122
src/components/DayEditor.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user