From 3555242c8409343cf4e3bc6990caa2747d383036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?POL=20Micka=C3=ABl?= Date: Mon, 6 Apr 2026 12:36:36 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20statistiques=20vides=20en=20production?= =?UTF-8?q?=20=E2=80=94=20normalise=20les=20dates=20migr=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les données migrées depuis "Minuteur Dragodinde" stockaient les dates comme timestamps numériques, causant un crash sur `.slice()`. Ajout de normalizeDate(), gardes défensives, migration robuste et fallback d'erreur visible dans l'UI. Bump v1.1.7. Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/application/queries/GetStatistics.ts | 68 ++++++++++++++----- src/infrastructure/electron/main.ts | 12 +++- .../components/StatistiquesView.ts | 19 ++++++ 4 files changed, 81 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index bb40933..1605876 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidienne", - "version": "1.1.6", + "version": "1.1.7", "description": "Obsidienne — Minuteur d'élevage Dragodinde pour Dofus 3", "main": "dist-electron/main.js", "author": "Mickael", diff --git a/src/application/queries/GetStatistics.ts b/src/application/queries/GetStatistics.ts index 357742b..4630f67 100755 --- a/src/application/queries/GetStatistics.ts +++ b/src/application/queries/GetStatistics.ts @@ -88,6 +88,15 @@ function toISO(d: Date): string { return d.toISOString().slice(0, 10); } +/** Normalise une date (string ISO, timestamp number, Date object) en string "YYYY-MM-DD". */ +function normalizeDate(raw: unknown): string { + if (!raw) return ''; + if (typeof raw === 'string') return raw.slice(0, 10); + if (typeof raw === 'number') return new Date(raw).toISOString().slice(0, 10); + if (raw instanceof Date) return raw.toISOString().slice(0, 10); + return String(raw).slice(0, 10); +} + function aggregate(entries: AccEntry[]) { let couples = 0, babies = 0; const races = new Set(); @@ -102,36 +111,60 @@ function aggregate(entries: AccEntry[]) { const WEEKDAY_NAMES = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi']; +function emptyResult(days: number): StatisticsResult { + const kpi0: KpiDelta = { value: 0, delta: null }; + const missingRaces: MissingRace[] = Object.entries(RACE_GEN) + .filter(([, gen]) => gen !== 1) + .map(([name, gen]) => ({ name, gen })) + .sort((a, b) => a.gen - b.gen || a.name.localeCompare(b.name)); + return { + totalBabies: kpi0, totalCouples: kpi0, successRate: kpi0, racesCount: kpi0, + dailyBirths: [], raceShares: [], raceSuccessRates: [], bestCouples: [], + genBreakdown: [], missingRaces, weekdayActivity: [], days, + }; +} + export function createGetStatisticsHandler(state: AppState) { return (query: GetStatisticsQuery): StatisticsResult => { const days = query.days ?? 30; + try { + return computeStatistics(state, days); + } catch (e) { + console.error('GetStatistics handler error:', e); + return emptyResult(days); + } + }; +} +function computeStatistics(state: AppState, days: number): StatisticsResult { // Collecter toutes les entrées normalisées (exclure Gen 1 : non créables, seulement capturables) const all: AccEntry[] = []; - for (const acc of state.accouplements) { + const accouplements = Array.isArray(state.accouplements) ? state.accouplements : []; + for (const acc of accouplements) { + if (!acc || !acc.baby) continue; if (acc.gen === 1 || (RACE_GEN[acc.baby] ?? 0) === 1) continue; all.push({ - parentA: acc.parentA, parentB: acc.parentB, - baby: acc.baby, gen: acc.gen, - couples: acc.couples, babiesObtained: acc.babiesObtained, - date: acc.date, + parentA: acc.parentA ?? '', parentB: acc.parentB ?? '', + baby: acc.baby, gen: acc.gen ?? (RACE_GEN[acc.baby] ?? 0), + couples: Number(acc.couples) || 0, babiesObtained: Number(acc.babiesObtained) || 0, + date: normalizeDate(acc.date), }); } - for (const arch of state.archivedStats as Array<{ + const archivedStats = Array.isArray(state.archivedStats) ? state.archivedStats : []; + for (const arch of archivedStats as Array<{ parentA?: string; parentB?: string; baby?: string; gen?: number; couples?: number; babiesObtained?: number; date?: string; }>) { - if (arch.baby) { - const gen = arch.gen ?? (RACE_GEN[arch.baby] ?? 0); - if (gen === 1) continue; - all.push({ - parentA: arch.parentA ?? '', parentB: arch.parentB ?? '', - baby: arch.baby, gen, - couples: arch.couples ?? 0, - babiesObtained: arch.babiesObtained ?? 0, - date: arch.date ?? '', - }); - } + if (!arch || !arch.baby) continue; + const gen = arch.gen ?? (RACE_GEN[arch.baby] ?? 0); + if (gen === 1) continue; + all.push({ + parentA: arch.parentA ?? '', parentB: arch.parentB ?? '', + baby: arch.baby, gen, + couples: Number(arch.couples) || 0, + babiesObtained: Number(arch.babiesObtained) || 0, + date: normalizeDate(arch.date), + }); } const now = new Date(); @@ -284,5 +317,4 @@ export function createGetStatisticsHandler(state: AppState) { weekdayActivity, days, }; - }; } diff --git a/src/infrastructure/electron/main.ts b/src/infrastructure/electron/main.ts index cce270f..667b2b4 100755 --- a/src/infrastructure/electron/main.ts +++ b/src/infrastructure/electron/main.ts @@ -34,7 +34,17 @@ if (process.env.ELECTRON_USER_DATA_DIR) { if (app.isPackaged) { const oldDataFile = path.join(app.getPath('appData'), 'Minuteur Dragodinde', 'dd-timer-data.json'); const newDataFile = path.join(app.getPath('userData'), 'dd-timer-data.json'); - if (!fs.existsSync(newDataFile) && fs.existsSync(oldDataFile)) { + const shouldMigrate = (() => { + if (!fs.existsSync(oldDataFile)) return false; + if (!fs.existsSync(newDataFile)) return true; + // Si le nouveau fichier est quasi-vide (<200 octets = état par défaut), on remigre + try { + const newSize = fs.statSync(newDataFile).size; + const oldSize = fs.statSync(oldDataFile).size; + return newSize < 200 && oldSize > newSize; + } catch { return false; } + })(); + if (shouldMigrate) { try { fs.mkdirSync(path.dirname(newDataFile), { recursive: true }); fs.copyFileSync(oldDataFile, newDataFile); diff --git a/src/presentation/components/StatistiquesView.ts b/src/presentation/components/StatistiquesView.ts index 49f18c6..5976c4a 100755 --- a/src/presentation/components/StatistiquesView.ts +++ b/src/presentation/components/StatistiquesView.ts @@ -43,6 +43,25 @@ export class StatistiquesView { private renderAll(): void { if (!this.el) return; + + try { + this.renderAllInner(); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + const stack = e instanceof Error ? e.stack ?? '' : ''; + console.error('Erreur statistiques:', e); + this.el.innerHTML = `
+
+

Statistiques d'\u00c9levage

+

Erreur : ${esc(msg)}

+
${esc(stack)}
+
+
`; + } + } + + private renderAllInner(): void { + if (!this.el) return; const data = this.getData(); let html = '';