@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 +38 -1
- package/README.md +34 -8
- package/llms.txt +33 -7
- package/package.json +5 -5
- package/src/commun.js +80 -0
- package/src/formulaires.js +328 -0
- package/src/index.js +8 -2
- package/src/kit.js +36 -0
- package/src/rendu.js +345 -137
- package/src/textes.js +68 -9
- package/src/vue.js +125 -11
package/src/rendu.js
CHANGED
|
@@ -1,59 +1,24 @@
|
|
|
1
1
|
// @mostajs/flotte-ui — le rendu : vue-modèle → HTML, sans script (DEC-UI-13 de Guerway). Fonctions PURES.
|
|
2
2
|
// Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
3
3
|
//
|
|
4
|
-
// Chaque filtre est un formulaire GET, chaque onglet un lien GET,
|
|
5
|
-
//
|
|
6
|
-
// codes de la flotte, de libelle()
|
|
4
|
+
// Chaque filtre est un formulaire GET, chaque onglet un lien GET, chaque menu un <details>, chaque écriture un
|
|
5
|
+
// formulaire POST vers un lien que l'application fournit. Toute valeur passe par esc(). Les composants viennent du
|
|
6
|
+
// kit (celui de l'application, sinon KIT_DEFAUT) ; les textes, de texte() ; les codes de la flotte, de libelle().
|
|
7
7
|
import { esc } from '@mostajs/app-shell-ui';
|
|
8
|
-
import {
|
|
9
|
-
import { texte } from './textes.js';
|
|
10
|
-
import { KIT_DEFAUT, COULEURS } from './kit.js';
|
|
8
|
+
import { COULEURS } from './kit.js';
|
|
11
9
|
import { ONGLETS } from './vue.js';
|
|
10
|
+
import {
|
|
11
|
+
contexte, url, caches, garder, lienFiche, delai, badgeStatut, capacite, tableau, bandeauOk, bandeauKo, texteRefus, bouton,
|
|
12
|
+
} from './commun.js';
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const lib = (famille, code, vars) => {
|
|
22
|
-
const l = libelle(famille, code, t, vars);
|
|
23
|
-
if (l !== code) return l;
|
|
24
|
-
const d = famille === 'type' ? declarations.types?.[code] : famille === 'echeance' ? declarations.echeances?.[code] : null;
|
|
25
|
-
return d?.libelle ?? code;
|
|
26
|
-
};
|
|
27
|
-
const jj = (d) => (d ? d.split('-').reverse().join('/') : '—');
|
|
28
|
-
/** Une phrase de motif ou d'avertissement : les valeurs qui sont des codes sont traduites, les dates mises en forme. */
|
|
29
|
-
const phrase = (famille, m) => {
|
|
30
|
-
const v = { ...m };
|
|
31
|
-
if (v.statut) v.statut = lib('statut', v.statut);
|
|
32
|
-
if (v.type) v.type = lib('echeance', v.type);
|
|
33
|
-
if (v.unite) v.unite = lib('unite', v.unite);
|
|
34
|
-
if (v.date) v.date = jj(v.date);
|
|
35
|
-
if (Number.isFinite(v.n)) v.n = nf(v.n);
|
|
36
|
-
return lib(famille, m.code, v);
|
|
37
|
-
};
|
|
38
|
-
return { k, c: k.classes, nf, tx, lib, jj, phrase, liens, droits, peutGerer: Boolean(droits.gerer) };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Une URL GET : la base, puis les paramètres non vides, dans l'ordre donné. */
|
|
42
|
-
function url(base, params) {
|
|
43
|
-
const q = Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
44
|
-
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
|
|
45
|
-
return q ? `${base}?${q}` : base;
|
|
46
|
-
}
|
|
47
|
-
const garder = (f = {}) => ({ q: f.q, type: f.type, statut: f.statut, region: f.region, ...(f.page > 1 ? { page: f.page } : {}) });
|
|
48
|
-
|
|
49
|
-
function delai(x, n) {
|
|
50
|
-
if (n === null || n === undefined) return '';
|
|
51
|
-
if (n < 0) return `<span class="fl-retard">(${esc(x.tx('info.depuisJours', { n: x.nf(-n) }))})</span>`;
|
|
52
|
-
if (n === 0) return `<span class="fl-delai">(${esc(x.tx('info.aujourdhui'))})</span>`;
|
|
53
|
-
return `<span class="fl-delai">(${esc(x.tx('info.dansJours', { n: x.nf(n) }))})</span>`;
|
|
54
|
-
}
|
|
55
|
-
const badgeStatut = (x, s) => x.k.badge(x.lib('statut', s), ...(BADGE[s] || ['gris']));
|
|
56
|
-
const capacite = (x, c) => [c.kg !== null ? `${x.nf(c.kg)} kg` : null, c.m3 !== null ? `${x.nf(c.m3)} m³` : null].filter(Boolean).join(' / ') || '—';
|
|
14
|
+
const STATUTS_PARC = ['en_service', 'a_l_atelier', 'immobilise'];
|
|
15
|
+
const TEINTE_DOC = { valide: 'vert', proche: 'orange', expiree: 'rouge', absente: 'gris' };
|
|
16
|
+
const TEINTE_ENT = { proche: 'orange', du: 'orange', depasse: 'rouge' };
|
|
17
|
+
const COULEURS_POSTES = { carburant: '#2878E0', entretien: '#FD8A2B', echeances: '#8B5CF6' };
|
|
18
|
+
const nomMois = (x, m, annee = false) => {
|
|
19
|
+
const s = new Intl.DateTimeFormat(x.locale, { month: 'short', ...(annee ? { year: 'numeric' } : {}), timeZone: 'UTC' }).format(new Date(`${m}-15T00:00:00Z`));
|
|
20
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
21
|
+
};
|
|
57
22
|
|
|
58
23
|
/** La variation d'un indicateur : flèche selon le sens, couleur selon le sens favorable. */
|
|
59
24
|
function variationHtml(x, v) {
|
|
@@ -66,46 +31,59 @@ function variationHtml(x, v) {
|
|
|
66
31
|
|
|
67
32
|
function indicateurs(x, I) {
|
|
68
33
|
const part = (o) => (o.part !== undefined ? `<span class="fl-doux">(${x.nf(o.part)} %)</span> ` : '');
|
|
69
|
-
const un = (o, cle, teinte, icone) => x.k.kpi({ teinte, icone, valeur: x.nf(o.n), libelle: x.tx(`kpi.${cle}`), sous: `${part(o)}${variationHtml(x, o.variation)}` });
|
|
70
|
-
return `<div class="fl-
|
|
71
|
-
+ `${un(I.enMaintenance, 'enMaintenance', '
|
|
34
|
+
const un = (o, cle, teinte, icone) => (o ? x.k.kpi({ teinte, icone, valeur: x.nf(o.n), libelle: x.tx(`kpi.${cle}`), sous: `${part(o)}${variationHtml(x, o.variation)}` }) : '');
|
|
35
|
+
return `<div class="fl-k6">${un(I.vehicules, 'vehicules', 'bleu', 'camion')}${un(I.chauffeurs, 'chauffeurs', 'vert', 'utilisateur')}${un(I.enService, 'enService', 'vert', 'coche')}`
|
|
36
|
+
+ `${un(I.enMaintenance, 'enMaintenance', 'orange', 'cle')}${un(I.horsService, 'horsService', 'rouge', 'croix')}${un(I.documentsExpires, 'documentsExpires', 'rouge', 'document')}</div>`;
|
|
72
37
|
}
|
|
73
38
|
|
|
74
39
|
/** Les actions d'une ligne : « Voir » toujours ; les autres si l'application en donne le lien (et le droit, pour écrire). */
|
|
75
40
|
function actionsLigne(x, l, filtres) {
|
|
76
41
|
const L = x.liens, base = L.base || '';
|
|
77
42
|
const a = [[url(base, { ...garder(filtres), v: l.id }), 'voir']];
|
|
43
|
+
if (L.fiche) a.push([L.fiche(l.id, ''), 'ouvrir']);
|
|
78
44
|
if (x.peutGerer && L.modifier) a.push([L.modifier(l.id), 'modifier']);
|
|
79
45
|
if (x.peutGerer && L.affecter) a.push([L.affecter(l.id), 'affecter']);
|
|
80
46
|
if (x.peutGerer && L.planifier) a.push([L.planifier(l.id), 'planifier']);
|
|
81
47
|
return a;
|
|
82
48
|
}
|
|
83
49
|
|
|
50
|
+
/** Les puces de statut (16.29) : un lien GET par statut, avec l'effectif ; la recherche est gardée. */
|
|
51
|
+
function puces(x, vm) {
|
|
52
|
+
const P = vm.puces, f = vm.filtres, base = x.liens.base || '';
|
|
53
|
+
const items = [['', x.tx('liste.tous'), P.tous], ...[...STATUTS_PARC, 'reforme'].map((s) => [s, x.lib('statut', s), P[s]])];
|
|
54
|
+
return `<nav class="fl-puces" aria-label="${esc(x.tx('liste.puces'))}">${items.map(([s, l, n]) => {
|
|
55
|
+
const actif = (f.statut || '') === s;
|
|
56
|
+
return `<a class="fl-puce${actif ? ' actif' : ''}" href="${esc(url(base, garder({ ...f, statut: s, page: 1 })))}"${actif ? ' aria-current="page"' : ''}>${esc(l)} <span class="fl-n">(${x.nf(n)})</span></a>`;
|
|
57
|
+
}).join('')}</nav>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
84
60
|
function liste(x, vm) {
|
|
85
61
|
const { filtres, liste: L, options } = vm;
|
|
86
62
|
const opt = (v, l, cour) => `<option value="${esc(v)}"${v === cour ? ' selected' : ''}>${esc(l)}</option>`;
|
|
87
63
|
const form = `<form class="fl-filtres" method="get" action="${esc(x.liens.base || '')}" role="search">`
|
|
88
64
|
+ `<input type="search" name="q" value="${esc(filtres.q)}" placeholder="${esc(x.tx('liste.recherche'))}" aria-label="${esc(x.tx('liste.recherche'))}">`
|
|
89
65
|
+ `<select name="type" aria-label="${esc(x.tx('col.type'))}">${opt('', x.tx('liste.tousTypes'), filtres.type)}${options.types.map((c) => opt(c, x.lib('type', c), filtres.type)).join('')}</select>`
|
|
90
|
-
+ `<select name="statut" aria-label="${esc(x.tx('col.statut'))}">${opt('', x.tx('liste.tousStatuts'), filtres.statut)}${options.statuts.map((c) => opt(c, x.lib('statut', c), filtres.statut)).join('')}</select>`
|
|
91
66
|
+ (options.regions.length ? `<select name="region" aria-label="${esc(x.tx('col.region'))}">${opt('', x.tx('liste.toutesRegions'), filtres.region)}${options.regions.map((c) => opt(c, c, filtres.region)).join('')}</select>` : '')
|
|
92
|
-
+ `<
|
|
67
|
+
+ (filtres.statut ? `<input type="hidden" name="statut" value="${esc(filtres.statut)}">` : '')
|
|
68
|
+
+ `<button type="submit">${esc(x.tx('liste.filtrer'))}</button></form>${vm.puces ? puces(x, vm) : ''}`;
|
|
93
69
|
if (!L.lignes.length) return `${form}<p class="fl-vide">${esc(x.tx('liste.vide'))}</p>`;
|
|
94
70
|
const dep = (d) => (d.ok
|
|
95
71
|
? `<span class="fl-depart ok" title="${esc(x.tx('depart.ouiCourt'))}">${x.k.icone('coche', 18)}<span class="fl-sr">${esc(x.tx('depart.ouiCourt'))}</span></span>`
|
|
96
72
|
: `<span class="fl-depart ko" title="${esc(d.motifs.map((m) => x.phrase('motif', m)).join(' ; '))}">${x.k.icone('croix', 18)}<span class="fl-sr">${esc(x.tx('depart.nonCourt'))}</span></span>`);
|
|
97
73
|
const choisi = vm.fiche?.vehicule.id;
|
|
98
|
-
const
|
|
99
|
-
const lignes = L.lignes.map((l, i) => {
|
|
74
|
+
const lignes = L.lignes.map((l) => {
|
|
100
75
|
const acts = actionsLigne(x, l, filtres);
|
|
101
76
|
const menu = `<details class="fl-menu"><summary aria-label="${esc(x.tx('action.menu', { plaque: l.plaque }))}"><span aria-hidden="true">⋮</span></summary><div>${acts.map(([h, cle]) => `<a href="${esc(h)}">${esc(x.tx(`action.${cle}`))}</a>`).join('')}</div></details>`;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
+ `<td class="fl-nowrap">${
|
|
105
|
-
+ `<td>${
|
|
77
|
+
const cap = l.capacite.kg !== null ? capacite(x, l.capacite) : '';
|
|
78
|
+
return `<tr${l.id === choisi ? ' class="choisi"' : ''}><td class="fl-plaque"><span class="fl-mini">${x.k.icone('camion', 18)}</span><a href="${esc(acts[0][0])}" dir="ltr">${esc(l.plaque)}</a></td>`
|
|
79
|
+
+ `<td class="fl-nowrap">${esc(x.lib('type', l.type))}${cap ? `<br><small class="fl-doux">${esc(cap)}</small>` : ''}</td>`
|
|
80
|
+
+ `<td class="fl-nowrap">${esc(l.site ?? '—')}${l.region ? `<br><small class="fl-doux">(${esc(l.region)})</small>` : ''}</td>`
|
|
81
|
+
+ `<td>${l.conducteur ? esc(l.conducteur.nom) : `<span class="fl-doux">${esc(x.tx('info.aucunChauffeur'))}</span>`}</td>`
|
|
82
|
+
+ `<td>${badgeStatut(x, l.statut)}${l.documentExpire ? `<br><span class="fl-marque">${esc(x.tx('documentExpire'))}</span>` : ''}</td>`
|
|
83
|
+
+ `<td>${dep(l.depart)}</td><td class="fl-nowrap">${l.km !== null ? `${x.nf(l.km)} km` : '—'}</td><td>${menu}</td></tr>`;
|
|
106
84
|
}).join('');
|
|
107
85
|
const C = (cle) => `<th scope="col">${esc(x.tx(`col.${cle}`))}</th>`;
|
|
108
|
-
const table = `<div class="fl-defile"><table class="${esc(x.c.tableau)}"><thead><tr>${['
|
|
86
|
+
const table = `<div class="fl-defile"><table class="${esc(x.c.tableau)}"><thead><tr>${['plaque', 'type', 'station', 'chauffeur', 'statut', 'depart', 'km', 'actions'].map(C).join('')}</tr></thead><tbody>${lignes}</tbody></table></div>`;
|
|
109
87
|
const pages = Number.isFinite(L.parPage) ? x.k.pagination({ total: L.total, page: L.page, parPage: L.parPage, lien: (p) => url(x.liens.base || '', { ...garder(filtres), page: p > 1 ? p : '' }) }) : '';
|
|
110
88
|
const exp = x.liens.export ? ` <a href="${esc(url(x.liens.export, garder(filtres)))}">${esc(x.tx('liste.exporter'))}</a>` : '';
|
|
111
89
|
return `${form}${table}<div class="fl-pied">${pages}${exp}</div>`;
|
|
@@ -119,25 +97,28 @@ function alertesHtml(x, vm) {
|
|
|
119
97
|
+ `<span>${esc(a.genre === 'entretien' ? `${x.tx('alertes.entretien')} — ${a.type}` : x.lib('echeance', a.type))}<small class="fl-plaque">${esc(a.plaque)}</small></span><span>${delai(x, a.n)}</span></li>`).join('')}</ul>`;
|
|
120
98
|
}
|
|
121
99
|
|
|
122
|
-
|
|
123
|
-
return `<div class="fl-barres">${['en_service', 'a_l_atelier', 'immobilise'].map((s) => {
|
|
124
|
-
const p = S.total ? Math.round((100 * S[s]) / S.total) : 0;
|
|
125
|
-
return `<div class="fl-barre"><span>${esc(x.lib('statut', s))}</span><span role="img" aria-label="${p} %"><i style="width:${p}%;background:${COULEURS.statut[s]}"></i></span><span><b>${x.nf(S[s])}</b> <span class="fl-doux">(${x.nf(p)} %)</span></span></div>`;
|
|
126
|
-
}).join('')}</div>`;
|
|
127
|
-
}
|
|
100
|
+
const statutsHtml = (x, S) => x.k.anneau(STATUTS_PARC.map((s) => ({ libelle: x.lib('statut', s), valeur: S[s], couleur: COULEURS.statut[s] })), { unite: x.tx('graphe.vehicules') });
|
|
128
101
|
|
|
129
|
-
function kmHtml(x, km
|
|
130
|
-
const mois = km.mois.map((m) =>
|
|
102
|
+
function kmHtml(x, km) {
|
|
103
|
+
const mois = km.mois.map((m) => nomMois(x, m));
|
|
131
104
|
return x.k.courbes(mois, [{ libelle: x.tx('graphe.kmSerie'), couleur: '#2878E0', valeurs: km.valeurs }])
|
|
132
105
|
+ `<table class="fl-donnees"><caption class="fl-sr">${esc(x.tx('graphe.kmSerie'))}</caption><thead><tr><th scope="row">${esc(x.tx('graphe.mois'))}</th>${mois.map((m) => `<th scope="col">${esc(m)}</th>`).join('')}</tr></thead>`
|
|
133
106
|
+ `<tbody><tr><th scope="row">km</th>${km.valeurs.map((v) => `<td>${x.nf(v)}</td>`).join('')}</tr></tbody></table>`;
|
|
134
107
|
}
|
|
135
108
|
|
|
109
|
+
/** Véhicules par station (16.30) : une barre empilée par statut, à l'échelle de la plus grande station. */
|
|
136
110
|
function parSiteHtml(x, vm) {
|
|
137
|
-
if (!vm.parSite.length) return
|
|
138
|
-
|
|
139
|
-
|
|
111
|
+
if (!vm.parSite.length) return '<p class="fl-vide">—</p>';
|
|
112
|
+
const max = Math.max(1, ...vm.parSite.map((s) => s.total));
|
|
113
|
+
const lignes = vm.parSite.map((s) => {
|
|
114
|
+
const parts = STATUTS_PARC.filter((k) => s[k]).map((k) => `<i style="width:${((100 * s[k]) / max).toFixed(1)}%;background:${COULEURS.statut[k]}"></i>`).join('');
|
|
115
|
+
const detail = STATUTS_PARC.map((k) => `${x.lib('statut', k)} ${x.nf(s[k])}`).join(', ');
|
|
116
|
+
return `<div class="fl-ligne-empile"><span title="${esc(s.libelle ?? '')}">${esc(s.site ?? '—')}${s.region ? ` <small class="fl-doux">(${esc(s.region)})</small>` : ''}</span><b>${x.nf(s.total)}</b>`
|
|
117
|
+
+ `<span class="fl-piste" role="img" aria-label="${esc(detail)}">${parts}</span></div>`;
|
|
118
|
+
}).join('');
|
|
119
|
+
return `<div class="fl-empile">${lignes}</div>${legende(x, STATUTS_PARC.map((k) => [x.lib('statut', k), COULEURS.statut[k]]))}`;
|
|
140
120
|
}
|
|
121
|
+
const legende = (x, items) => `<p class="fl-legende-ligne">${items.map(([l, c]) => `<span><span class="fl-point" style="background:${c}"></span> ${esc(l)}</span>`).join('')}</p>`;
|
|
141
122
|
|
|
142
123
|
function topHtml(x, vm) {
|
|
143
124
|
const act = vm.avecActivite;
|
|
@@ -148,113 +129,278 @@ function topHtml(x, vm) {
|
|
|
148
129
|
}
|
|
149
130
|
|
|
150
131
|
function rapidesHtml(x, vm) {
|
|
151
|
-
const L = x.liens, id = vm.fiche?.vehicule.id
|
|
132
|
+
const L = x.liens, id = vm.fiche?.vehicule.id;
|
|
152
133
|
const items = [
|
|
153
134
|
x.peutGerer && L.nouveau && [L.nouveau, 'nouveau', 'plein', 'plus'],
|
|
154
135
|
x.peutGerer && L.affecter && id && [L.affecter(id), 'action.affecter', 'vert', 'utilisateur'],
|
|
155
136
|
x.peutGerer && L.planifier && id && [L.planifier(id), 'action.planifier', 'orange', 'cle'],
|
|
156
|
-
id && [
|
|
137
|
+
id && [lienFiche(x, id, 'documents', {}, vm.filtres), 'action.documents', 'violet', 'document'],
|
|
157
138
|
L.tournee && [L.tournee, 'action.tournee', 'bleu', 'calendrier'],
|
|
158
|
-
id && [
|
|
139
|
+
id && [lienFiche(x, id, 'historique', {}, vm.filtres), 'action.historique', 'rouge', 'horloge'],
|
|
159
140
|
].filter(Boolean);
|
|
160
141
|
return `<div class="${esc(x.c.rapides)}">${items.map(([h, cle, teinte, ic]) => `<a class="${esc(x.c.rapide)} ${teinte}" href="${esc(h)}">${x.k.icone(ic, 18)}${esc(x.tx(cle))}</a>`).join('')}</div>`;
|
|
161
142
|
}
|
|
162
143
|
|
|
163
|
-
// ── La fiche
|
|
144
|
+
// ── La fiche : bandeau, informations, onglets ───────────────────────────────────────────────────────────────
|
|
164
145
|
function bandeau(x, d) {
|
|
165
|
-
if (d.ok) return
|
|
166
|
-
return
|
|
146
|
+
if (d.ok) return bandeauOk(x, `${esc(x.tx('depart.oui'))}${d.avertissements.length ? `<ul>${d.avertissements.map((a) => `<li>${esc(x.phrase('avertissement', a))}</li>`).join('')}</ul>` : `<br><small>${esc(x.tx('depart.aucunMotif'))}</small>`}`);
|
|
147
|
+
return bandeauKo(x, `${esc(x.tx('depart.non'))}<ul>${d.motifs.map((m) => `<li>${esc(x.phrase('motif', m))}</li>`).join('')}</ul>`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** L'état d'une échéance en une ligne : « Valide jusqu'au », « Expire le … (dans N jours) », « Expirée le ». */
|
|
151
|
+
function etatEcheance(x, e) {
|
|
152
|
+
if (e.etat === 'absente') return `<span class="${e.obligatoire ? 'fl-retard' : 'fl-doux'}">${esc(x.tx('info.absent'))}</span>`;
|
|
153
|
+
if (e.etat === 'expiree') return `<span class="fl-retard">${x.k.icone('croix', 16)} ${esc(x.tx('info.expireeLe', { date: x.jj(e.courante.fin) }))}</span>`;
|
|
154
|
+
if (e.etat === 'proche') return `<span class="fl-delai">${x.k.icone('alerte', 16)} ${esc(x.tx('info.expireBientot', { date: x.jj(e.courante.fin) }))}</span> ${delai(x, e.n)}`;
|
|
155
|
+
return `<span class="fl-ok">${x.k.icone('coche', 16)}</span> ${esc(x.tx('info.valideJusquau', { date: x.jj(e.courante.fin) }))}`;
|
|
167
156
|
}
|
|
168
157
|
|
|
169
158
|
function informations(x, f) {
|
|
170
159
|
const p = f.prochain;
|
|
171
160
|
const prochain = p ? (p.le ? `${esc(x.jj(p.le))} ${delai(x, p.dans)}${p.estime ? ` <span class="fl-doux">${esc(x.tx('info.estimee'))}</span>` : ''}<br><span class="fl-doux">${esc(p.libelle)}</span>`
|
|
172
161
|
: `${esc(p.libelle)}${p.km ? ` <span class="fl-doux">${esc(x.tx('info.auKm', { km: x.nf(p.km.echeance) }))}</span>` : ''}`) : esc(x.tx('info.aucunPlan'));
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
: e.etat === 'expiree' ? `<span class="fl-retard">${x.k.icone('croix', 16)} ${esc(x.tx('info.expireeLe', { date: x.jj(e.courante.fin) }))}</span>`
|
|
176
|
-
: e.etat === 'proche' ? `<span class="fl-delai">${x.k.icone('alerte', 16)} ${esc(x.tx('info.expireBientot', { date: x.jj(e.courante.fin) }))}</span> ${delai(x, e.n)}`
|
|
177
|
-
: `<span class="fl-ok">${x.k.icone('coche', 16)}</span> ${esc(x.tx('info.valideJusquau', { date: x.jj(e.courante.fin) }))}`;
|
|
178
|
-
return [x.lib('echeance', e.type), v];
|
|
179
|
-
});
|
|
180
|
-
const ch = f.conducteur ? `${esc(f.conducteur.nom)}${f.conducteur.telephone ? ` <span class="fl-doux">(${esc(f.conducteur.telephone)})</span>` : ''}` : esc(x.tx('info.aucunChauffeur'));
|
|
162
|
+
const ch = f.conducteur ? `${esc(f.conducteur.nom)}${f.conducteur.telephone ? ` <span class="fl-doux" dir="ltr">(${esc(f.conducteur.telephone)})</span>` : ''}` : esc(x.tx('info.aucunChauffeur'));
|
|
163
|
+
const v = f.vehicule;
|
|
181
164
|
const lignes = [
|
|
182
|
-
[x.tx('info.plaque'), esc(f.plaque)], [x.tx('info.type'), esc(x.lib('type',
|
|
165
|
+
[x.tx('info.plaque'), `<span dir="ltr">${esc(f.plaque)}</span>`], [x.tx('info.type'), esc(x.lib('type', v.type))], [x.tx('info.capacite'), esc(capacite(x, f.capacite))],
|
|
183
166
|
[x.tx('info.region'), esc(f.region ?? '—')], [x.tx('info.station'), esc(f.siteLibelle ?? '—')], [x.tx('info.km'), f.km !== null ? `${x.nf(f.km)} km` : '—'],
|
|
184
167
|
[x.tx('info.miseEnService'), esc(x.jj(f.miseEnService))], [x.tx('info.chauffeur'), ch],
|
|
185
168
|
[x.tx('info.consommation'), f.consommation !== null && f.consommation !== undefined ? `${x.nf(f.consommation)} L / 100 km` : '—'],
|
|
186
|
-
[x.tx('info.prochainEntretien'), prochain], ...
|
|
169
|
+
[x.tx('info.prochainEntretien'), prochain], ...f.echeances.map((e) => [x.lib('echeance', e.type), etatEcheance(x, e)]),
|
|
170
|
+
...(v.chassis ? [[x.tx('info.chassis'), `<span dir="ltr">${esc(v.chassis)}</span>`]] : []),
|
|
171
|
+
...(v.mode ? [[x.tx('info.mode'), esc(x.lib('mode', v.mode))]] : []),
|
|
187
172
|
];
|
|
188
|
-
return `<dl class="fl-infos ${esc(x.c.fiche)}">${lignes.map(([l,
|
|
173
|
+
return `<dl class="fl-infos ${esc(x.c.fiche)}">${lignes.map(([l, val]) => `<dt>${esc(l)}</dt><dd>${val}</dd>`).join('')}</dl>`;
|
|
189
174
|
}
|
|
190
175
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
176
|
+
// ── Documents (16.33) : une carte par échéance déclarée ──
|
|
177
|
+
function documentsHtml(x, f) {
|
|
178
|
+
const id = f.vehicule.id;
|
|
179
|
+
const piece = (e) => {
|
|
180
|
+
if (!e?.piece) return '';
|
|
181
|
+
const nom = typeof e.piece === 'string' ? e.piece : e.piece.nom ?? '';
|
|
182
|
+
const emp = typeof e.piece === 'object' && e.piece.empreinte ? String(e.piece.empreinte) : '';
|
|
183
|
+
const lien = x.liens.piece ? `<a href="${esc(x.liens.piece(id, e.id))}">${esc(nom)}</a>` : esc(nom);
|
|
184
|
+
return `<div class="fl-piece">${x.k.icone('document', 22)}<div>${lien}${emp ? `<br><small class="fl-doux" dir="ltr">${esc(x.tx('documents.empreinte', { court: `${emp.slice(0, 4)}…${emp.slice(-4)}` }))}</small>` : ''}`
|
|
185
|
+
+ `${e.par ? `<br><small class="fl-doux">${esc(x.tx('documents.deposee', { par: e.par, date: x.jj(e.deposeLe) }))}</small>` : ''}</div></div>`;
|
|
186
|
+
};
|
|
187
|
+
const cartes = f.documents.map((d) => {
|
|
188
|
+
const c = d.courante;
|
|
189
|
+
const bloque = (d.etat === 'expiree' && d.bloquante) || (d.etat === 'absente' && d.obligatoire);
|
|
190
|
+
const dl = c ? `<dl class="fl-infos">${[[x.tx('documents.reference'), c.reference], [x.tx('documents.organisme'), c.organisme], [x.tx('documents.du'), x.jj(c.debut)], [x.tx('documents.au'), x.jj(c.fin)], [x.tx('documents.cout'), c.cout ? x.argent(c.cout) : null]]
|
|
191
|
+
.filter(([, v]) => v).map(([l, v]) => `<dt>${esc(l)}</dt><dd>${esc(v)}</dd>`).join('')}</dl>` : '';
|
|
192
|
+
const renouveler = x.peutGerer && x.liens.renouveler ? `<a class="${bouton(x, 'fl-secondaire fl-bloc')}" href="${esc(x.liens.renouveler(id, d.type))}">${esc(x.tx('action.renouveler'))}</a>` : '';
|
|
193
|
+
const anciennes = d.anciennes.length ? `<details class="fl-replie"><summary>${esc(x.tx('documents.precedentes', { n: x.nf(d.anciennes.length) }))}</summary><ul>${d.anciennes.map((a) => `<li>${esc(x.jj(a.debut))} – ${esc(x.jj(a.fin))}${a.reference ? ` · <span dir="ltr">${esc(a.reference)}</span>` : ''}${a.cout ? ` · ${esc(x.argent(a.cout))}` : ''}</li>`).join('')}</ul></details>` : '';
|
|
194
|
+
return `<article class="fl-doc${bloque ? ' fl-ko' : ''}"><header><span class="fl-pastille-doc">${x.k.icone('document', 22)}</span><div><b>${esc(x.lib('echeance', d.type))}</b>`
|
|
195
|
+
+ `${d.obligatoire ? ` <small class="fl-doux">(${esc(x.tx('documents.obligatoire'))})</small>` : ''}<div>${etatEcheance(x, d)}</div></div>`
|
|
196
|
+
+ `<span class="fl-droite">${x.k.badge(x.tx(`documents.etats.${d.etat}`), TEINTE_DOC[d.etat])}</span></header>${dl}${piece(c)}`
|
|
197
|
+
+ `${bloque ? `<p class="fl-retard">${esc(x.tx('documents.bloque'))}</p>` : ''}${renouveler}${anciennes}</article>`;
|
|
198
|
+
}).join('');
|
|
199
|
+
return `<h3>${esc(x.tx('documents.titre'))}</h3><div class="fl-docs">${cartes}</div>`;
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
202
|
+
// ── Entretiens (16.34) : plans à double jauge, interventions ──
|
|
203
|
+
function entretiensHtml(x, f) {
|
|
204
|
+
const E = f.entretiens, id = f.vehicule.id, T = (c, v) => x.tx(c, v);
|
|
205
|
+
const pas = (p) => [p.tousLesKm ? T('entretiens.km', { n: x.nf(p.tousLesKm) }) : null, p.tousLesJours ? T('entretiens.jours', { n: x.nf(p.tousLesJours) }) : null]
|
|
206
|
+
.filter(Boolean).join(` ${T('entretiens.ou')} `);
|
|
207
|
+
const jauge = (cle, j, unite) => `<div class="fl-jauge"><span>${esc(T(cle))}</span><b>${x.nf(j.pct)} %</b><span class="fl-piste" role="img" aria-label="${x.nf(j.pct)} %"><i style="width:${j.pct}%"></i></span>`
|
|
208
|
+
+ `<span class="fl-doux">${esc(T('entretiens.fait', { fait: x.nf(j.fait), pas: `${x.nf(j.pas)} ${x.lib('unite', unite)}` }))}</span></div>`;
|
|
209
|
+
const prochaine = (p) => {
|
|
210
|
+
const l = [];
|
|
211
|
+
if (p.le) l.push(`<b>${esc(T('entretiens.prochaine', { date: x.jj(p.le) }))}</b> ${p.estime ? `<span class="fl-doux">(${esc(T('info.estimee'))})</span> ` : ''}${delai(x, p.dans)}`);
|
|
212
|
+
if (p.km?.echeance) l.push(esc(T('entretiens.aKm', { km: x.nf(p.km.echeance) })));
|
|
213
|
+
if (p.estime && E.rythme) l.push(esc(T('entretiens.rythme', { n: x.nf(E.rythme) })));
|
|
214
|
+
if (p.auPlusTard) l.push(esc(T('entretiens.auPlusTard', { date: x.jj(p.auPlusTard) })));
|
|
215
|
+
return `<div class="fl-prochaine">${x.k.icone('calendrier', 18)}<div>${l.join('<br>')}</div></div>`;
|
|
216
|
+
};
|
|
217
|
+
const planifier = x.peutGerer && x.liens.planifier ? `<a class="${bouton(x)}" href="${esc(x.liens.planifier(id))}">${x.k.icone('plus', 18)}${esc(T('action.planifier'))}</a>` : '';
|
|
218
|
+
const plans = E.plans.length ? `<div class="fl-plans">${E.plans.map((p) => `<article class="fl-plan"><header><span class="fl-pastille-doc">${x.k.icone('cle', 22)}</span><div><b>${esc(p.libelle)}</b>`
|
|
219
|
+
+ `<div class="fl-doux">${esc(T('entretiens.tousLes', { pas: pas(p) }))}</div></div>${p.etat !== 'ok' ? `<span class="fl-droite">${x.k.badge(T(`entretiens.etats.${p.etat}`), TEINTE_ENT[p.etat] || 'gris')}</span>` : ''}</header>`
|
|
220
|
+
+ `<div class="fl-jauges">${p.km ? jauge('entretiens.avanceKm', p.km, 'km') : ''}${p.jours ? jauge('entretiens.avanceJours', p.jours, 'jours') : ''}</div>${prochaine(p)}</article>`).join('')}</div>`
|
|
221
|
+
: `<p class="fl-vide">${esc(T('info.aucunPlan'))}</p>`;
|
|
222
|
+
const ouvrir = x.peutGerer && x.liens.intervention ? `<a class="${bouton(x)}" href="${esc(x.liens.intervention(id))}">${x.k.icone('plus', 18)}${esc(T('action.intervention'))}</a>` : '';
|
|
223
|
+
const inter = tableau(x, [T('entretiens.numero'), T('entretiens.nature'), T('entretiens.ouverte'), T('entretiens.close'), T('entretiens.atelier'), T('entretiens.kmIntervention'), T('entretiens.cout'), T('entretiens.statut'), T('col.actions')],
|
|
224
|
+
E.interventions.map((i) => [`<code>${esc(i.numero)}</code>`, x.k.badge(x.lib('nature', i.nature), i.nature === 'corrective' ? 'orange' : i.nature === 'inspection' ? 'bleu' : 'vert'), esc(x.jj(i.ouverteLe)),
|
|
225
|
+
i.closeLe ? esc(x.jj(i.closeLe)) : '—', esc(i.prestataire ?? '—'), i.km !== null && i.km !== undefined ? `${x.nf(i.km)} km` : '—', i.cout ? esc(x.argent(i.cout)) : '—',
|
|
226
|
+
i.close ? `<span class="fl-cadenas">${x.k.icone('cadenas', 14)} ${esc(T('entretiens.fermee'))}</span>` : x.k.badge(T('entretiens.enCours'), 'orange', 'horloge'),
|
|
227
|
+
!i.close && x.peutGerer && x.liens.clore ? `<a href="${esc(x.liens.clore(id, i.id))}">${esc(T('action.clore'))}</a>` : '']), T('entretiens.vide'));
|
|
228
|
+
return `<div class="fl-titre-section"><h3>${esc(T('entretiens.plans'))}</h3>${planifier}</div>${plans}<div class="fl-titre-section"><h3>${esc(T('entretiens.interventions'))}</h3>${ouvrir}</div>${inter}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Affectations (16.35) : chauffeur actuel, frise, panneau « Affecter un chauffeur » ──
|
|
232
|
+
function affectationsHtml(x, f) {
|
|
233
|
+
const A = f.affectations, id = f.vehicule.id, T = (c, v) => x.tx(c, v);
|
|
234
|
+
const qui = (a) => `<b>${esc(a.conducteur?.nom ?? '—')}</b>${a.conducteur?.telephone ? `<br><span class="fl-doux" dir="ltr">${esc(a.conducteur.telephone)}</span>` : ''}${a.permis ? ` <span class="fl-doux">· ${esc(T('affectations.permisDe', { p: a.permis }))}</span>` : ''}`;
|
|
235
|
+
const liberer = x.peutGerer && x.liens.liberer && A.courante
|
|
236
|
+
? `<form method="post" action="${esc(x.liens.liberer(id))}" class="fl-en-ligne"><button type="submit" class="${bouton(x, 'fl-secondaire')}">${esc(T('action.liberer'))}</button></form>` : '';
|
|
237
|
+
const actuel = `<div class="fl-titre-section"><h3>${esc(T('affectations.actuel'))}</h3>${liberer}</div>`
|
|
238
|
+
+ (A.courante ? `<div class="fl-actuel">${x.k.icone('utilisateur', 32)}<div>${qui(A.courante)}</div><div class="fl-droite">${esc(T('affectations.depuis', { date: x.jj(A.courante.du) }))}`
|
|
239
|
+
+ `${A.courante.par ? `<br><span class="fl-doux">${esc(T('affectations.parLe', { par: A.courante.par }))}</span>` : ''}</div></div>` : `<p class="fl-vide">${esc(T('info.aucunChauffeur'))}</p>`);
|
|
240
|
+
const frise = A.frise.length ? `<h3>${esc(T('affectations.frise'))}</h3><ol class="fl-frise">${A.frise.map((a) => `<li${a.au ? '' : ' class="actif"'}><div><b>${esc(x.jj(a.du))}</b> → ${a.au ? `<b>${esc(x.jj(a.au))}</b>` : `<b class="fl-ok">${esc(T('affectations.enCours'))}</b>`}</div>`
|
|
241
|
+
+ `<div>${qui(a)}</div>${a.par ? `<div class="fl-doux">${esc(T('affectations.parLe', { par: a.par }))}</div>` : ''}</li>`).join('')}</ol>` : `<p class="fl-vide">${esc(T('affectations.vide'))}</p>`;
|
|
242
|
+
let form = '';
|
|
243
|
+
if (x.peutGerer && x.liens.affecter && A.candidats.length) {
|
|
244
|
+
const dispo = (c) => (c.disponibilite === 'libre' ? `<span class="fl-ok">●</span> ${esc(T('affectations.libre'))}`
|
|
245
|
+
: c.disponibilite === 'ce_vehicule' ? `<span class="fl-ok">●</span> ${esc(T('affectations.ceVehicule'))}` : `<span class="fl-retard">●</span> ${esc(T('affectations.conduit', { plaque: c.plaque }))}`);
|
|
246
|
+
const lignes = A.candidats.map((c) => [`<label><input type="radio" name="conducteur" value="${esc(c.id)}"${c.disponibilite === 'ce_vehicule' ? ' checked' : ''}> ${esc(c.nom ?? c.id)}</label>`,
|
|
247
|
+
`<span dir="ltr">${esc(c.telephone ?? '—')}</span>`, `${c.permis ? esc(T('affectations.permisDe', { p: c.permis })) : '—'}${c.habilite === false ? ` ${x.k.badge(T('affectations.nonHabilite'), 'rouge')}` : ''}`, dispo(c)]);
|
|
248
|
+
form = `<form method="post" action="${esc(x.liens.affecter(id))}" class="fl-panneau"><h3>${esc(T('action.affecter'))}</h3>`
|
|
249
|
+
+ `<p>${esc(T('affectations.choisir'))}${A.categorie ? ` — ${esc(T('affectations.categorie'))} : <b>${esc(A.categorie)}</b>` : ''}</p>`
|
|
250
|
+
+ `${tableau(x, [T('affectations.nom'), T('affectations.telephone'), T('affectations.permis'), T('affectations.disponibilite')], lignes, T('affectations.aucunCandidat'))}`
|
|
251
|
+
+ `<div class="fl-boutons"><a class="${bouton(x, 'fl-secondaire')}" href="${esc(lienFiche(x, id, 'affectations'))}">${esc(T('action.annuler'))}</a><button type="submit" class="${bouton(x)}">${esc(T('action.valider'))}</button></div></form>`;
|
|
226
252
|
}
|
|
253
|
+
return `${actuel}${frise}${form}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Pleins et relevés (16.37) ──
|
|
257
|
+
function pleinsHtml(x, f) {
|
|
258
|
+
const P = f.pleins, id = f.vehicule.id, T = (c, v) => x.tx(c, v);
|
|
259
|
+
const debut = P.courbe.valeurs.findIndex((v) => v !== null);
|
|
260
|
+
const mois = debut < 0 ? [] : P.courbe.mois.slice(debut).map((m) => nomMois(x, m, true));
|
|
261
|
+
// Un mois sans relevé garde le kilométrage du mois précédent : le compteur ne redescend pas.
|
|
262
|
+
const vals = debut < 0 ? [] : P.courbe.valeurs.slice(debut).reduce((a, v) => [...a, v ?? a.at(-1)], []);
|
|
263
|
+
const courbe = `<div class="fl-titre-section"><h3>${esc(T('pleins.courbe'))}</h3><div class="fl-droite"><b>${P.km !== null ? `${x.nf(P.km)} km` : '—'}</b><br><small class="fl-doux">${esc(T('pleins.au', { date: x.jj(f.date) }))}</small></div></div>`
|
|
264
|
+
+ (vals.length ? courbeKm(x, mois, vals) : '')
|
|
265
|
+
+ (P.rythme ? `<p class="fl-doux">${esc(T('pleins.rythme', { n: x.nf(P.rythme) }))}</p>` : '')
|
|
266
|
+
+ (vals.length ? `<table class="fl-donnees"><caption class="fl-sr">${esc(T('pleins.kmReleve'))}</caption><thead><tr><th scope="row">${esc(T('graphe.mois'))}</th>${mois.map((m) => `<th scope="col">${esc(m)}</th>`).join('')}</tr></thead>`
|
|
267
|
+
+ `<tbody><tr><th scope="row">km</th>${vals.map((v) => `<td>${x.nf(v)}</td>`).join('')}</tr></tbody></table>` : '');
|
|
268
|
+
const ajouter = x.peutGerer && x.liens.releve ? `<a class="${bouton(x, 'fl-secondaire')}" href="${esc(x.liens.releve(id))}">${x.k.icone('plus', 18)}${esc(T('action.releve'))}</a>` : '';
|
|
269
|
+
const commentaire = (r) => [...(r.avertissements || []).map((a) => `<span class="fl-avert">${x.k.icone('alerte', 14)} ${esc(x.phrase('avertissement', a))}</span>`),
|
|
270
|
+
...(r.correction?.motif ? [esc(T('pleins.correction', { motif: r.correction.motif }))] : [])].join('<br>') || '—';
|
|
271
|
+
const releves = tableau(x, [T('pleins.date'), T('pleins.km'), T('pleins.source'), T('pleins.saisiPar'), T('pleins.commentaire')],
|
|
272
|
+
P.releves.map((r) => [esc(x.jj(r.le)), `${x.nf(r.km)} km`, x.k.badge(x.lib('source', r.source), r.source === 'plein' ? 'vert' : r.source === 'intervention' ? 'violet' : 'bleu'), esc(r.par ?? '—'), commentaire(r)]), T('pleins.aucunReleve'));
|
|
273
|
+
const saisir = x.peutGerer && x.liens.plein ? `<a class="${bouton(x, 'fl-secondaire')}" href="${esc(x.liens.plein(id))}">${x.k.icone('plus', 18)}${esc(T('action.plein'))}</a>` : '';
|
|
274
|
+
const moyenne = P.consommation !== null && P.consommation !== undefined ? `<span class="fl-doux">${esc(T('pleins.consoMoyenne'))} : <b>${x.nf(P.consommation)} L/100 km</b></span>` : '';
|
|
275
|
+
const pleins = tableau(x, [T('pleins.date'), T('pleins.km'), T('pleins.litres'), T('pleins.montant'), T('pleins.complet'), T('pleins.conso')],
|
|
276
|
+
P.pleins.map((p) => [esc(x.jj(p.le)), `${x.nf(p.km)} km`, `${x.nf(p.litres)} L`, esc(x.argent(p.montant)), p.complet ? x.k.badge(T('pleins.oui'), 'vert') : x.k.badge(T('pleins.non'), 'orange'),
|
|
277
|
+
p.conso !== null ? `${x.nf(p.conso)} L/100 km` : '—']), T('pleins.aucunPlein'));
|
|
278
|
+
return `${courbe}<div class="fl-titre-section"><h3>${esc(T('pleins.releves'))}</h3>${ajouter}</div>${releves}`
|
|
279
|
+
+ `<div class="fl-titre-section"><h3>${esc(T('pleins.pleins'))}</h3>${moyenne}${saisir}</div>${pleins}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* La courbe du compteur (16.37), dessinée ici : un compteur se lit entre son minimum et son maximum, pas depuis zéro
|
|
284
|
+
* (la courbe du kit, faite pour des effectifs, écraserait la pente). Graduations arrondies, aire sous la courbe.
|
|
285
|
+
*/
|
|
286
|
+
function courbeKm(x, mois, vals) {
|
|
287
|
+
const L = 640, H = 220, g = 64, bas = H - 26, haut = 12;
|
|
288
|
+
const pas = 10 ** Math.max(3, Math.floor(Math.log10(Math.max(1, Math.max(...vals) - Math.min(...vals)))));
|
|
289
|
+
const min = Math.floor(Math.min(...vals) / pas) * pas, max = Math.max(min + pas, Math.ceil(Math.max(...vals) / pas) * pas);
|
|
290
|
+
const X = (i) => g + ((L - g - 16) * i) / Math.max(1, vals.length - 1), Y = (v) => bas - ((bas - haut) * (v - min)) / (max - min);
|
|
291
|
+
const graduations = [0, 1, 2, 3, 4].map((k) => min + ((max - min) * k) / 4);
|
|
292
|
+
const pts = vals.map((v, i) => `${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(' ');
|
|
293
|
+
return `<svg class="fl-courbe" viewBox="0 0 ${L} ${H}" width="100%" role="img" aria-label="${esc(x.tx('pleins.kmReleve'))}">`
|
|
294
|
+
+ graduations.map((v) => `<line x1="${g}" x2="${L - 8}" y1="${Y(v).toFixed(1)}" y2="${Y(v).toFixed(1)}" stroke="currentColor" opacity=".12"/><text x="${g - 8}" y="${(Y(v) + 4).toFixed(1)}" text-anchor="end" font-size="11" fill="currentColor" opacity=".7">${esc(x.nf(Math.round(v)))}</text>`).join('')
|
|
295
|
+
+ `<polygon points="${X(0).toFixed(1)},${bas} ${pts} ${X(vals.length - 1).toFixed(1)},${bas}" fill="#2878E0" opacity=".1"/><polyline points="${pts}" fill="none" stroke="#2878E0" stroke-width="2.5"/>`
|
|
296
|
+
+ vals.map((v, i) => `<circle cx="${X(i).toFixed(1)}" cy="${Y(v).toFixed(1)}" r="3.5" fill="#2878E0"/>`).join('')
|
|
297
|
+
+ mois.map((m, i) => `<text x="${X(i).toFixed(1)}" y="${H - 6}" text-anchor="middle" font-size="11" fill="currentColor" opacity=".7">${esc(m)}</text>`).join('') + '</svg>';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Coûts (16.38) ──
|
|
301
|
+
function coutsHtml(x, f) {
|
|
302
|
+
const C = f.couts, c = C.cout, id = f.vehicule.id, T = (k, v) => x.tx(k, v);
|
|
303
|
+
const chips = ['mois', 'trimestre', 'annee'].map((p) => {
|
|
304
|
+
const actif = C.periode === p;
|
|
305
|
+
return `<a class="fl-puce${actif ? ' actif' : ''}" href="${esc(lienFiche(x, id, 'couts', { periode: p === 'mois' ? '' : p }))}"${actif ? ' aria-current="page"' : ''}>${esc(T(`couts.${p}`))}</a>`;
|
|
306
|
+
}).join('');
|
|
307
|
+
const action = caches(lienFiche(x, id, 'couts'));
|
|
308
|
+
const perso = `<form class="fl-filtres" method="get" action="${esc(action.chemin)}">${action.html}<input type="hidden" name="periode" value="perso">`
|
|
309
|
+
+ `<label>${esc(T('couts.du'))} <input type="date" name="du" value="${esc(C.du)}"></label><label>${esc(T('couts.au'))} <input type="date" name="au" value="${esc(C.au)}"></label>`
|
|
310
|
+
+ `<button type="submit">${esc(T(C.periode === 'perso' ? 'couts.perso' : 'couts.appliquer'))}</button></form>`;
|
|
311
|
+
const parKm = (n) => (n !== null && n !== undefined ? `${x.nf2(n)}${x.devise ? ` ${x.devise}` : ''}/km` : '—');
|
|
312
|
+
const kpis = `<div class="fl-k4">${x.k.kpi({ teinte: 'bleu', icone: 'pieces', valeur: x.argent(c.carburant), libelle: T('couts.carburant') })}`
|
|
313
|
+
+ `${x.k.kpi({ teinte: 'orange', icone: 'cle', valeur: x.argent(c.entretien), libelle: T('couts.entretien') })}`
|
|
314
|
+
+ `${x.k.kpi({ teinte: 'violet', icone: 'document', valeur: x.argent(c.echeances), libelle: T('couts.echeances') })}`
|
|
315
|
+
+ `${x.k.kpi({ teinte: 'bleu', icone: 'barres', valeur: x.argent(c.total), libelle: T('couts.total'), sous: esc(T('couts.nonComptes', { postes: C.nonComptes.map((p) => x.lib('poste', p)).join(', ') })) })}</div>`
|
|
316
|
+
+ `<div class="fl-k2">${x.k.kpi({ teinte: 'gris', icone: 'fleche', valeur: `${x.nf(c.km)} km`, libelle: T('couts.km') })}${x.k.kpi({ teinte: 'bleu', icone: 'barres', valeur: parKm(c.parKm), libelle: T('couts.parKm') })}</div>`;
|
|
317
|
+
const max = Math.max(1, ...C.mois.map((m) => m.total));
|
|
318
|
+
const postes = ['carburant', 'entretien', 'echeances'];
|
|
319
|
+
const colonnes = `<div class="fl-colonnes">${C.mois.map((m) => `<div class="fl-colonne"><span class="fl-total">${x.nf(Math.round(m.total))}</span><span class="fl-pile-col" role="img" aria-label="${esc(`${nomMois(x, m.mois)} : ${x.argent(m.total)}`)}">`
|
|
320
|
+
+ `${postes.filter((p) => m[p] > 0).map((p) => `<i style="height:${((100 * m[p]) / max).toFixed(1)}%;background:${COULEURS_POSTES[p]}"></i>`).join('')}</span><span>${esc(nomMois(x, m.mois))}</span></div>`).join('')}</div>`
|
|
321
|
+
+ legende(x, postes.map((p) => [T(`couts.${p}`), COULEURS_POSTES[p]]))
|
|
322
|
+
+ `<table class="fl-donnees"><thead><tr><th scope="row">${esc(T('graphe.mois'))}</th>${C.mois.map((m) => `<th scope="col">${esc(nomMois(x, m.mois))}</th>`).join('')}</tr></thead><tbody>`
|
|
323
|
+
+ postes.map((p) => `<tr><th scope="row">${esc(T(`couts.${p}`))}</th>${C.mois.map((m) => `<td>${x.nf(Math.round(m[p]))}</td>`).join('')}</tr>`).join('') + '</tbody></table>';
|
|
324
|
+
const parc = tableau(x, ['#', T('couts.vehicule'), T('col.type'), T('couts.km'), T('couts.carburant'), T('couts.entretien'), T('couts.echeances'), T('couts.total'), T('couts.parKm')],
|
|
325
|
+
C.parc.map((v, i) => [x.nf(i + 1), `<span dir="ltr">${esc(v.plaque)}</span>`, esc(x.lib('type', v.type)), `${x.nf(v.km)} km`, esc(x.nf2(v.carburant)), esc(x.nf2(v.entretien)), esc(x.nf2(v.echeances)), esc(x.nf2(v.total)),
|
|
326
|
+
esc(parKm(v.parKm))]), '—', { classeLigne: (i) => (C.parc[i].courant ? 'choisi' : '') });
|
|
327
|
+
return `<h3>${esc(T('couts.periode'))}</h3><div class="fl-puces">${chips}</div>${perso}${kpis}<h3>${esc(T('couts.parMois'))}</h3>${colonnes}`
|
|
328
|
+
+ `<h3>${esc(T('couts.parc'))} <span class="fl-doux">${esc(T('couts.parcSous'))}</span></h3>${parc}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function historiqueHtml(x, f) {
|
|
332
|
+
const T = (c) => x.tx(c);
|
|
333
|
+
const lignes = f.historique.map((h) => (h.vers ? [esc(x.jj(h.le)), badgeStatut(x, h.de), badgeStatut(x, h.vers), esc(h.motif ?? '—'), esc(h.agent ?? '—')]
|
|
334
|
+
: [esc(x.jj(h.le)), '—', '—', esc(x.tx('historique.modification', { champs: (h.modification?.champs || []).join(', ') })), esc(h.agent ?? '—')]));
|
|
335
|
+
lignes.push([esc(x.jj(f.vehicule.inscritLe)), '—', badgeStatut(x, 'en_service'), esc(T('historique.inscrit')), esc(f.vehicule.inscritPar ?? '—')]);
|
|
336
|
+
return tableau(x, [T('historique.le'), T('historique.de'), T('historique.vers'), T('historique.motif'), T('historique.par')], lignes, T('historique.vide'));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function contenuOnglet(x, f) {
|
|
340
|
+
if (f.onglet === 'documents' && f.documents) return documentsHtml(x, f);
|
|
341
|
+
if (f.onglet === 'entretiens' && f.entretiens) return entretiensHtml(x, f);
|
|
342
|
+
if (f.onglet === 'affectations' && f.affectations) return affectationsHtml(x, f);
|
|
343
|
+
if (f.onglet === 'pleins' && f.pleins) return pleinsHtml(x, f);
|
|
344
|
+
if (f.onglet === 'couts' && f.couts) return coutsHtml(x, f);
|
|
345
|
+
if (f.onglet === 'historique' && f.historique) return historiqueHtml(x, f);
|
|
227
346
|
return informations(x, f);
|
|
228
347
|
}
|
|
229
348
|
|
|
230
|
-
|
|
349
|
+
const ongletsHtml = (x, f, filtres) => `<nav class="${esc(x.c.onglets)} fl-serres" aria-label="${esc(x.tx('detail.titre'))}">${ONGLETS.map((o) => `<a href="${esc(lienFiche(x, f.vehicule.id, o, {}, filtres))}"${o === f.onglet ? ' aria-current="page" class="actif"' : ''}>${esc(x.tx(`onglet.${o}`))}</a>`).join('')}</nav>`;
|
|
350
|
+
|
|
351
|
+
/** La fiche d'un véhicule, en panneau (tableau de bord) : en-tête, bandeau « peut partir », onglets, contenu. */
|
|
231
352
|
export function rendreFiche(f, opts = {}) {
|
|
232
353
|
const x = contexte(opts);
|
|
233
354
|
if (!f) return `<p class="fl-vide">${esc(x.tx('detail.aucun'))}</p>`;
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
return `${tete}${bandeau(x, f.depart)}${
|
|
355
|
+
const modifier = x.peutGerer && x.liens.modifier ? `<a class="${bouton(x, 'petit')}" href="${esc(x.liens.modifier(f.vehicule.id))}">${esc(x.tx('action.modifier'))}</a>` : '';
|
|
356
|
+
const ouvrir = x.liens.fiche ? `<a class="${bouton(x, 'petit fl-secondaire')}" href="${esc(x.liens.fiche(f.vehicule.id, ''))}">${esc(x.tx('action.ouvrir'))}</a>` : '';
|
|
357
|
+
const tete = `<div class="fl-entete-fiche"><span class="fl-vignette">${x.k.icone('camion', 40)}</span><div><b dir="ltr">${esc(f.plaque)}</b><div>${esc(x.lib('type', f.vehicule.type))} · ${esc(capacite(x, f.capacite))}</div></div><div>${badgeStatut(x, f.vehicule.statut)}</div></div>`
|
|
358
|
+
+ `${modifier || ouvrir ? `<div class="fl-boutons-fiche">${ouvrir}${modifier}</div>` : ''}`;
|
|
359
|
+
return `${tete}${bandeau(x, f.depart)}${ongletsHtml(x, f, opts.filtres || {})}${contenuOnglet(x, f)}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* La fiche pleine page (16.31, 16.32). Rend { titre, sousTitre, fil, outils, corps } : `fil` est le fil d'Ariane
|
|
364
|
+
* que l'application pose dans son gabarit. opts : ceux de rendreTableau, plus { message?, refus? (une ErreurFlotte
|
|
365
|
+
* du moteur, rendue par son code), devise? } ; liens : { base, fiche(id, onglet), modifier, statut(id, transition),
|
|
366
|
+
* renouveler(id, type), piece(id, echeance), planifier, intervention, clore(id, intervention), affecter, liberer,
|
|
367
|
+
* plein, releve }.
|
|
368
|
+
*/
|
|
369
|
+
export function rendrePageVehicule(f, opts = {}) {
|
|
370
|
+
const x = contexte(opts);
|
|
371
|
+
const v = f.vehicule, id = v.id, T = (c, vars) => x.tx(c, vars);
|
|
372
|
+
const modifier = x.peutGerer && x.liens.modifier ? `<a class="${bouton(x, 'fl-secondaire')}" href="${esc(x.liens.modifier(id))}">${esc(T('action.modifier'))}</a>` : '';
|
|
373
|
+
const statut = x.peutGerer && x.liens.statut && f.transitions.length
|
|
374
|
+
? `<details class="fl-menu fl-statut"><summary class="${bouton(x)}">${esc(T('action.changerStatut'))} ▾</summary><div>${f.transitions.map((t) => `<a href="${esc(x.liens.statut(id, t.id))}">${esc(x.lib('transition', t.id))}</a>`).join('')}</div></details>` : '';
|
|
375
|
+
const ch = f.conducteur;
|
|
376
|
+
const tete = `<div class="fl-tete-page"><span class="fl-vignette">${x.k.icone('camion', 48)}</span>`
|
|
377
|
+
+ `<div class="fl-ident"><div><b class="fl-grande" dir="ltr">${esc(f.plaque)}</b> ${badgeStatut(x, v.statut)}</div><div>${esc(x.lib('type', v.type))} · ${esc(capacite(x, f.capacite))}</div>`
|
|
378
|
+
+ `<div class="fl-doux">${x.k.icone('epingle', 16)} ${esc(f.site ?? '—')}${f.region ? ` · ${esc(f.region)}` : ''} · ${f.km !== null ? `${x.nf(f.km)} km` : '—'}</div></div>`
|
|
379
|
+
+ `<div class="fl-qui">${x.k.icone('utilisateur', 20)} <div>${ch ? `${esc(ch.nom)}${ch.telephone ? `<br><span class="fl-doux" dir="ltr">${esc(ch.telephone)}</span>` : ''}` : `<span class="fl-doux">${esc(T('info.aucunChauffeur'))}</span>`}</div></div>`
|
|
380
|
+
+ `<div class="fl-actions">${modifier}${statut}</div></div>`;
|
|
381
|
+
const msg = opts.message ? bandeauOk(x, esc(opts.message)) : '';
|
|
382
|
+
const refus = opts.refus ? bandeauKo(x, esc(texteRefus(x, opts.refus))) : '';
|
|
383
|
+
const corps = `<div class="fl-ecran fl-page">${refus}${msg}<div class="fl-carte">${tete}${bandeau(x, f.depart)}</div>`
|
|
384
|
+
+ `<div class="fl-carte">${ongletsHtml(x, f)}${contenuOnglet(x, f)}</div></div>`;
|
|
385
|
+
return { titre: f.plaque, sousTitre: `${x.lib('type', v.type)} · ${capacite(x, f.capacite)}`, fil: [{ libelle: T('titre'), href: x.liens.base || null }, { libelle: f.plaque, href: null }], outils: '', corps };
|
|
239
386
|
}
|
|
240
387
|
|
|
241
388
|
/**
|
|
242
|
-
* Le tableau de bord d'ECR-16. Rend { titre, sousTitre, outils, corps } : l'application les pose dans
|
|
243
|
-
* (zone de titre et corps). opts : { kit?, t?, locale?, liens?: { base,
|
|
244
|
-
* planifier?(id), tournee?, alertes?, export? }, droits?: { gerer }, declarations? }
|
|
389
|
+
* Le tableau de bord d'ECR-16 (planche A). Rend { titre, sousTitre, outils, corps } : l'application les pose dans
|
|
390
|
+
* son gabarit (zone de titre et corps). opts : { kit?, t?, locale?, liens?: { base, fiche?(id, onglet), nouveau?,
|
|
391
|
+
* modifier?(id), affecter?(id), planifier?(id), tournee?, alertes?, export? }, droits?: { gerer }, declarations? }
|
|
245
392
|
*/
|
|
246
393
|
export function rendreTableau(vm, opts = {}) {
|
|
247
394
|
const x = contexte(opts);
|
|
248
|
-
const locale = opts.locale || 'fr-FR';
|
|
249
395
|
const outils = x.peutGerer && x.liens.nouveau ? `<a class="${esc(x.c.btn)}" href="${esc(x.liens.nouveau)}">${x.k.icone('plus', 18)}${esc(x.tx('nouveau'))}</a>` : '';
|
|
250
396
|
const carte = (cle, ic, corps, o = {}) => x.k.carte(esc(x.tx(cle)) + (o.sous ? ` <span class="fl-doux">${esc(x.tx(o.sous))}</span>` : ''), ic, corps, o);
|
|
251
397
|
const voirTout = x.liens.alertes ? `<a href="${esc(x.liens.alertes)}">${esc(x.tx('alertes.voirTout'))}</a>` : '';
|
|
252
398
|
const segments = vm.repartition.map((r, i) => ({ libelle: x.lib('type', r.type), valeur: r.n, couleur: COULEURS.types[i % COULEURS.types.length] }));
|
|
253
399
|
const corps = `<div class="fl-ecran">${indicateurs(x, vm.indicateurs)}
|
|
254
|
-
<div class="fl-large"><div class="fl-pile">${carte('liste.titre', 'liste', liste(x, vm))}
|
|
255
|
-
<div class="fl-
|
|
256
|
-
<div class="fl-
|
|
257
|
-
<div class="fl-
|
|
400
|
+
<div class="fl-large"><div class="fl-pile">${carte('liste.titre', 'liste', liste(x, vm))}</div>
|
|
401
|
+
<div class="fl-pile">${carte('detail.titre', 'utilisateur', rendreFiche(vm.fiche, { ...opts, filtres: vm.filtres }), { id: 'detail' })}${carte('alertes.titre', 'calendrier', alertesHtml(x, vm), { droite: voirTout })}${carte('rapides.titre', 'fleche', rapidesHtml(x, vm))}</div></div>
|
|
402
|
+
<div class="fl-c3">${carte('graphe.statuts', 'barres', statutsHtml(x, vm.statuts))}${carte('graphe.carte', 'epingle', parSiteHtml(x, vm), { sous: 'graphe.carteSous' })}${carte('graphe.top', 'camion', topHtml(x, vm))}</div>
|
|
403
|
+
<div class="fl-c2">${carte('graphe.types', 'barres', x.k.anneau(segments, { unite: x.tx('graphe.vehicules') }))}${carte('graphe.km', 'barres', kmHtml(x, vm.km), { sous: 'graphe.kmSous' })}</div></div>`;
|
|
258
404
|
return { titre: x.tx('titre'), sousTitre: x.tx('sousTitre'), outils, corps };
|
|
259
405
|
}
|
|
260
406
|
|
|
@@ -267,3 +413,65 @@ export function exporterCsv(vm, opts = {}) {
|
|
|
267
413
|
x.lib('statut', l.statut) + (l.documentExpire ? ` (${x.tx('documentExpire')})` : ''), l.depart.ok ? x.tx('depart.ouiCourt') : l.depart.motifs.map((m) => x.phrase('motif', m)).join(' ; ')]);
|
|
268
414
|
return `${[ent, ...lignes].map((l) => l.map(champ).join(';')).join('\n')}\n`;
|
|
269
415
|
}
|
|
416
|
+
|
|
417
|
+
// ── « Mon véhicule » du livreur (16.40) ─────────────────────────────────────────────────────────────────────
|
|
418
|
+
/** opts.liens : { plein, releve } (des URL : le livreur n'a qu'un véhicule). Rend { titre, corps }. */
|
|
419
|
+
export function rendreMonVehicule(vm, opts = {}) {
|
|
420
|
+
const x = contexte(opts), T = (c, v) => x.tx(c, v);
|
|
421
|
+
const f = vm.vehicule;
|
|
422
|
+
if (!f) return { titre: T('monVehicule.titre'), corps: `<div class="fl-ecran fl-mobile"><p class="fl-vide">${esc(T('monVehicule.aucun'))}</p></div>` };
|
|
423
|
+
const ch = f.conducteur;
|
|
424
|
+
const tete = `<div class="fl-carte"><div class="fl-entete-fiche"><span class="fl-vignette">${x.k.icone('camion', 40)}</span><div><b dir="ltr">${esc(f.plaque)}</b><div>${esc(x.lib('type', f.vehicule.type))}</div><div class="fl-doux">${esc(capacite(x, f.capacite))}</div></div></div>`
|
|
425
|
+
+ `${bandeau(x, f.depart)}<ul class="fl-liste-infos"><li>${x.k.icone('epingle', 18)} ${esc(f.site ?? '—')}${f.region ? ` — ${esc(f.region)}` : ''}</li>`
|
|
426
|
+
+ `${ch ? `<li>${x.k.icone('utilisateur', 18)} ${esc(ch.nom)}${ch.telephone ? ` <span class="fl-doux" dir="ltr">${esc(ch.telephone)}</span>` : ''}</li>` : ''}`
|
|
427
|
+
+ `<li>${x.k.icone('horloge', 18)} ${f.km !== null ? `${x.nf(f.km)} km` : '—'}</li>${f.consommation !== null && f.consommation !== undefined ? `<li>${x.k.icone('barres', 18)} ${x.nf(f.consommation)} L/100 km</li>` : ''}</ul></div>`;
|
|
428
|
+
const boutons = `<div class="fl-boutons-pleins">${x.liens.plein ? `<a class="${bouton(x, 'fl-bloc')}" href="${esc(x.liens.plein)}">${x.k.icone('plus', 20)}${esc(T('action.plein'))}</a>` : ''}`
|
|
429
|
+
+ `${x.liens.releve ? `<a class="${bouton(x, 'fl-bloc fl-secondaire')}" href="${esc(x.liens.releve)}">${x.k.icone('horloge', 20)}${esc(T('action.relever'))}</a>` : ''}</div>`;
|
|
430
|
+
const prochains = vm.prochains.length ? `<ul class="fl-liste-infos">${vm.prochains.map((p) => {
|
|
431
|
+
const l = [];
|
|
432
|
+
if (p.le) l.push(`${esc(x.jj(p.le))}${p.estime ? ` (${esc(T('info.estimee'))})` : ''}`);
|
|
433
|
+
if (p.km?.echeance) l.push(esc(T('entretiens.aKm', { km: x.nf(p.km.echeance) })));
|
|
434
|
+
if (p.estime && vm.rythme) l.push(esc(T('entretiens.rythme', { n: x.nf(vm.rythme) })));
|
|
435
|
+
if (p.estime && p.jours?.echeance) l.push(`(${esc(T('entretiens.auPlusTard', { date: x.jj(p.jours.echeance) }))})`);
|
|
436
|
+
const badge = p.etat !== 'ok' ? x.k.badge(T(`entretiens.etats.${p.etat}`), TEINTE_ENT[p.etat] || 'gris') : p.dans !== null && p.dans !== undefined ? x.k.badge(T('info.dansJours', { n: x.nf(p.dans) }), 'orange') : '';
|
|
437
|
+
return `<li>${x.k.icone('cle', 18)} <div><b>${esc(p.libelle)}</b> ${badge}<br>${l.join('<br>')}</div></li>`;
|
|
438
|
+
}).join('')}</ul>` : `<p class="fl-vide">${esc(T('info.aucunPlan'))}</p>`;
|
|
439
|
+
const docs = `<ul class="fl-liste-infos">${f.echeances.map((e) => `<li><div><b>${esc(x.lib('echeance', e.type))}</b><br>${etatEcheance(x, e)}</div></li>`).join('')}</ul>`;
|
|
440
|
+
const corps = `<div class="fl-ecran fl-mobile">${tete}${boutons}<section class="fl-carte"><h2>${esc(T('monVehicule.prochains'))}</h2>${prochains}</section>`
|
|
441
|
+
+ `<section class="fl-carte"><h2>${esc(T('monVehicule.documents'))}</h2>${docs}</section></div>`;
|
|
442
|
+
return { titre: T('monVehicule.titre'), corps };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ── Tournée : aptitude des véhicules (16.41) ────────────────────────────────────────────────────────────────
|
|
446
|
+
/** La liste des véhicules d'une tournée, chacun avec son aptitude : un bouton radio par véhicule (le formulaire est celui de l'application). */
|
|
447
|
+
export function rendreChoixVehicule(aptitudes, opts = {}) {
|
|
448
|
+
const x = contexte(opts), nom = opts.nom || 'vehicule';
|
|
449
|
+
return `<div class="fl-choix" role="radiogroup" aria-label="${esc(x.tx('tournee.vehicule'))}">${aptitudes.map((a) => {
|
|
450
|
+
const etat = a.depart.ok
|
|
451
|
+
? (a.depart.avertissements.length ? `<span class="fl-avert">${x.k.icone('alerte', 16)} ${esc(x.phrase('avertissement', a.depart.avertissements[0]))}</span>` : '')
|
|
452
|
+
: x.k.badge(a.depart.motifs.length === 1 ? x.tx('tournee.motif') : x.tx('tournee.motifs', { n: x.nf(a.depart.motifs.length) }), 'rouge', 'croix');
|
|
453
|
+
return `<label class="fl-option${a.depart.ok ? '' : ' ko'}"><input type="radio" name="${esc(nom)}" value="${esc(a.id)}"${a.id === opts.choisi ? ' checked' : ''}>`
|
|
454
|
+
+ `<span class="fl-depart ${a.depart.ok ? 'ok' : 'ko'}">${x.k.icone(a.depart.ok ? 'coche' : 'croix', 18)}</span>`
|
|
455
|
+
+ `<span><b dir="ltr">${esc(a.plaque)}</b><br><small class="fl-doux">${esc(x.lib('type', a.type))} (${esc(capacite(x, a.capacite))})</small></span>`
|
|
456
|
+
+ `<span>${a.km !== null ? `${x.nf(a.km)} km` : '—'}</span><span>${esc(a.site ?? '—')}</span><span>${etat}</span></label>`;
|
|
457
|
+
}).join('')}</div>`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Le refus de départ (côté serveur) : TOUS les motifs, et le lien vers le carnet du véhicule. '' si le départ est permis. */
|
|
461
|
+
export function rendreRefusDepart(depart, opts = {}) {
|
|
462
|
+
if (!depart || depart.ok) return '';
|
|
463
|
+
const x = contexte(opts);
|
|
464
|
+
return `<div class="fl-refus" role="alert">${x.k.icone('croix', 28)}<div><b>${esc(x.tx('tournee.refus'))}</b><p>${esc(x.tx('tournee.refusSous'))}</p>`
|
|
465
|
+
+ `<ul>${depart.motifs.map((m) => `<li>${esc(x.phrase('motif', m))}</li>`).join('')}</ul>`
|
|
466
|
+
+ `${opts.lien ? `<a class="${bouton(x, 'fl-secondaire')}" href="${esc(opts.lien)}">${esc(x.tx('tournee.carnet'))}</a>` : ''}</div></div>`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Les autres véhicules qui peuvent partir (sauf `exclure`), dans une carte du kit. */
|
|
470
|
+
export function rendreDisponibles(aptitudes, opts = {}) {
|
|
471
|
+
const x = contexte(opts);
|
|
472
|
+
const ok = aptitudes.filter((a) => a.depart.ok && a.id !== opts.exclure);
|
|
473
|
+
const corps = ok.length ? `<ul class="fl-liste-infos">${ok.map((a) => `<li><span class="fl-depart ok">${x.k.icone('coche', 18)}</span><div><b dir="ltr">${esc(a.plaque)}</b><br><small class="fl-doux">${esc(x.lib('type', a.type))} (${esc(capacite(x, a.capacite))})`
|
|
474
|
+
+ ` · ${a.km !== null ? `${x.nf(a.km)} km` : '—'}${a.region ? ` · ${esc(a.region)}` : ''}</small>${a.depart.avertissements.length ? `<br><span class="fl-avert">${esc(x.phrase('avertissement', a.depart.avertissements[0]))}</span>` : ''}</div></li>`).join('')}</ul>`
|
|
475
|
+
: `<p class="fl-vide">${esc(x.tx('tournee.aucunDisponible'))}</p>`;
|
|
476
|
+
return x.k.carte(esc(x.tx('tournee.disponibles')), 'camion', `<p class="fl-doux">${esc(x.tx('tournee.disponiblesSous'))}</p>${corps}`);
|
|
477
|
+
}
|