/** * ───────────────────────────────────────────────────────────────────────────── * Écran Doses (portage de `ui/screens/DosesScreen.kt`). * * - en-tête « temps sous THS » (v1.3.1) : depuis la 1re prise, jours * totaux + décomposition mois(30 j)/jours — ⚠️ la string hrt_duration a * TROIS placeholders (mois, jours, total) : les trois sont passés (bug * Android #47 = crash MissingFormatArgumentException) ; * - liste groupée par jour (desc) ; * - Δ jours depuis la dose PRÉCÉDENTE du MÊME traitement (colonne * « Interval (d) » du .ods) ; * - tap ligne → édition (TOUS les traitements proposés — §6.bis) ; * - suppression avec confirmation. * ───────────────────────────────────────────────────────────────────────────── */ import { store } from '../data/store.js'; import { t } from '../util/i18n.js'; import { formatDose, formatTime, formatLongDate, formatISODate, HrtDuration } from '../util/format.js'; import { el, assistChip, confirmDialog, fab } from './components.js'; import { openDoseDialog } from './dialogs.js'; const DAY_MS = 86400000; /** * Intervalle (en jours) entre chaque dose et la dose précédente du MÊME * traitement (miroir intervalsByDoseId) — clé = id de la dose. * * @param {object[]} doseLogs * @returns {Map} id → jours (décimaux) */ export function intervalsByDoseId(doseLogs) { const byTreatment = new Map(); for (const d of doseLogs.slice().sort((a, b) => a.timestamp - b.timestamp)) { if (!byTreatment.has(d.treatmentId)) byTreatment.set(d.treatmentId, []); byTreatment.get(d.treatmentId).push(d); } const out = new Map(); for (const logs of byTreatment.values()) { for (let i = 1; i < logs.length; i++) { out.set(logs[i].id, (logs[i].timestamp - logs[i - 1].timestamp) / DAY_MS); } } return out; } /** * Rend l'écran Doses. * @param {HTMLElement} container */ export function renderDoses(container) { const doseLogs = store.getDoseLogs().slice().sort((a, b) => b.timestamp - a.timestamp); const treatments = store.getTreatments(); // TOUS (édition d'une dose inactivée OK) const treatmentMap = new Map(treatments.map((tr) => [tr.id, tr])); const intervals = intervalsByDoseId(doseLogs); const col = el('div', {}); container.appendChild(col); // ── En-tête « temps sous THS » (v1.3.1) ────────────────────────────────── if (doseLogs.length > 0) { const first = Math.min(...doseLogs.map((d) => d.timestamp)); const card = el('div', { class: 'card primary' }); card.appendChild(el('div', { style: 'font-weight:600;' }, t('hrt_since', formatLongDate(first)))); const totalDays = HrtDuration.daysSince(first, Date.now()); if (totalDays > 0) { const [months, days] = HrtDuration.monthsAndDays(totalDays); // ⚠️ hrt_duration a TROIS placeholders (%1 mois, %2 jours, %3 total) : // passer aussi le total, sinon crash Android #47 (épinglé ici aussi). card.appendChild(el('div', {}, months > 0 ? t('hrt_duration', months, days, totalDays) : t('hrt_duration_days_only', days))); } col.appendChild(card); } if (doseLogs.length === 0) { col.appendChild(el('p', {}, t('no_data'))); container.appendChild(fab(() => openDoseDialog({ treatments: treatments.filter((x) => x.isActive), preselected: null, existing: null }))); return; } // ── Groupement par jour (desc) ─────────────────────────────────────────── const grouped = new Map(); for (const log of doseLogs) { const key = formatISODate(log.timestamp); if (!grouped.has(key)) grouped.set(key, []); grouped.get(key).push(log); } for (const [dateKey, logs] of grouped) { col.appendChild(el('div', { class: 'group-header' }, dateKey)); for (const log of logs) { const tr = treatmentMap.get(log.treatmentId); const item = el('div', { class: 'list-item', onclick: () => openDoseDialog({ treatments, preselected: null, existing: log }) }); const main = el('div', { class: 'main' }); main.appendChild(el('div', { class: 'title' }, `${tr ? tr.name : '?'} · ${formatDose(log.doseAmount)} ${tr ? tr.doseUnit : ''}`)); const sub = el('div', { class: 'sub' }); sub.appendChild(el('span', {}, formatTime(log.timestamp))); // Intervalle depuis la dose précédente du même traitement const iv = intervals.get(log.id); if (iv !== undefined) sub.appendChild(el('span', {}, t('interval_value', iv.toFixed(1)))); // Override d'ester de cette injection (chip, comme Android) if (log.esterType && log.esterType !== '' && log.esterType !== 'NONE') { sub.appendChild(assistChip(log.esterType)); } if (log.notes && log.notes.trim() !== '') sub.appendChild(el('span', {}, log.notes)); main.appendChild(sub); item.appendChild(main); const del = el('button', { class: 'icon-btn', 'aria-label': t('delete') }, '🗑'); del.addEventListener('click', (e) => { e.stopPropagation(); // ne pas déclencher l'édition confirmDialog(t('delete'), t('confirm_delete'), () => store.deleteDoseLog(log.id)); }); item.appendChild(del); col.appendChild(item); } } // FAB : création (traitements ACTIFS uniquement — miroir DosesScreen) container.appendChild(fab(() => openDoseDialog({ treatments: treatments.filter((x) => x.isActive), preselected: null, existing: null }))); }