v1.11.0 (web) : inactifs regroupés en bas de la page Traitements + cartes atténuées — miroir Android
- Helper pur treatmentsForDisplay dans js/data/models.js (miroir du Kotlin, tri STABLE : actifs d'abord, inactifs après, ordre relatif conservé dans chaque groupe) — demande : « peu discernables, à placer à la fin ». - Rendu : en-tête « Traitements inactifs » (seulement s'il en existe), cartes opacity 0.55 en plus du badge — lisibles et cliquables. - Clés i18n inactive_section FR/EN ; WEB_VERSION alignée sur 1.11.0. - 4 tests treatments-display.test.js (miroir TreatmentsDisplayTest.kt). 184 tests + E2E verts (check.sh).
This commit is contained in:
parent
f36e25ef62
commit
0dd5fa7dfd
13
README.md
13
README.md
@ -17,15 +17,14 @@
|
||||
> Fie-toi toujours à tes analyses de sang et aux consignes de ton
|
||||
> endocrinologue.
|
||||
|
||||
- **Statut** : web v1.10.0 — parité fonctionnelle avec l'Android v1.10.0
|
||||
sur la logique miroirée (fix #68 : creux PRÉ-INJECTION dans la
|
||||
recommandation de prise de sang — date toujours postérieure à la
|
||||
stabilisation, cohérente avec le créneau affiché ; les autres changements
|
||||
Android de la v1.10.0 — dialog fantôme #69, rappel sauté si dose déjà
|
||||
loggée — sont sans équivalent web, notifications = Android) ;
|
||||
- **Statut** : web v1.11.0 — parité fonctionnelle avec l'Android v1.11.0
|
||||
sur la logique miroirée (page Traitements : inactifs regroupés tout en
|
||||
bas sous un en-tête dédié, cartes atténuées — helper pur
|
||||
`treatmentsForDisplay` miroir du Kotlin ; le fix #68 de la reco de prise
|
||||
de sang reste couvert, cf changelog) ;
|
||||
l'auto-backup journalier Android reste structurellement non porté,
|
||||
cf §12 — l'export manuel couvre la donnée ·
|
||||
**180 tests verts** · E2E navigateur ✅ · conteneur testé en
|
||||
**184 tests verts** · E2E navigateur ✅ · conteneur testé en
|
||||
configuration durcie ✅ · lint/i18n ✅
|
||||
- **Journal des versions web** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
|
||||
- **Releases** : [gitea.cloudyfy.fr/Siphonight/HormoneTrack-web/releases](https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web/releases)
|
||||
|
||||
@ -5,6 +5,22 @@
|
||||
> section la plus récente EN PREMIER ; ce fichier est lu par le dialog
|
||||
> « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`).
|
||||
|
||||
## [1.11.0] — 2026-09-29
|
||||
|
||||
### Ajouté — les traitements inactifs regroupés en bas de la page Traitements
|
||||
|
||||
- Miroir de l'Android v1.11.0 (demande : « les traitements inactifs sont
|
||||
peu discernables — les placer à la fin, et peut-être les rendre plus
|
||||
distincts ») : les ACTIFS d'abord, les INACTIFS regroupés **tout en bas**
|
||||
sous un en-tête « Traitements inactifs » (seulement s'il en existe),
|
||||
cartes **atténuées** (opacity 0,55) en plus du badge.
|
||||
- Tri STABLE : l'ordre relatif à l'intérieur de chaque groupe est conservé.
|
||||
Helper pur `treatmentsForDisplay` dans `js/data/models.js` (miroir du
|
||||
Kotlin, node-safe) — 4 tests (`tests/treatments-display.test.js`,
|
||||
miroir de TreatmentsDisplayTest.kt).
|
||||
|
||||
Vérifié : 184 tests verts + E2E (`check.sh`).
|
||||
|
||||
## [1.10.0] — 2026-09-27
|
||||
|
||||
### Corrigé — la recommandation de prise de sang précédait la stabilisation (#68)
|
||||
|
||||
@ -60,7 +60,7 @@ Règles du portage, fixées au départ :
|
||||
|
||||
| Choix | Décision | Pourquoi |
|
||||
|---|---|---|
|
||||
| Langage | JavaScript ES modules natifs (pas de TS, pas de build) | zéro chaîne de compilation → le déploiement = copier le dossier ; les types critiques sont documentés en JSDoc et épinglés par 180 tests |
|
||||
| Langage | JavaScript ES modules natifs (pas de TS, pas de build) | zéro chaîne de compilation → le déploiement = copier le dossier ; les types critiques sont documentés en JSDoc et épinglés par 184 tests |
|
||||
| Framework UI | Aucun — DOM via un helper `el()` (`ui/components.js`) | même philosophie que la v1 Android (pas de ViewModel/DI) ; une seule abstraction à connaître |
|
||||
| Rendu graphique | Canvas 2D natif (portage de `CurveChart.kt`) | pas de lib de charts : fidélité du rendu, zéro dépendance |
|
||||
| Persistance | `localStorage` (clés préfixées `hormonetrack.`) | données petites (JSON) ; backend INJECTABLE → testable en Node (`store.setBackend(new Map())`) |
|
||||
@ -260,6 +260,9 @@ HormoneTrack-web/ (dépôt séparé)
|
||||
│ │ créneau, saut de stabilisation, gardes)
|
||||
│ ├── lab-notes.test.js ← labNotesForDisplay (v1.7.1 : notes d'une
|
||||
│ │ prise E2+T — les DEUX distinctes affichées)
|
||||
│ ├── treatments-display.test.js ← treatmentsForDisplay (v1.11.0 : actifs
|
||||
│ │ d'abord, inactifs regroupés en bas — tri
|
||||
│ │ stable)
|
||||
│ ├── lab-trajectory-model.test.js ← LabTrajectoryModelTest.kt (v1.5.0
|
||||
│ │ « Tracé labs » + v1.6.0 prolongation —
|
||||
│ │ passage exact, garde #61, horizon cutoff)
|
||||
@ -405,7 +408,7 @@ Pièges rencontrés :
|
||||
|---|---|---|---|
|
||||
| Syntaxe | `node --check` | tous les modules ES | `bash scripts/check.sh` |
|
||||
| i18n | `scripts/i18n-check.mjs` | clés FR/EN synchronisées + clés utilisées | (dans check.sh) |
|
||||
| Unitaires | `node --test` | **180 tests** : noyau PK (ESE analytique/TFS/WHS), calibration, backup, store, helpers, alertes, rappels, changelog, tracé labs, estrannaise-models/cloud, rounding, presets (v1.9.4) | `npm test` |
|
||||
| Unitaires | `node --test` | **184 tests** : noyau PK (ESE analytique/TFS/WHS), calibration, backup, store, helpers, alertes, rappels, changelog, tracé labs, estrannaise-models/cloud, rounding, presets (v1.9.4), ordre d'affichage des traitements (v1.11.0) | `npm test` |
|
||||
| E2E navigateur | playwright-core + build Firefox | app réelle : rendu, canvas peint (pixels), navigation, dialog changelog, langue | `npm run e2e` |
|
||||
| Smoke HTTP | curl | ressources clés en 200 | `check.sh --with-serve` |
|
||||
|
||||
|
||||
@ -211,3 +211,21 @@ export function newLabResult(init = {}) {
|
||||
...init,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ORDRE D'AFFICHAGE de la page Traitements (v1.11.0, miroir fidèle de
|
||||
* `ui/screens/TreatmentsScreen.kt` — même KDoc) : les ACTIFS d'abord, les
|
||||
* INACTIFS regroupés TOUT EN BAS — l'ancien ordre brut du store mélangeait
|
||||
* les deux groupes et le badge discret ne suffisait plus à les discerner
|
||||
* (« peu discernables », remontée).
|
||||
*
|
||||
* Tri STABLE : l'ordre relatif À L'INTÉRIEUR de chaque groupe est conservé.
|
||||
* PUR (aucune dépendance DOM) → testable en node
|
||||
* (tests/treatments-display.test.js, miroir de TreatmentsDisplayTest.kt).
|
||||
*
|
||||
* @param {Array<object>} treatments
|
||||
* @returns {Array<object>} nouvelle liste triée (l'entrée n'est pas mutée)
|
||||
*/
|
||||
export function treatmentsForDisplay(treatments) {
|
||||
return [...treatments].sort((a, b) => Number(b.isActive) - Number(a.isActive));
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ import { el, showDialog, field, switchRow, filterChip, toast } from './component
|
||||
* (APK + web au MÊME numéro, tags appariés `vX.Y.Z` + `vX.Y.Z-web`).
|
||||
* v1.4.10 = portage initial de l'Android v1.4.10.
|
||||
*/
|
||||
export const WEB_VERSION = '1.10.0';
|
||||
export const WEB_VERSION = '1.11.0';
|
||||
|
||||
/** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */
|
||||
function parseNumOrNull(text) {
|
||||
|
||||
@ -5,11 +5,15 @@
|
||||
* Cartes (nom, voie, dose, chips ester·modèle / Tmax / ×scale / ⏰ rappel,
|
||||
* badge inactif) → tap = éditeur. ⚠️ Le chip modèle passe par modelLabelKey
|
||||
* (fix #59 : un ternaire à 2 branches affichait « Estrannaise » pour WHSAH).
|
||||
*
|
||||
* v1.11.0 (demande utilisatrice, miroir Android) : les ACTIFS d'abord, les
|
||||
* INACTIFS regroupés TOUT EN BAS sous un en-tête de section, cartes
|
||||
* ATTÉNUÉES (opacity 0.55) en plus du badge — « peu discernables ».
|
||||
* ─────────────────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
import { store } from '../data/store.js';
|
||||
import { modelLabelKey, routeLabelKey, treatmentFlags } from '../data/models.js';
|
||||
import { modelLabelKey, routeLabelKey, treatmentFlags, treatmentsForDisplay } from '../data/models.js';
|
||||
import { t } from '../util/i18n.js';
|
||||
import { formatDose } from '../util/format.js';
|
||||
import { el, assistChip, fab } from './components.js';
|
||||
@ -20,7 +24,10 @@ import { el, assistChip, fab } from './components.js';
|
||||
* @param {object} ctx { openEditor: (id:number)=>void, openNew: ()=>void }
|
||||
*/
|
||||
export function renderTreatments(container, ctx) {
|
||||
const treatments = store.getTreatments();
|
||||
// v1.11.0 : actifs d'abord, inactifs regroupés en bas (cf treatmentsForDisplay)
|
||||
const treatments = treatmentsForDisplay(store.getTreatments());
|
||||
const active = treatments.filter((tr) => tr.isActive);
|
||||
const inactive = treatments.filter((tr) => !tr.isActive);
|
||||
|
||||
const col = el('div', {});
|
||||
container.appendChild(col);
|
||||
@ -29,9 +36,18 @@ export function renderTreatments(container, ctx) {
|
||||
col.appendChild(el('p', {}, t('no_data')));
|
||||
}
|
||||
|
||||
for (const tr of treatments) {
|
||||
/**
|
||||
* Rend UNE carte traitement. @param {object} tr @param {boolean} dimmed
|
||||
* carte inactive → opacité réduite (second signal visuel au-delà du
|
||||
* badge ; reste lisible et cliquable).
|
||||
*/
|
||||
const renderCard = (tr, dimmed) => {
|
||||
const flags = treatmentFlags(tr); // miroir des getters Kotlin
|
||||
const card = el('div', { class: 'card', style: 'cursor:pointer;', onclick: () => ctx.openEditor(tr.id) });
|
||||
const card = el('div', {
|
||||
class: 'card',
|
||||
style: `cursor:pointer;${dimmed ? 'opacity:0.55;' : ''}`,
|
||||
onclick: () => ctx.openEditor(tr.id),
|
||||
});
|
||||
|
||||
const titleRow = el('div', { class: 'row' });
|
||||
titleRow.appendChild(el('span', { style: 'font-weight:600;flex:1;' }, tr.name));
|
||||
@ -55,7 +71,19 @@ export function renderTreatments(container, ctx) {
|
||||
chipRow.appendChild(assistChip(`⏰ ${hh}:${mm}`));
|
||||
}
|
||||
card.appendChild(chipRow);
|
||||
col.appendChild(card);
|
||||
return card;
|
||||
};
|
||||
|
||||
for (const tr of active) {
|
||||
col.appendChild(renderCard(tr, false));
|
||||
}
|
||||
|
||||
// En-tête de section SEULEMENT s'il existe des inactifs (miroir Android)
|
||||
if (inactive.length > 0) {
|
||||
col.appendChild(el('p', { class: 'sub', style: 'margin:14px 0 4px;' }, t('inactive_section')));
|
||||
}
|
||||
for (const tr of inactive) {
|
||||
col.appendChild(renderCard(tr, true));
|
||||
}
|
||||
|
||||
container.appendChild(fab(ctx.openNew));
|
||||
|
||||
@ -66,6 +66,7 @@ const EN = {
|
||||
confirm_delete_blood_draw: 'Delete the whole blood draw ({0})?',
|
||||
// Traitements
|
||||
inactive: 'inactive',
|
||||
inactive_section: 'Inactive treatments',
|
||||
pk_absorption: 'Tmax',
|
||||
pk_halflife: 'Half-life (h)',
|
||||
pk_bioavail: 'Bioavailability (0–1)',
|
||||
@ -273,6 +274,7 @@ const FR = {
|
||||
edit_which_lab: 'Quelle entrée modifier ?',
|
||||
confirm_delete_blood_draw: 'Supprimer la prise de sang entière ({0}) ?',
|
||||
inactive: 'inactive',
|
||||
inactive_section: 'Traitements inactifs',
|
||||
pk_absorption: 'Tmax',
|
||||
pk_halflife: 'Demi-vie (h)',
|
||||
pk_bioavail: 'Biodisponibilité (0–1)',
|
||||
|
||||
66
tests/treatments-display.test.js
Normal file
66
tests/treatments-display.test.js
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Tests de l'ORDRE D'AFFICHAGE de la page Traitements (v1.11.0, miroir de
|
||||
* TreatmentsDisplayTest.kt — demande utilisatrice : « les traitements
|
||||
* inactifs faudrait les placer à la fin (tout en bas) de la liste ») :
|
||||
* - les ACTIFS d'abord, les INACTIFS regroupés après ;
|
||||
* - tri STABLE (ordre conservé à l'intérieur de chaque groupe) ;
|
||||
* - cas limites : liste vide, tout actif, tout inactif (identité).
|
||||
*/
|
||||
|
||||
import { test, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { treatmentsForDisplay } from '../js/data/models.js';
|
||||
import { makeTreatment } from './helpers.js';
|
||||
|
||||
describe('treatments display order (v1.11.0)', () => {
|
||||
test('les inactifs sont regroupés à la fin', () => {
|
||||
const mixed = [
|
||||
makeTreatment({ id: 1, name: 'EEn', isActive: true }),
|
||||
makeTreatment({ id: 2, name: 'EV-old', isActive: false }),
|
||||
makeTreatment({ id: 3, name: 'CPA', isActive: true }),
|
||||
makeTreatment({ id: 4, name: 'EEn-old', isActive: false }),
|
||||
makeTreatment({ id: 5, name: 'Ritaline', isActive: true }),
|
||||
];
|
||||
assert.deepEqual(
|
||||
treatmentsForDisplay(mixed).map((t) => t.id),
|
||||
[1, 3, 5, 2, 4],
|
||||
'actifs d\'abord (ordre conservé), inactifs après (ordre conservé)',
|
||||
);
|
||||
});
|
||||
|
||||
test('tri stable : ordre conservé à l\'intérieur de chaque groupe', () => {
|
||||
const mixed = [
|
||||
makeTreatment({ id: 10, name: 'A', isActive: false }),
|
||||
makeTreatment({ id: 11, name: 'B', isActive: true }),
|
||||
makeTreatment({ id: 12, name: 'C', isActive: false }),
|
||||
makeTreatment({ id: 13, name: 'D', isActive: true }),
|
||||
];
|
||||
assert.deepEqual(
|
||||
treatmentsForDisplay(mixed).map((t) => t.id),
|
||||
[11, 13, 10, 12],
|
||||
);
|
||||
});
|
||||
|
||||
test('tout actif / tout inactif : ordre inchangé (identité)', () => {
|
||||
const actifs = [
|
||||
makeTreatment({ id: 3, isActive: true }),
|
||||
makeTreatment({ id: 1, isActive: true }),
|
||||
];
|
||||
assert.deepEqual(treatmentsForDisplay(actifs).map((t) => t.id), [3, 1]);
|
||||
const inactifs = [
|
||||
makeTreatment({ id: 2, isActive: false }),
|
||||
makeTreatment({ id: 1, isActive: false }),
|
||||
];
|
||||
assert.deepEqual(treatmentsForDisplay(inactifs).map((t) => t.id), [2, 1]);
|
||||
});
|
||||
|
||||
test('liste vide reste vide ; entrée non mutée', () => {
|
||||
assert.equal(treatmentsForDisplay([]).length, 0);
|
||||
const mixed = [
|
||||
makeTreatment({ id: 2, isActive: false }),
|
||||
makeTreatment({ id: 1, isActive: true }),
|
||||
];
|
||||
treatmentsForDisplay(mixed);
|
||||
assert.deepEqual(mixed.map((t) => t.id), [2, 1], 'la liste d\'entrée n\'est pas mutée');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user