@mostajs/flotte-ui 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,44 @@
3
3
  Format [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) · [SemVer](https://semver.org/lang/fr/).
4
4
  Auteur : Dr Hamid MADANI <drmdh@msn.com>
5
5
 
6
- ## [0.1.1] — non publiée
6
+ ## [0.2.0] — non publiée
7
+ Les planches A à F de Guerway (`GUERWAY/docs/L0/maquette-flotte/`, exigences ECR-16.29 à 16.41). Demande
8
+ `@mostajs/flotte` **^0.5.0** (`TRANSITIONS`, `transitionsDepuis`, pièce d'un plein).
9
+ ### Added
10
+ - Tableau de bord de la planche A : six indicateurs (« Hors service »), puces de statut avec effectifs, liste
11
+ (type et capacité, station et région), véhicules par station en barres empilées, statuts en anneau. Essais
12
+ T-FUI-12 et 13.
13
+ - `rendrePageVehicule` : la fiche pleine page. Elle comprend :
14
+ - un en-tête commun, « Modifier », et « Changer le statut » limité aux transitions du moteur ;
15
+ - sept onglets ;
16
+ - un bandeau de message ou de refus.
17
+
18
+ Essai T-FUI-14.
19
+ - Les onglets :
20
+ - Documents : une carte par échéance. Essai T-FUI-15.
21
+ - Entretiens : jauges km et jours, prochaine échéance estimée, interventions à cadenas. Essai T-FUI-16.
22
+ - Affectations : frise et panneau « Affecter un chauffeur ». Essai T-FUI-17.
23
+ - Pleins et relevés : courbe du compteur, avertissements, consommation mesurée. Essai T-FUI-19.
24
+ - Coûts : périodes, postes, non comptés, barres par mois, coût au km du parc. Essai T-FUI-20.
25
+ - Les formulaires d'écriture et leurs **lecteurs**. Le module n'écrit rien : un lecteur rend les arguments de l'API
26
+ de `flotte`.
27
+ - `rendreFormulaireStatut` et `lireStatut` (T-FUI-18).
28
+ - `rendreFormulaireVehicule`, `lireFormulaireVehicule` et `lireModification` (T-FUI-21, 22).
29
+ - `FORMULAIRES`, `rendreFormulaire` et `lireFormulaire` : plan, intervention, clôture, plein, relevé, échéance
30
+ (T-FUI-23).
31
+ - `erreurDeFlotte` place un refus du moteur sous son champ.
32
+ - `construireMonVehicule` et `rendreMonVehicule` : « Mon véhicule » du livreur. Essai T-FUI-24.
33
+ - `construireAptitudes`, `rendreChoixVehicule`, `rendreRefusDepart` et `rendreDisponibles` : l'aptitude en tournée,
34
+ avec le refus et **tous** ses motifs. Essai T-FUI-25.
35
+ - Essai transverse T-FUI-26 : aucun script, tout est échappé, et chaque clé de texte appelée existe.
36
+ ### Changed
37
+ - Vue-modèle de la fiche :
38
+ - `documents`, `entretiens` et `affectations` changent de forme (plan de dev §8.3) ;
39
+ - `ONGLETS` compte sept onglets ;
40
+ - `indicateurs.horsService` et `puces` apparaissent.
41
+ - La colonne « Capacité » passe sous le type.
42
+
43
+ ## [0.1.1] — 2026-10-24
7
44
  ### Fixed
8
45
  - Une entrée d'historique sans `vers` (modification descriptive, `@mostajs/flotte` ≥ 0.4.0) n'est plus lue comme un
9
46
  changement de statut : la variation des indicateurs reste juste. L'onglet Historique l'affiche
package/README.md CHANGED
@@ -6,16 +6,28 @@ Les écrans du carnet du parc ([`@mostajs/flotte`](../mosta-flotte)), en HTML re
6
6
  script**. Le module sert le même écran à Guerway et au SaaS « Carnet du parc ». Chacun l'habille de sa propre charte.
7
7
 
8
8
  - **Le tableau de bord** (écran ECR-16 de Guerway) :
9
- - cinq indicateurs avec leur variation sur la période ;
9
+ - six indicateurs avec leur variation sur la période, et des puces de statut avec leurs effectifs ;
10
10
  - la liste filtrée et paginée, avec le statut, l'indicateur « document expiré » et la réponse « peut partir » du
11
11
  jour ;
12
12
  - les alertes et échéances ;
13
13
  - la répartition par type, les statuts et le kilométrage sur six mois ;
14
- - les véhicules par station, le top 5 et les actions rapides.
15
- - **La fiche du véhicule** :
14
+ - les véhicules par station (barres empilées par statut), le top 5 et les actions rapides.
15
+ - **La fiche du véhicule**, en panneau ou en pleine page (`rendrePageVehicule`) :
16
16
  - le bandeau « peut partir », ou ses motifs ;
17
- - les onglets Informations, Documents, Entretiens, Affectations et Historique, chacun en lien GET ;
18
- - le prochain entretien daté, y compris quand cette date est estimée au rythme du véhicule.
17
+ - « Modifier », et « Changer le statut », qui ne propose que les transitions permises par le moteur ;
18
+ - sept onglets, chacun en lien GET :
19
+ - Informations ;
20
+ - Documents : une carte par échéance, avec la pièce et son empreinte ;
21
+ - Entretiens : jauges en km et en jours, prochaine échéance datée, même quand la date est estimée ;
22
+ - Affectations : frise, et choix d'un chauffeur du bureau ;
23
+ - Pleins et relevés : courbe du compteur et consommation mesurée ;
24
+ - Coûts : par période et par mois, coût au km du parc ;
25
+ - Historique.
26
+ - **Les formulaires d'écriture**, avec leurs lecteurs :
27
+ - nouveau véhicule, modifier, changement de statut, réforme confirmée ;
28
+ - plan, intervention, clôture, plein, relevé, échéance.
29
+ - **« Mon véhicule »** du livreur, et l'**aptitude des véhicules** au départ d'une tournée, avec un refus qui donne
30
+ tous ses motifs.
19
31
 
20
32
  L'écran se fait en deux temps :
21
33
  1. **construire** : lit l'API de `flotte` et produit une vue-modèle faite de codes ;
@@ -60,6 +72,19 @@ Le kit reprend les signatures des composants de Guerway (`src/charte.mjs`), qui
60
72
 
61
73
  Un membre absent est pris dans `KIT_DEFAUT`.
62
74
 
75
+ ### Écrire : le module lit, l'application appelle le moteur
76
+
77
+ ```js
78
+ import { lireFormulaire, rendreFormulaire, erreurDeFlotte } from '@mostajs/flotte-ui';
79
+
80
+ const { donnees, erreurs } = lireFormulaire('plein', req.body); // « 18 500,00 » → 18500, JJ/MM/AAAA → AAAA-MM-JJ
81
+ if (!Object.keys(erreurs).length) {
82
+ try { await flotte.saisirPlein({ vehicule: id, ...donnees }, { agent }); return redirect(`/flotte/${id}?onglet=pleins`); }
83
+ catch (refus) { /* RECUL : sous le champ km, formulaire rempli */ return page(rendreFormulaire('plein', { fiche, valeurs: req.body, refus }, opts)); }
84
+ }
85
+ return page(rendreFormulaire('plein', { fiche, valeurs: req.body, erreurs }, opts));
86
+ ```
87
+
63
88
  ## Langues
64
89
 
65
90
  Le module ne contient aucune chaîne en dur.
@@ -72,12 +97,13 @@ Le module ne contient aucune chaîne en dur.
72
97
  - Écrire ou décider : c'est `@mostajs/flotte`, qui porte toutes les règles.
73
98
  - Router, poser le menu ou vérifier les droits : c'est l'application. `droits.gerer` ne fait que masquer, le serveur
74
99
  refuse.
75
- - Les formulaires d'écriture (nouveau véhicule, affectation, entretien, plein) : ils viendront en 0.2.0, une fois
76
- les maquettes reçues (`Entreprise/GUERWAY/docs/L0/PROMPT-MAQUETTE-FLOTTE.md`).
100
+ - Stocker un fichier joint (pièce d'une échéance, ticket d'un plein) : l'application le reçoit, le garde, et passe
101
+ à `flotte` son nom et son empreinte.
102
+ - Connaître le permis d'un chauffeur : l'application fournit `chauffeurs` (`permis`, `habilite`).
77
103
 
78
104
  ## Vérifier
79
105
 
80
106
  ```bash
81
- npm test # T-FUI-1 à 10
107
+ npm test # T-FUI-1 à 26
82
108
  node examples/verifier.mjs # écrit test-scripts/.out/exemple-flotte-ui.html (le parc de la maquette)
83
109
  ```
package/llms.txt CHANGED
@@ -1,8 +1,9 @@
1
1
  # @mostajs/flotte-ui — llms.txt
2
2
 
3
3
  RÔLE
4
- Écrans HTML du carnet du parc (@mostajs/flotte), sans React ni script : tableau de bord ECR-16 de Guerway et fiche
5
- du véhicule à onglets. Deux temps : construire (async, lit l'API de flotte → vue-modèle de CODES) puis rendre (pur,
4
+ Écrans HTML du carnet du parc (@mostajs/flotte), sans React ni script : tableau de bord ECR-16 de Guerway, fiche
5
+ du véhicule pleine page à sept onglets, formulaires d'écriture et leurs lecteurs, « Mon véhicule » du livreur,
6
+ aptitude en tournée (planches A à F, 0.2.0). Deux temps : construire (async, lit l'API de flotte → vue-modèle de CODES) puis rendre (pur,
6
7
  vue-modèle → HTML, avec le kit et la traduction de l'application). N3, métier, forme « vue ».
7
8
 
8
9
  EXPORTS
@@ -14,11 +15,32 @@ EXPORTS
14
15
  { sens, pct|null, favorable }), liste: { lignes, total, page, pages, parPage }, options, alertes: { lignes, total, jours },
15
16
  repartition, statuts, km: { mois, valeurs }, parSite, top, avecActivite, fiche }
16
17
  ligne = { id, code, plaque, type, capacite: { kg, m3 }, site, region, conducteur, km, statut, documentExpire, depart }
17
- construireFiche(flotte, id, { declarations, date?, sites?, conducteurs?, onglet? }) -> fiche
18
- onglet ∈ ONGLETS (informations, documents, entretiens, affectations, historique) ; inconnu → informations
18
+ construireFiche(flotte, id, { declarations, date?, sites?, conducteurs?, agents?, onglet?, chauffeurs?, periode?, perimetre? }) -> fiche
19
+ onglet ∈ ONGLETS (informations, documents, entretiens, affectations, pleins, couts, historique) ; inconnu → informations
20
+ fiche.transitions (flotte.transitionsDepuis), categoriePermis, plans ; selon l'onglet : documents [{ type, etat, courante, anciennes }],
21
+ entretiens { plans [{ km|jours: { fait, pas, pct }, le, estime, auPlusTard }], interventions, rythme },
22
+ affectations { courante, frise, candidats [{ …chauffeur, disponibilite: libre|ce_vehicule|occupe, plaque? }], categorie },
23
+ pleins { km, rythme, courbe, releves, pleins [{ …, conso }], consommation }, couts { periode, du, au, cout, mois, parc, nonComptes }
24
+ chauffeurs : [{ id, nom, telephone, permis, habilite? }] fournis par l'application (permis et habilitation : les siens)
25
+ periode : 'mois'|'trimestre'|'annee'|{ du, au } (la requête GET convient) ; periodeDe(p, jour)
26
+ construireMonVehicule(flotte, conducteur, opts) -> { vehicule: fiche|null, prochains, rythme }
27
+ construireAptitudes(flotte, { date?, conducteur?, perimetre?, sites? }) -> [{ id, plaque, type, capacite, km, site, region, depart }] aptes d'abord
19
28
  rendreTableau(vm, { kit?, t?, locale?, liens?: { base, nouveau?, modifier?(id), affecter?(id), planifier?(id), tournee?,
20
29
  alertes?, export? }, droits?: { gerer }, declarations? }) -> { titre, sousTitre, outils, corps }
21
- rendreFiche(fiche, mêmes opts + filtres?) -> HTML
30
+ rendreFiche(fiche, mêmes opts + filtres?) -> HTML (panneau du tableau de bord)
31
+ rendrePageVehicule(fiche, opts + { message?, refus?, devise? }) -> { titre, sousTitre, fil, outils, corps }
32
+ liens : fiche(id, onglet), modifier, statut(id, t), renouveler(id, type), piece(id, e), planifier, intervention, clore(id, i),
33
+ affecter, liberer, plein, releve — chaque écriture est un formulaire POST vers le lien fourni
34
+ rendreFormulaireVehicule({ mode, vehicule?, declarations, sites, valeurs?, erreurs?, refus? }, opts + liens.enregistrer)
35
+ lireFormulaireVehicule(corps, { declarations }) -> { donnees: { code, immatriculation, type, attributs, chassis, miseEnService,
36
+ site, mode, kmDepart, echeances }, erreurs } inscrire(donnees), puis relever(kmDepart), poserEcheance(…)
37
+ lireModification(corps, vehicule, { declarations }) -> { donnees (champs changés seuls, pour modifier), erreurs }
38
+ rendreFormulaireStatut(fiche, transition, opts) ; lireStatut(corps, transition) -> { donnees: { motif, confirme }, erreurs }
39
+ FORMULAIRES { plan, intervention, cloture, plein, releve, echeance } ; rendreFormulaire(nom, { fiche, declarations?, valeurs?,
40
+ erreurs?, refus? }, opts + action) ; lireFormulaire(nom, corps, { declarations? }) -> { donnees, erreurs }
41
+ erreurDeFlotte(e) -> { champ, code } le refus du moteur sous son champ (RECUL → km, DOUBLON → immatriculation…)
42
+ rendreMonVehicule(vm, { liens: { plein, releve } }) ; rendreChoixVehicule(aptitudes, { nom?, choisi? })
43
+ rendreRefusDepart(depart, { lien }) ; rendreDisponibles(aptitudes, { exclure })
22
44
  exporterCsv(vm, opts?) -> CSV « ; » (construire avec parPage: Infinity)
23
45
  TEXTES, texte(cle, t?, vars?) t('flotte.ecran.<cle>') puis repli français
24
46
  KIT_DEFAUT { icone, kpi, badge, carte, alerte, anneau, courbes, pagination, classes } ; FLOTTE_CSS (classes fl-*) ; COULEURS
@@ -29,14 +51,18 @@ RÈGLES
29
51
  - Le texte cherche aussi le NOM du chauffeur (le moteur ne connaît que l'identifiant).
30
52
  - « Document expiré » est un indicateur (pastille), jamais un statut.
31
53
  - Une action n'apparaît que si son lien est fourni ; une action d'écriture, que si droits.gerer.
54
+ - Le module n'écrit rien : un lecteur signale les champs mal formés (requis, nombre, date) et rend les arguments du
55
+ moteur ; les règles (motif de plaque, doublon, recul, transition) restent au moteur, dont le refus revient sous
56
+ son champ, formulaire rempli.
57
+ - Le menu « Changer le statut » ne montre que transitionsDepuis(statut) ; la catégorie de permis vient du type (D23).
32
58
 
33
59
  PIÈGES
34
60
  - Toute valeur est échappée ; titre et corps des cartes du kit reçoivent du HTML déjà sûr.
35
61
  - Les nombres sont formatés par Intl (fr-FR : espace fine insécable U+202F) : en test, accepter [\s ].
36
- - Dépend de @mostajs/flotte ^0.3.0 (vehicules()) en peerDependency : l'application fournit l'instance.
62
+ - Dépend de @mostajs/flotte ^0.5.0 (vehicules, vehiculeDe, pleins, transitionsDepuis) en peerDependency : l'application fournit l'instance.
37
63
 
38
64
  EXEMPLE / TEST
39
65
  examples/verifier.mjs le parc de la maquette, page complète → test-scripts/.out/exemple-flotte-ui.html
40
- test-scripts/unit/flotte-ui.test.mjs T-FUI-1 à 10
66
+ test-scripts/unit/flotte-ui.test.mjs T-FUI-1 à 11 ; flotte-ui-planches.test.mjs T-FUI-12 à 26 (planches A à F)
41
67
 
42
68
  LICENCE AGPL-3.0-or-later · Auteur Dr Hamid MADANI <drmdh@msn.com>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mostajs/flotte-ui",
3
- "version": "0.1.1",
4
- "description": "Écrans HTML du carnet du parc (@mostajs/flotte), sans React ni script : tableau de bord (indicateurs, liste filtrée, alertes et échéances, répartitions, kilométrage, véhicules par station, top 5, actions rapides) et fiche du véhicule à onglets. Vue-modèle lue dans l'API de flotte, rendue avec le kit de composants de l'application (sa charte) ou un kit sobre par défaut.",
3
+ "version": "0.2.0",
4
+ "description": "Écrans HTML du carnet du parc (@mostajs/flotte), sans React ni script : tableau de bord, fiche du véhicule pleine page à sept onglets (documents, entretiens, affectations, pleins et relevés, coûts, historique), formulaires d'écriture et leurs lecteurs, « Mon véhicule » du livreur, aptitude en tournée. Vue-modèle lue dans l'API de flotte, rendue avec le kit de composants de l'application (sa charte) ou un kit sobre par défaut.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
7
7
  "type": "module",
@@ -13,15 +13,15 @@
13
13
  "keywords": ["mostajs", "flotte", "fleet", "fleet-management", "html", "server-side", "no-react", "dashboard"],
14
14
  "repository": { "type": "git", "url": "https://github.com/apolocine/mosta-flotte-ui" },
15
15
  "dependencies": { "@mostajs/app-shell-ui": "^0.3.0" },
16
- "peerDependencies": { "@mostajs/flotte": "^0.3.0" },
16
+ "peerDependencies": { "@mostajs/flotte": "^0.5.0" },
17
17
  "devDependencies": { "@mostajs/mjs-unit": "0.10.0" },
18
18
  "mostajs": {
19
19
  "niveau": "N3", "nature": "metier", "reutilisabilite": "mono-domaine", "depend_de": ["@mostajs/flotte", "@mostajs/app-shell-ui"],
20
20
  "question": "Comment le chef de parc voit-il son parc, et chaque véhicule, sans React ?",
21
21
  "forme": "vue",
22
22
  "entites": [],
23
- "verbes": ["construireTableau", "rendreTableau", "construireFiche", "rendreFiche", "exporterCsv"],
23
+ "verbes": ["construireTableau", "rendreTableau", "construireFiche", "rendreFiche", "rendrePageVehicule", "exporterCsv", "rendreFormulaireVehicule", "lireFormulaireVehicule", "lireModification", "rendreFormulaireStatut", "lireStatut", "rendreFormulaire", "lireFormulaire", "erreurDeFlotte", "construireMonVehicule", "rendreMonVehicule", "construireAptitudes", "rendreChoixVehicule", "rendreRefusDepart", "rendreDisponibles"],
24
24
  "interop": { "moteur": "@mostajs/flotte (vehicules, peutPartir, prochainsEntretiens, echeances, releves, affectations)", "primitives": "@mostajs/app-shell-ui (esc, pagination)", "charte": "kit de composants injecté par l'application" },
25
- "ne_fait_pas": { "stocker, calculer une règle de flotte": "@mostajs/flotte", "gabarit, menu, routes": "l'application (ou @mostajs/app-shell-ui)", "droits": "l'application (droits.gerer)" }
25
+ "ne_fait_pas": { "stocker, écrire, calculer une règle de flotte": "@mostajs/flotte (les lecteurs rendent ses arguments)", "gabarit, menu, routes": "l'application (ou @mostajs/app-shell-ui)", "droits": "l'application (droits.gerer)" }
26
26
  }
27
27
  }
package/src/commun.js ADDED
@@ -0,0 +1,80 @@
1
+ // @mostajs/flotte-ui — ce que partagent le rendu des écrans et celui des formulaires : contexte, liens, formats.
2
+ // Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
3
+ import { esc } from '@mostajs/app-shell-ui';
4
+ import { libelle } from '@mostajs/flotte';
5
+ import { texte } from './textes.js';
6
+ import { KIT_DEFAUT } from './kit.js';
7
+
8
+ export const BADGE = { en_service: ['vert', 'coche'], a_l_atelier: ['orange', 'cle'], immobilise: ['rouge', 'croix'], reforme: ['gris', 'document'] };
9
+
10
+ /** Le contexte de rendu : kit complété, traduction, formats. opts : { kit, t, locale, liens, droits, declarations, devise } */
11
+ export function contexte({ kit = {}, t, locale = 'fr-FR', liens = {}, droits = {}, declarations = {}, devise = '' } = {}) {
12
+ const k = { ...KIT_DEFAUT, ...kit, classes: { ...KIT_DEFAUT.classes, ...(kit.classes || {}) } };
13
+ const nf = (x) => new Intl.NumberFormat(locale).format(x);
14
+ const nf2 = (x) => new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(x);
15
+ const argent = (x) => (x === null || x === undefined ? '—' : `${nf2(x)}${devise ? ` ${devise}` : ''}`);
16
+ const tx = (cle, vars) => texte(cle, t, vars);
17
+ // Un code de la flotte ; à défaut de traduction, le libellé déclaré, sinon le code.
18
+ const lib = (famille, code, vars) => {
19
+ const l = libelle(famille, code, t, vars);
20
+ if (l !== code) return l;
21
+ const d = famille === 'type' ? declarations.types?.[code] : famille === 'echeance' ? declarations.echeances?.[code] : null;
22
+ return d?.libelle ?? code;
23
+ };
24
+ const jj = (d) => (d ? d.split('-').reverse().join('/') : '—');
25
+ /** Une phrase de motif ou d'avertissement : les valeurs qui sont des codes sont traduites, les dates mises en forme. */
26
+ const phrase = (famille, m) => {
27
+ const v = { ...m };
28
+ if (v.statut) v.statut = lib('statut', v.statut);
29
+ if (v.type) v.type = lib('echeance', v.type);
30
+ if (v.unite) v.unite = lib('unite', v.unite);
31
+ if (v.date) v.date = jj(v.date);
32
+ if (Number.isFinite(v.n)) v.n = nf(v.n);
33
+ return lib(famille, m.code, v);
34
+ };
35
+ return { k, c: k.classes, nf, nf2, argent, devise, tx, lib, jj, phrase, liens, droits, peutGerer: Boolean(droits.gerer), locale };
36
+ }
37
+
38
+ const nonVide = ([, v]) => v !== undefined && v !== null && v !== '';
39
+ const requete = (params) => Object.entries(params).filter(nonVide).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
40
+ /** Une URL GET : la base, puis les paramètres non vides, dans l'ordre donné. */
41
+ export function url(base, params) { const q = requete(params); return q ? `${base}?${q}` : base; }
42
+ /** Ajoute des paramètres à une URL qui en a peut-être déjà (celle que rend un lien de l'application). */
43
+ export function avec(href, params) { const q = requete(params); return q ? `${href}${href.includes('?') ? '&' : '?'}${q}` : href; }
44
+ /** Les champs cachés qui reportent la requête d'une action GET (un formulaire GET remplace la requête de son action). */
45
+ export function caches(href) {
46
+ const [chemin, q = ''] = href.split('?');
47
+ const champs = q ? q.split('&').map((p) => p.split('=').map(decodeURIComponent)) : [];
48
+ return { chemin, html: champs.map(([k, v]) => `<input type="hidden" name="${esc(k)}" value="${esc(v ?? '')}">`).join('') };
49
+ }
50
+ export const garder = (f = {}) => ({ q: f.q, type: f.type, statut: f.statut, region: f.region, ...(f.page > 1 ? { page: f.page } : {}) });
51
+
52
+ /** Le lien d'un onglet de la fiche : la fiche pleine page si l'application la fournit, sinon le tableau de bord. */
53
+ export function lienFiche(x, id, onglet = 'informations', extra = {}, filtres = {}) {
54
+ const o = onglet === 'informations' ? '' : onglet;
55
+ if (x.liens.fiche) return avec(x.liens.fiche(id, o), extra);
56
+ return url(x.liens.base || '', { ...garder(filtres), v: id, onglet: o, ...extra });
57
+ }
58
+
59
+ export function delai(x, n) {
60
+ if (n === null || n === undefined) return '';
61
+ if (n < 0) return `<span class="fl-retard">(${esc(x.tx('info.depuisJours', { n: x.nf(-n) }))})</span>`;
62
+ if (n === 0) return `<span class="fl-delai">(${esc(x.tx('info.aujourdhui'))})</span>`;
63
+ return `<span class="fl-delai">(${esc(x.tx('info.dansJours', { n: x.nf(n) }))})</span>`;
64
+ }
65
+ export const badgeStatut = (x, s) => x.k.badge(x.lib('statut', s), ...(BADGE[s] || ['gris']));
66
+ export const capacite = (x, c) => [c.kg !== null && c.kg !== undefined ? `${x.nf(c.kg)} kg` : null, c.m3 !== null && c.m3 !== undefined ? `${x.nf(c.m3)} m³` : null].filter(Boolean).join(' / ') || '—';
67
+
68
+ /** Un tableau sobre ; `lignes` : cellules déjà en HTML. */
69
+ export function tableau(x, entetes, lignes, vide, { classeLigne } = {}) {
70
+ if (!lignes.length) return `<p class="fl-vide">${esc(vide)}</p>`;
71
+ return `<div class="fl-defile"><table class="${esc(x.c.tableau)}"><thead><tr>${entetes.map((e) => `<th scope="col">${esc(e)}</th>`).join('')}</tr></thead>`
72
+ + `<tbody>${lignes.map((l, i) => `<tr${classeLigne?.(i) ? ` class="${classeLigne(i)}"` : ''}>${l.map((c) => `<td>${c}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`;
73
+ }
74
+
75
+ /** Les bandeaux : succès (role=status) et refus (role=alert). Sans « × » : ils disparaissent au rechargement. */
76
+ export const bandeauOk = (x, texteHtml) => `<div class="fl-bandeau ok" role="status">${x.k.icone('coche', 18)}<div>${texteHtml}</div></div>`;
77
+ export const bandeauKo = (x, texteHtml) => `<div class="fl-bandeau ko" role="alert">${x.k.icone('croix', 18)}<div>${texteHtml}</div></div>`;
78
+ /** Le texte d'un refus du moteur (ErreurFlotte) ou d'une erreur quelconque : jamais le message technique. */
79
+ export const texteRefus = (x, e) => (e && typeof e.code === 'string' ? x.lib('erreur', e.code) : x.tx('message.erreur'));
80
+ export const bouton = (x, classe = '') => `${esc(x.c.btn)}${classe ? ` ${classe}` : ''}`;