forked from FroSteel/Planification
Version 2.0.1 — Ajustements interface v2
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Planning Techniciens — Vue claire",
|
"name": "Planning Techniciens — Vue claire",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"description": "Réaffiche le planning du jour (itsma.vd.ch) avec pompier, absents, tooltips enrichis et thème clair/sombre.",
|
"description": "Réaffiche le planning du jour (itsma.vd.ch) avec pompier, absents, tooltips enrichis et thème clair/sombre.",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
|
|||||||
+73
-34
@@ -176,10 +176,29 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
padding: 10px 20px 0 20px;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 20px 4px 20px;
|
||||||
|
font-size: 13px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.global-stat b {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.global-stat-main b {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.global-stat-sub {
|
||||||
|
color: var(--text-faint);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
.global-stat-sep {
|
||||||
|
color: var(--text-faint);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
Grille de cartes
|
Grille de cartes
|
||||||
@@ -284,19 +303,6 @@ html, body {
|
|||||||
background: var(--danger-soft);
|
background: var(--danger-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-pompier::before {
|
|
||||||
content: "En pompier toute la journée";
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
right: 14px;
|
|
||||||
font-size: 10px;
|
|
||||||
color: var(--danger);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-bar {
|
.timeline-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -387,23 +393,43 @@ html, body {
|
|||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stats matin / après-midi / total */
|
/* Stats par carte : total en gros, matin/aprem en secondaire */
|
||||||
.card-stats {
|
.card-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
align-items: baseline;
|
||||||
padding: 6px 14px 8px 14px;
|
justify-content: space-between;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: var(--bg-muted);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.stat-total {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.stat-total-num {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.stat-total-lbl {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
background: var(--bg-muted);
|
|
||||||
}
|
}
|
||||||
.card-stats .stat-chunk b {
|
.stat-split {
|
||||||
color: var(--text);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
}
|
||||||
|
.stat-split-item b {
|
||||||
|
color: var(--text-muted);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.card-stats .stat-chunk.total {
|
.stat-split-sep {
|
||||||
margin-left: auto;
|
opacity: 0.4;
|
||||||
color: var(--text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note de statut pompier/absent en haut de carte */
|
/* Note de statut pompier/absent en haut de carte */
|
||||||
@@ -482,31 +508,44 @@ html, body {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.intervention-title {
|
/* La référence S260xxx_xxxxx est mise en avant */
|
||||||
|
.intervention-refhdr {
|
||||||
|
font-family: var(--mono);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.intervention-refhdr.no-ref {
|
||||||
|
font-family: var(--font);
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--text-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Titre type d'intervention en secondaire */
|
||||||
|
.intervention-title {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.intervention-meta {
|
.intervention-meta {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: var(--text-muted);
|
color: var(--text-faint);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.intervention-ref {
|
|
||||||
font-family: var(--mono);
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--text-faint);
|
|
||||||
}
|
|
||||||
|
|
||||||
.intervention-copy {
|
.intervention-copy {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
|||||||
@@ -683,21 +683,34 @@ function ddmmyyyyToDateNum(s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function computeStats(techs, targetDate) {
|
function computeStats(techs, targetDate) {
|
||||||
let pompiers = 0, absents = 0, activeInterventions = 0;
|
let pompiers = 0, absents = 0;
|
||||||
|
let totalInterventions = 0, morning = 0, afternoon = 0;
|
||||||
|
|
||||||
for (const tech of techs) {
|
for (const tech of techs) {
|
||||||
const isPompier = tech.interventions.some(iv => iv.isPompier);
|
const isPompier = tech.interventions.some(iv => iv.isPompier);
|
||||||
const isAbsent = isTechAbsent(tech, targetDate);
|
const isAbsent = isTechAbsent(tech, targetDate);
|
||||||
if (isPompier) pompiers++;
|
if (isPompier) pompiers++;
|
||||||
if (isAbsent) absents++;
|
if (isAbsent) absents++;
|
||||||
if (!isPompier && !isAbsent) {
|
|
||||||
activeInterventions += tech.interventions.length;
|
// Compter TOUTES les vraies interventions (même celles des pompiers)
|
||||||
|
const real = tech.interventions.filter(iv =>
|
||||||
|
iv.type !== "AL-Absence" && !iv.isPompier
|
||||||
|
);
|
||||||
|
totalInterventions += real.length;
|
||||||
|
for (const iv of real) {
|
||||||
|
const s = timeToMinutes(iv.startTime);
|
||||||
|
if (s !== null && s < 12 * 60) morning++;
|
||||||
|
else if (s !== null) afternoon++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalTechs: techs.length,
|
totalTechs: techs.length,
|
||||||
pompiers,
|
pompiers,
|
||||||
absents,
|
absents,
|
||||||
activeInterventions
|
totalInterventions,
|
||||||
|
morning,
|
||||||
|
afternoon
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,11 +781,16 @@ function renderCaptureInfo(data) {
|
|||||||
function renderStats(data) {
|
function renderStats(data) {
|
||||||
const el = document.getElementById("stats");
|
const el = document.getElementById("stats");
|
||||||
const s = data.stats;
|
const s = data.stats;
|
||||||
el.textContent =
|
el.innerHTML = `
|
||||||
`${s.totalTechs} techs · ` +
|
<span class="global-stat global-stat-main"><b>${s.totalInterventions}</b> intervention${s.totalInterventions > 1 ? "s" : ""}</span>
|
||||||
`${s.pompiers} pompier${s.pompiers > 1 ? "s" : ""} · ` +
|
<span class="global-stat global-stat-sub">(${s.morning} matin · ${s.afternoon} après-midi)</span>
|
||||||
`${s.absents} absent${s.absents > 1 ? "s" : ""} · ` +
|
<span class="global-stat-sep">·</span>
|
||||||
`${s.activeInterventions} intervention${s.activeInterventions > 1 ? "s" : ""} active${s.activeInterventions > 1 ? "s" : ""}`;
|
<span class="global-stat"><b>${s.totalTechs}</b> techs</span>
|
||||||
|
<span class="global-stat-sep">·</span>
|
||||||
|
<span class="global-stat"><b>${s.pompiers}</b> pompier${s.pompiers > 1 ? "s" : ""}</span>
|
||||||
|
<span class="global-stat-sep">·</span>
|
||||||
|
<span class="global-stat"><b>${s.absents}</b> absent${s.absents > 1 ? "s" : ""}</span>
|
||||||
|
`;
|
||||||
el.classList.remove("hidden");
|
el.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,19 +835,20 @@ function buildCard(tech, targetDate) {
|
|||||||
nameEl.textContent = tech.name;
|
nameEl.textContent = tech.name;
|
||||||
header.appendChild(nameEl);
|
header.appendChild(nameEl);
|
||||||
|
|
||||||
|
// Badge de statut (pompier/absent uniquement). Les techs actifs n'ont pas de badge
|
||||||
|
// — leurs stats complètes sont juste en dessous.
|
||||||
|
if (isPompier || isAbsent) {
|
||||||
const badge = document.createElement("div");
|
const badge = document.createElement("div");
|
||||||
badge.className = "card-tech-badge";
|
badge.className = "card-tech-badge";
|
||||||
if (isPompier) {
|
if (isPompier) {
|
||||||
badge.classList.add("badge-pompier");
|
badge.classList.add("badge-pompier");
|
||||||
badge.textContent = "Pompier" + (realInterventions.length > 0 ? ` · ${realInterventions.length} int.` : "");
|
badge.textContent = "Pompier";
|
||||||
} else if (isAbsent) {
|
|
||||||
badge.classList.add("badge-absent");
|
|
||||||
badge.textContent = "Absent" + (realInterventions.length > 0 ? ` · ${realInterventions.length} int.` : "");
|
|
||||||
} else {
|
} else {
|
||||||
badge.classList.add("badge-count");
|
badge.classList.add("badge-absent");
|
||||||
badge.textContent = realInterventions.length + " int.";
|
badge.textContent = "Absent";
|
||||||
}
|
}
|
||||||
header.appendChild(badge);
|
header.appendChild(badge);
|
||||||
|
}
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
|
||||||
// --- Body ---
|
// --- Body ---
|
||||||
@@ -899,14 +918,20 @@ function buildCard(tech, targetDate) {
|
|||||||
// 4. Timeline (pour pompier aussi, avec fond spécial ; pour absent si interventions)
|
// 4. Timeline (pour pompier aussi, avec fond spécial ; pour absent si interventions)
|
||||||
body.appendChild(buildTimeline(realInterventions, pompierBlocks, absenceBlocks, card, isPompier, isAbsent));
|
body.appendChild(buildTimeline(realInterventions, pompierBlocks, absenceBlocks, card, isPompier, isAbsent));
|
||||||
|
|
||||||
// 5. Mini-stats matin/après-midi
|
// 5. Stats matin/après-midi (total bien mis en avant)
|
||||||
if (realInterventions.length > 0) {
|
if (realInterventions.length > 0) {
|
||||||
const stats = document.createElement("div");
|
const stats = document.createElement("div");
|
||||||
stats.className = "card-stats";
|
stats.className = "card-stats";
|
||||||
stats.innerHTML = `
|
stats.innerHTML = `
|
||||||
<span class="stat-chunk"><b>${morning}</b> matin</span>
|
<div class="stat-total">
|
||||||
<span class="stat-chunk"><b>${afternoon}</b> après-midi</span>
|
<span class="stat-total-num">${realInterventions.length}</span>
|
||||||
<span class="stat-chunk total"><b>${realInterventions.length}</b> total</span>
|
<span class="stat-total-lbl">intervention${realInterventions.length > 1 ? "s" : ""}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-split">
|
||||||
|
<span class="stat-split-item"><b>${morning}</b> matin</span>
|
||||||
|
<span class="stat-split-sep">·</span>
|
||||||
|
<span class="stat-split-item"><b>${afternoon}</b> après-midi</span>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
body.appendChild(stats);
|
body.appendChild(stats);
|
||||||
}
|
}
|
||||||
@@ -951,25 +976,33 @@ function buildInterventionRow(iv, cardEl) {
|
|||||||
}
|
}
|
||||||
row.appendChild(timeEl);
|
row.appendChild(timeEl);
|
||||||
|
|
||||||
// Contenu (titre + meta)
|
// Contenu (ref en avant + titre + meta)
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
content.className = "intervention-content";
|
content.className = "intervention-content";
|
||||||
|
|
||||||
|
// Ligne 1 : la référence S260xxx_xxxxx (en évidence)
|
||||||
|
const refHeader = document.createElement("div");
|
||||||
|
refHeader.className = "intervention-refhdr";
|
||||||
|
if (iv.ref) {
|
||||||
|
refHeader.textContent = iv.ref;
|
||||||
|
} else {
|
||||||
|
refHeader.textContent = "—";
|
||||||
|
refHeader.classList.add("no-ref");
|
||||||
|
}
|
||||||
|
content.appendChild(refHeader);
|
||||||
|
|
||||||
|
// Ligne 2 : type (livraison / récupération / etc.) en plus discret
|
||||||
const title = document.createElement("div");
|
const title = document.createElement("div");
|
||||||
title.className = "intervention-title";
|
title.className = "intervention-title";
|
||||||
title.textContent = shortTitle(iv);
|
title.textContent = shortTitle(iv);
|
||||||
content.appendChild(title);
|
content.appendChild(title);
|
||||||
|
|
||||||
|
// Ligne 3 : contact / lieu
|
||||||
const meta = document.createElement("div");
|
const meta = document.createElement("div");
|
||||||
meta.className = "intervention-meta";
|
meta.className = "intervention-meta";
|
||||||
meta.textContent = shortMeta(iv);
|
meta.textContent = shortMeta(iv);
|
||||||
content.appendChild(meta);
|
content.appendChild(meta);
|
||||||
|
|
||||||
const refEl = document.createElement("div");
|
|
||||||
refEl.className = "intervention-ref";
|
|
||||||
refEl.textContent = iv.ref || "";
|
|
||||||
content.appendChild(refEl);
|
|
||||||
|
|
||||||
row.appendChild(content);
|
row.appendChild(content);
|
||||||
|
|
||||||
// Bouton copier
|
// Bouton copier
|
||||||
|
|||||||
Reference in New Issue
Block a user