First working prototype

This commit is contained in:
2026-09-06 13:50:58 +02:00
parent ecf08f8307
commit f61b9025eb
44 changed files with 5118 additions and 905 deletions

148
src/routes/Resume.svelte Normal file
View File

@@ -0,0 +1,148 @@
<script>
import { dayState, reopenToday } from '../lib/day.svelte.js';
import { buildResume, resumeToText } from '../lib/resume.js';
import { go } from '../lib/router.svelte.js';
import { settings } from '../lib/settings.svelte.js';
import { fmtDuration, weekday } from '../lib/time.js';
let copied = $state(false);
const resume = $derived(
buildResume(
dayState.lastClosed,
dayState.lastClosedEntries,
settings.roundingMinutes,
),
);
const text = $derived(resumeToText(resume));
/** Shown when today is a work day whose start time has not arrived yet. */
const startsAt = $derived(
settings.workdays.includes(weekday(dayState.today)) ? settings.dayStart : null,
);
const closedToday = $derived(dayState.lastClosed?.date === dayState.today);
async function copy() {
try {
await navigator.clipboard.writeText(text);
} catch {
// Clipboard API needs a secure context; fall back to a selectable dialog.
window.prompt('Copier le récapitulatif :', text);
}
copied = true;
setTimeout(() => (copied = false), 2500);
}
</script>
<div class="screen">
{#if !resume}
<div class="card center stack">
<h2>Aucune journée enregistrée</h2>
<p class="dim">
Le récapitulatif de la dernière journée s'affichera ici, prêt à recopier.
</p>
</div>
{:else}
<div class="row-between">
<h1>{resume.label}</h1>
</div>
<div class="card stack">
{#each resume.clients as client (client.name)}
<div class="client">
<div class="row-between head">
<h2>{client.name}</h2>
<span class="tnum">{fmtDuration(client.totalMs)}</span>
</div>
<ul>
{#each client.tasks as task (task.name)}
<li class="row-between">
<span>{task.name}</span>
<span class="tnum">{fmtDuration(task.ms)}</span>
</li>
{/each}
</ul>
</div>
{/each}
{#if resume.lunch}
<hr />
<div class="row-between">
<span
>Déjeuner : {resume.lunch.type === 'restaurant'
? `restaurant (${resume.lunch.restaurant || 'non précisé'})`
: 'panier'}</span
>
<span class="tnum">{fmtDuration(resume.lunch.ms)}</span>
</div>
{/if}
<hr />
<div class="row-between total">
<strong>Total travaillé</strong>
<strong class="tnum">{fmtDuration(resume.workedMs)}</strong>
</div>
{#if resume.omitted}
<p class="dim small">
{resume.omitted} poste{resume.omitted > 1 ? 's' : ''} de moins de {settings.roundingMinutes}
min non affiché{resume.omitted > 1 ? 's' : ''}.
</p>
{/if}
</div>
<button type="button" class="btn-primary" onclick={copy}>
{copied ? '✓ Copié' : 'Copier le récapitulatif'}
</button>
{/if}
{#if closedToday}
<button
type="button"
onclick={async () => {
await reopenToday();
go('today');
}}>Reprendre la journée</button
>
{:else if startsAt}
<p class="dim center small">
La journée démarrera automatiquement à {startsAt}.
</p>
{:else}
<p class="dim center small">Aujourd'hui n'est pas un jour travaillé.</p>
{/if}
</div>
<style>
.client + .client {
border-top: 2px solid var(--line);
padding-top: var(--gap);
}
.head {
margin-bottom: 6px;
}
ul {
list-style: none;
margin: 0;
padding: 0 0 0 var(--pad);
display: flex;
flex-direction: column;
gap: 4px;
}
li {
font-size: 0.95rem;
}
.total {
font-size: 1.15rem;
}
.small {
font-size: 0.85rem;
}
</style>