diff --git a/README.md b/README.md index f55d4f1..bc2db79 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,13 @@ npm run dev # http://localhost:5173 npm test # unit and IndexedDB regression tests npm run check # svelte-check npm run build # production build into dist/ +npm run validate # tests, Svelte check and full PWA build ``` ## Exporter et importer les données Dans **Réglages → Données**, **Exporter les données** télécharge un fichier -`timelog-AAAA-MM-JJ.json`. Il contient toutes les journées conservées, leurs postes +`pardis-AAAA-MM-JJ.json`. Il contient toutes les journées conservées, leurs postes et les réglages enregistrés. L'export fonctionne hors ligne et ne modifie pas les données de l'appareil. Les identifiants internes des postes sont recréés à l'import. @@ -41,7 +42,7 @@ exportée en cours, le dernier poste est arrêté à l'heure de l'export : le te écoulé est conservé. Les intervalles sans travail déjà présents sont conservés. Une sauvegarde vide est valide et peut servir à restaurer un appareil vide. -Le format JSON est identifié par `format: "timelog-export"` et `version: 1`. +Le format JSON est identifié par `format: "timelog-export"` et `version: 2`. Le format v1 reste importable. Les anciennes applications refusent le format v2 pour éviter de transformer les pauses en travail. Les fichiers étrangers, versions non prises en charge, dates en double et réglages invalides sont refusés. Les postes inutilisables et les corrections de plages horaires sont signalés avant confirmation. La limite de taille est de 20 Mo. @@ -49,9 +50,7 @@ L'écriture est transactionnelle : un échec annule le remplacement ou la fusion dans son intégralité. Le récapitulatif, le thème et les suggestions sont ensuite actualisés sans recharger la page. -L'import ne déclenche pas immédiatement de purge. La durée de conservation -s'applique de nouveau à la prochaine ouverture ou au changement de jour ; le -bouton **Purger maintenant** reste disponible. +La purge automatique est désactivée. **Purger maintenant** présente les dates concernées et demande confirmation. Une sauvegarde locale est conservée avant la suppression ; **Récupérer la sauvegarde avant purge** permet de la fusionner ou de la télécharger après restauration. Elle reste sur le même appareil : un export externe demeure nécessaire. Les tests d'import/export couvrent le format, les réglages, la fidélité des durées, les fichiers invalides, la fusion, le remplacement et l'annulation transactionnelle @@ -65,9 +64,7 @@ il n'est pas publié avec l'application. enregistrée. Cette heure est une estimation : l'application ne mesure pas le travail lorsqu'elle est fermée. Le dernier poste conserve ainsi sa durée jusqu'à cette limite, au lieu d'être ramené à zéro. -- Une journée terminée propose **Reprendre la journée**. Cela annule sa clôture et - continue le dernier poste, client et déjeuner conservés. Le temps écoulé depuis - l'arrêt est compté dans ce poste : la reprise corrige une clôture prématurée. +- Une journée terminée aujourd’hui propose **Reprendre la journée** avec confirmation : prolonger le dernier poste, ou créer une pause jusqu’à maintenant (choix initial). Un dernier repas ou une pause garde sa nature ; le travail reprend dans un nouveau poste. Les journées passées se corrigent par **Voir les détails → Corriger cette journée**. - La création, la clôture et la reprise sont transactionnelles. Les actions simultanées sur le même poste ne créent pas de doublons ; une journée clôturée ne peut pas recevoir un nouveau poste actif. La purge épargne les journées @@ -299,3 +296,22 @@ src/lib/ Pure helpers and database/lifecycle operations have regression tests. `db.js` keeps day and entry changes in shared transactions, including creation, closure, reopening and boundary corrections. + +## Pauses et nouveaux parcours + +**Journée → Commencer une pause** ferme le poste courant. **Reprendre le travail** +propose la dernière tâche de travail et son client, immédiatement modifiables. +Une pause a ses propres horaires et son libellé, sans client ni type de repas. +Le détail montre tous les postes ; la consultation reste séparée de la correction. +Le récapitulatif et sa copie excluent les pauses. Le transport entre dans le total +travaillé, les repas et pauses en sont exclus. Seules les statistiques montrent le +cumul des pauses, séparément des repas. Les intervalles vides importés sont préservés. + +**Réglages → Gérer les automatismes** et **Gérer les jours non travaillés** ouvrent +des pages dédiées ; le bouton Retour du navigateur fonctionne via les routes hash. +Les réglages sont écrits par transaction sur la dernière valeur persistée pour +préserver les modifications d’autres onglets. Les erreurs de démarrage et +actualisation sont affichées avec une action Réessayer. + +La checklist avant production et les validations restant à exécuter sont suivies +dans [PRODUCTION.md](PRODUCTION.md). diff --git a/package.json b/package.json index e731289..d925b18 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "vite build", + "validate": "npm test && npm run check && npm run build", "preview": "vite preview", "test": "vitest run", "test:watch": "vitest", diff --git a/public/favicon.svg b/public/favicon.svg index 6893eb1..cfb713e 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1 +1,13 @@ - \ No newline at end of file + diff --git a/src/App.svelte b/src/App.svelte index b78f250..a761f57 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -6,13 +6,22 @@ import Resume from './routes/Resume.svelte'; import Settings from './routes/Settings.svelte'; import Today from './routes/Today.svelte'; + import DayDetails from './routes/DayDetails.svelte'; + import Automations from './routes/Automations.svelte'; + import NonWorkingSettings from './routes/NonWorkingSettings.svelte'; import Stats from './routes/Stats.svelte'; let ready = $state(false); + let bootError = $state(''); + let retry = $state(0); + async function tick() { try { await reconcile(); } catch (error) { bootError = `Actualisation impossible : ${error.message}`; } } $effect(() => { + retry; + bootError = ''; let alive = true; (async () => { + try { await loadSettings(); if (import.meta.env.DEV) { const { installDevSeed } = await import('./lib/seed.js'); @@ -20,14 +29,15 @@ } await reconcile(); if (alive) ready = true; + } catch (error) { if (alive) bootError = `Accès aux données impossible : ${error.message}`; } })(); return () => (alive = false); }); // Time drives this app, so the clock is polled rather than waited on. $effect(() => { - const id = setInterval(() => reconcile(), 30_000); - const onVisible = () => document.visibilityState === 'visible' && reconcile(); + const id = setInterval(tick, 30_000); + const onVisible = () => document.visibilityState === 'visible' && tick(); document.addEventListener('visibilitychange', onVisible); return () => { clearInterval(id); @@ -40,7 +50,7 @@ {#if !ready} -
Chargement…
{bootError}
Chargement…
{/if}Fusion : journées locales conservées et ignorées du fichier : {staged.days.filter(d => dayState.closedDays.some(local => local.date === d.date) || dayState.day?.date === d.date).map(d => d.date).join(', ')}.
+ {/if} {#if staged.closedActive}{staged.closedActive} journée(s) en cours seront enregistrées comme terminées à l’heure de l’export.
{/if} - {#if confirmingReplace} +Toutes les journées et tous les réglages de cet appareil seront remplacés par ceux du fichier. {#if dayState.day}La journée en cours sera également remplacée.{/if} @@ -165,7 +188,8 @@ Confirmer - {:else} +
Touchez un poste pour corriger ses horaires, sa tâche ou son client.
-Touchez un poste pour corriger ses horaires, sa tâche ou son client.
{/if} +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.
{/if} - - {#if draft.kind === 'lunch'}Cette pause est exclue du total travaillé.
{/if} +Le poste voisin absorbera cette durée. Supprimer un repas peut modifier le total travaillé.
+Le poste voisin absorbera cette durée. Supprimer un repas ou une pause peut modifier le total travaillé.
{:else} @@ -146,6 +146,4 @@ .hint { font-size: 0.85rem; } select { width: 100%; min-width: 0; } [role='alert'] { color: var(--danger); } - .meal-toggle { display: flex; align-items: center; gap: 12px; min-height: var(--tap); font-weight: 700; } - .meal-toggle input { width: 26px; height: 26px; accent-color: var(--accent); } diff --git a/src/components/ReopenDialog.svelte b/src/components/ReopenDialog.svelte new file mode 100644 index 0000000..df716d9 --- /dev/null +++ b/src/components/ReopenDialog.svelte @@ -0,0 +1,36 @@ + +Clôture enregistrée : {fmtTime(day?.endedTs ?? now)}. Heure actuelle : {fmtTime(now)}.
+ +Aucune journée enregistrée.
{/if} +Utiliser ces horaires pour les rappels et, si activée ci-dessous, la pause automatique.
{option.description}
-Conserver les saisies manuelles. Sans tâche précédente, utiliser « Travaux sur site ». Une pause lancée ou corrigée manuellement reste manuelle.
-Exécuter les automatismes pendant l’utilisation et rattraper les étapes manquées à la réouverture. Ne pas créer les journées entièrement absentes de l’historique.
-- La purge s'exécute aussi automatiquement à l'ouverture de l'application. + La purge nécessite une confirmation et conserve une sauvegarde locale récupérable.
- + {#if purged !== null}{purged === 0 @@ -202,6 +198,13 @@
Journées concernées : {purgeDates.length ? purgeDates.join(', ') : 'aucune'}.
+Une sauvegarde récupérable sera conservée sur cet appareil avant la suppression.
+ {#if purgeError}{purgeError}
{/if} + + +