Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fbc1997bb | |||
| cd54764dd5 |
+2
-15
@@ -1,19 +1,8 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Planification",
|
"name": "Planification",
|
||||||
"version": "2026.5.35",
|
"version": "2026.5.37",
|
||||||
"description": "Vue claire et rapide du planning des techniciens EasyVista. Regroupe interventions et réservations par tech, affiche horaires, contact, lieu, catégorie et statut en un coup d'œil.",
|
"description": "Vue claire et rapide du planning des techniciens EasyVista. Regroupe interventions et réservations par tech, affiche horaires, contact, lieu, catégorie et statut en un coup d'œil.",
|
||||||
"browser_specific_settings": {
|
|
||||||
"gecko": {
|
|
||||||
"id": "planification@vd.ch",
|
|
||||||
"strict_min_version": "140.0",
|
|
||||||
"data_collection_permissions": {
|
|
||||||
"required": [
|
|
||||||
"none"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"scripting",
|
"scripting",
|
||||||
@@ -29,9 +18,7 @@
|
|||||||
"default_title": "Ouvrir la Planification"
|
"default_title": "Ouvrir la Planification"
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"service_worker": "background.js"
|
||||||
"background.js"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icons/icon16.png",
|
"16": "icons/icon16.png",
|
||||||
|
|||||||
+424
-30
@@ -3586,55 +3586,449 @@ html.view-classic .tech-row-stats {
|
|||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
v2026.5.35 : en vue horizontale, stats globales sur le CÔTÉ GAUCHE
|
v2026.5.35 : en vue horizontale, stats globales sur le CÔTÉ GAUCHE
|
||||||
(colonne verticale fixe) au lieu d'être au-dessus des cartes.
|
v2026.5.36 : REFONTE — les stats (et tout le reste) sont maintenant déplacés
|
||||||
Libère de la hauteur verticale pour les 8 techs.
|
physiquement dans .horizontal-sidebar (via JS _moveElementsToSidebar).
|
||||||
|
Le CSS ci-dessous est conservé au cas où une ancienne instance
|
||||||
|
ait encore .stats dans <main>, mais il ne devrait plus s'appliquer.
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
html.view-horizontal main {
|
html.view-horizontal main#main.legacy-stats-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.view-horizontal .stats {
|
/* ==========================================================================
|
||||||
|
v2026.5.36 : Vue horizontale — sidebar verticale à gauche
|
||||||
|
Contient (haut → bas) : nav date, horloge, stats, boutons actions.
|
||||||
|
Seuls restent en haut : user-badge, titre "Planification", bouton thème.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* Topbar en vue horizontale : minimaliste */
|
||||||
|
html.view-horizontal .topbar {
|
||||||
|
padding: 6px 12px !important;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
/* Cacher la zone centrale (déplacée vers sidebar) */
|
||||||
|
html.view-horizontal .topbar .app-clock,
|
||||||
|
html.view-horizontal .topbar .capture-info,
|
||||||
|
html.view-horizontal .topbar .app-session {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/* topbar-left ne contient plus que user-badge + titre */
|
||||||
|
html.view-horizontal .topbar-left {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
/* topbar-right ne contient plus que le theme-toggle */
|
||||||
|
html.view-horizontal .topbar-right {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar verticale à gauche */
|
||||||
|
.horizontal-sidebar {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
flex-direction: column !important;
|
min-width: 200px;
|
||||||
align-items: flex-start !important;
|
max-width: 200px;
|
||||||
gap: 8px !important;
|
|
||||||
padding: 12px 16px !important;
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
background: var(--bg-muted);
|
background: var(--bg-muted);
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
min-height: calc(100vh - 80px);
|
max-height: calc(100vh - 48px); /* topbar ~48px */
|
||||||
font-size: 12px !important;
|
font-size: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.view-horizontal .stats .global-stat {
|
/* Cacher la sidebar en vue classique (au cas où elle existe encore) */
|
||||||
display: block;
|
html.view-classic .horizontal-sidebar {
|
||||||
width: 100%;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
html.view-horizontal .stats .global-stat-main b {
|
|
||||||
font-size: 18px !important;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
/* Masquer les séparateurs "·" en vue verticale */
|
|
||||||
html.view-horizontal .stats .global-stat-sep {
|
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.view-horizontal .cards {
|
/* Layout main avec sidebar */
|
||||||
flex: 1 1 auto;
|
html.view-horizontal main#main {
|
||||||
min-width: 0; /* autorise le shrink si grand nombre de techs */
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Breakpoint étroit : stats plus compactes */
|
/* Groupe navigation date dans la sidebar — v2026.5.36 (OBSOLÈTE v2026.5.37)
|
||||||
|
v2026.5.37 : date-nav devient display: contents pour que ses enfants
|
||||||
|
soient directement positionnables dans la sidebar (btn-today
|
||||||
|
en haut, app-clock intercalé, flèches groupées dans wrapper).
|
||||||
|
Les anciennes règles ci-dessous sont conservées pour compat
|
||||||
|
mais neutralisées par les overrides v5.37 plus bas. */
|
||||||
|
html.view-horizontal .horizontal-sidebar .date-nav {
|
||||||
|
/* v5.37 override via display: contents ci-dessous */
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .date-nav .btn-nav {
|
||||||
|
/* v5.37 : ces règles sont conservées au cas où flèches restent dans date-nav
|
||||||
|
(pas censé arriver puisque JS les sort dans #sidebar-arrows). */
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .date-nav .date-custom {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .date-nav .btn-today {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horloge dans la sidebar */
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock {
|
||||||
|
position: static !important;
|
||||||
|
transform: none !important;
|
||||||
|
left: auto !important;
|
||||||
|
top: auto !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock-date {
|
||||||
|
font-size: 11px !important;
|
||||||
|
color: var(--text-muted) !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock-date::after {
|
||||||
|
content: "" !important; /* supprimer le gros point · */
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock-time {
|
||||||
|
font-size: 20px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
color: var(--text) !important;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info capture (Synchronisé à...) */
|
||||||
|
html.view-horizontal .horizontal-sidebar .capture-info {
|
||||||
|
display: block !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats globales dans la sidebar (séparateurs cachés) */
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
gap: 4px !important;
|
||||||
|
padding: 6px 4px !important;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
margin: 2px 0;
|
||||||
|
background: transparent !important;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats .global-stat {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px 0;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats .global-stat b {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats .global-stat-main b {
|
||||||
|
font-size: 15px !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats .global-stat-sep {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats .global-stat-sub {
|
||||||
|
display: block !important;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Boutons d'action dans la sidebar : pleine largeur, empilés */
|
||||||
|
html.view-horizontal .horizontal-sidebar button.btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
padding: 6px 10px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .btn-action-label,
|
||||||
|
html.view-horizontal .horizontal-sidebar .btn-refresh-label {
|
||||||
|
display: inline !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .btn-action-icon,
|
||||||
|
html.view-horizontal .horizontal-sidebar .btn-refresh-icon {
|
||||||
|
flex: 0 0 16px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grille cards prend le reste de la largeur */
|
||||||
|
html.view-horizontal .cards {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* v2026.5.36 : zone nom tech encore plus petite (140 → 120px) */
|
||||||
|
html.view-horizontal .card-header {
|
||||||
|
min-width: 120px !important;
|
||||||
|
max-width: 120px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Annuler les règles plus anciennes v2026.5.35 qui mettaient les stats à gauche
|
||||||
|
de manière inline (elles sont désormais dans la sidebar unifiée) */
|
||||||
|
html.view-horizontal main .stats {
|
||||||
|
/* Les stats sont maintenant DANS la sidebar, pas dans main. Si pour une
|
||||||
|
raison quelconque elles y restent, on les cache. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breakpoint étroit : sidebar plus fine */
|
||||||
@media (max-width: 1400px) {
|
@media (max-width: 1400px) {
|
||||||
html.view-horizontal .stats {
|
.horizontal-sidebar {
|
||||||
width: 160px;
|
width: 170px;
|
||||||
padding: 8px 12px !important;
|
min-width: 170px;
|
||||||
font-size: 11px !important;
|
max-width: 170px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
html.view-horizontal .card-header {
|
||||||
|
min-width: 110px !important;
|
||||||
|
max-width: 110px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
v2026.5.36 : layout global du body en vue horizontale
|
||||||
|
La topbar reste en haut (pleine largeur), puis body devient flex-row :
|
||||||
|
[sidebar] + [main]
|
||||||
|
========================================================================== */
|
||||||
|
html.view-horizontal body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
html.view-horizontal body > header.topbar {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
html.view-horizontal body {
|
||||||
|
/* Le body doit permettre à sidebar + main de se placer côte à côte.
|
||||||
|
On met un wrapper flex via un pseudo-selecteur impossible à hacker
|
||||||
|
proprement sans JS, donc on utilise display:flex sur body et
|
||||||
|
flex-direction: row pour tout sauf la topbar et quelques éléments
|
||||||
|
absolument positionnés (toasts, modals...). Plus simple :
|
||||||
|
on rend .horizontal-sidebar et main voisins via flex sur un wrapper
|
||||||
|
JS-créé (plus bas). À défaut, positioning absolute fallback. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback : si pas de wrapper, on positionne .horizontal-sidebar en sticky
|
||||||
|
dans le flux normal. <main> n'est pas juste à côté mais en dessous —
|
||||||
|
ce n'est pas l'idéal, d'où le wrapper JS. */
|
||||||
|
|
||||||
|
/* v2026.5.36 : wrapper flex-row pour sidebar + main en vue horizontale */
|
||||||
|
.horizontal-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-wrapper > main#main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
v2026.5.37 : refonte layout vue horizontale
|
||||||
|
- Topbar en haut : masquée complètement (user-badge + titre descendent
|
||||||
|
dans la sidebar)
|
||||||
|
- Sidebar : user-badge + titre en haut, date/heure sous le bouton Auj.,
|
||||||
|
stats puis espace libre puis boutons en bas (direction colonne-inverse)
|
||||||
|
- Progress-bar : overlay par-dessus user-badge + titre
|
||||||
|
- Banderole "En pompier du..." : masquée en vue horizontale
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* 1. Topbar masquée complètement en vue horizontale */
|
||||||
|
html.view-horizontal body > header.topbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Sidebar : structure verticale avec section fixe en haut (user+titre+date)
|
||||||
|
et section "boutons" en bas poussée via margin-top: auto. */
|
||||||
|
html.view-horizontal .horizontal-sidebar {
|
||||||
|
max-height: 100vh !important;
|
||||||
|
padding-top: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. User-badge et titre dans la sidebar, côte à côte en haut */
|
||||||
|
html.view-horizontal .horizontal-sidebar #user-badge {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto 2px auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #app-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
margin: 0 0 8px 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Bouton "Aujourd'hui" en pleine largeur, avant date/heure */
|
||||||
|
/* v2026.5.37 : on utilise un DOM wrapper #sidebar-arrows créé en JS pour
|
||||||
|
les 2 flèches côte à côte. date-nav est décomposé en : [Auj.] + [clock
|
||||||
|
intercalé] + [date-custom] + [arrows-wrapper]. Le JS s'en occupe. */
|
||||||
|
html.view-horizontal .horizontal-sidebar .date-nav {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
/* Le bouton Aujourd'hui devient prominent */
|
||||||
|
html.view-horizontal .horizontal-sidebar .btn-today {
|
||||||
|
order: 1; /* tout en haut après titre */
|
||||||
|
width: 100% !important;
|
||||||
|
text-align: center !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
background: var(--bg) !important;
|
||||||
|
border: 1px solid var(--border) !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
color: var(--text) !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .btn-today::before {
|
||||||
|
content: "↺ ";
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. App-clock (date + heure) centré sous le bouton "Aujourd'hui" */
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock {
|
||||||
|
order: 2;
|
||||||
|
align-items: center !important;
|
||||||
|
text-align: center !important;
|
||||||
|
padding: 6px 4px !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock-date {
|
||||||
|
text-align: center !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar .app-clock-time {
|
||||||
|
text-align: center !important;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 22px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. Séparateur visuel après date/heure (avant sélecteur date)
|
||||||
|
v2026.5.37 : on override order:-1 qui venait de v5.36 */
|
||||||
|
html.view-horizontal .horizontal-sidebar .date-nav .date-custom-wrapper {
|
||||||
|
order: 3 !important;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. Flèches ◀ ▶ côte à côte via wrapper JS #sidebar-arrows */
|
||||||
|
html.view-horizontal .horizontal-sidebar #sidebar-arrows {
|
||||||
|
order: 4;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #sidebar-arrows .btn-nav {
|
||||||
|
flex: 1 1 0 !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 8. Stats empilées avec order pour venir après les flèches */
|
||||||
|
html.view-horizontal .horizontal-sidebar #stats {
|
||||||
|
order: 5;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9. Capture-info (Synchronisé à HH:MM) sous les stats */
|
||||||
|
html.view-horizontal .horizontal-sidebar .capture-info {
|
||||||
|
order: 6;
|
||||||
|
margin-top: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 10. Boutons poussés en bas via margin-top: auto sur le premier d'entre eux
|
||||||
|
(Absence, qui a order:7) */
|
||||||
|
html.view-horizontal .horizontal-sidebar #absence-btn {
|
||||||
|
order: 7;
|
||||||
|
margin-top: auto !important; /* pousse tout ce qui suit en bas */
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #douchette-btn { order: 8; }
|
||||||
|
html.view-horizontal .horizontal-sidebar #refresh-partial-btn { order: 9; }
|
||||||
|
html.view-horizontal .horizontal-sidebar #refresh-btn { order: 10; }
|
||||||
|
html.view-horizontal .horizontal-sidebar #clear-cache-btn { order: 11; }
|
||||||
|
html.view-horizontal .horizontal-sidebar #theme-toggle {
|
||||||
|
order: 12;
|
||||||
|
margin-top: 4px !important;
|
||||||
|
}
|
||||||
|
html.view-horizontal .horizontal-sidebar #abort-btn {
|
||||||
|
order: 8; /* avec douchette-btn (qui est aussi 8) — ne devrait pas
|
||||||
|
être visible en même temps (un seul actif à la fois) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 11. Theme-toggle en bas : pleine largeur centrée */
|
||||||
|
html.view-horizontal .horizontal-sidebar #theme-toggle {
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 6px !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 12. Sidebar doit être flex column pour que margin-top:auto fonctionne */
|
||||||
|
html.view-horizontal .horizontal-sidebar {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 13. Barre de rafraîchissement en vue horizontale : overlay par-dessus
|
||||||
|
user-badge + titre (zone haut de sidebar). */
|
||||||
|
html.view-horizontal #progress-bar {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
width: 200px !important; /* largeur de la sidebar */
|
||||||
|
z-index: 9999 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
/* Sur breakpoint étroit (sidebar 170px) */
|
||||||
|
@media (max-width: 1400px) {
|
||||||
|
html.view-horizontal #progress-bar {
|
||||||
|
width: 170px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 14. Banderole "En pompier du..." : masquée en vue horizontale uniquement
|
||||||
|
(la barre rouge à gauche et le badge POMPIER restent visibles). */
|
||||||
|
html.view-horizontal .card-status-note.pompier {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1046,10 +1046,253 @@ function _toggleView() {
|
|||||||
_setCurrentView(next);
|
_setCurrentView(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v2026.5.36 : applique le mode de vue (classique/horizontal) en déplaçant
|
||||||
|
* physiquement les éléments de la topbar vers/depuis une sidebar verticale
|
||||||
|
* à gauche de l'écran.
|
||||||
|
*
|
||||||
|
* En vue horizontale :
|
||||||
|
* - Sidebar gauche verticale contenant (haut → bas) :
|
||||||
|
* · Navigation date (prev / date / next / aujourd'hui)
|
||||||
|
* · Horloge + date (compacte, une par ligne)
|
||||||
|
* · Info de synchro
|
||||||
|
* · Stats globales (interventions/techs/absents)
|
||||||
|
* · Boutons actions (Absence, Douchette, Actualiser, Tout recharger, Vider cache)
|
||||||
|
* - Topbar réduite à : user-badge + titre + theme-toggle
|
||||||
|
*
|
||||||
|
* En vue classique :
|
||||||
|
* - Tout est remis dans la topbar comme avant (topbar-left / topbar-right)
|
||||||
|
*
|
||||||
|
* On mémorise les parents d'origine sur chaque élément (data-orig-parent)
|
||||||
|
* pour restaurer proprement en vue classique.
|
||||||
|
*
|
||||||
|
* Logs [viewMode] pour debug.
|
||||||
|
*/
|
||||||
function _applyViewMode() {
|
function _applyViewMode() {
|
||||||
const mode = _getCurrentView();
|
const mode = _getCurrentView();
|
||||||
|
console.log(`[viewMode] application de la vue : ${mode}`);
|
||||||
|
|
||||||
|
// Mettre à jour la classe sur <html> pour les règles CSS
|
||||||
document.documentElement.classList.remove("view-classic", "view-horizontal");
|
document.documentElement.classList.remove("view-classic", "view-horizontal");
|
||||||
document.documentElement.classList.add("view-" + mode);
|
document.documentElement.classList.add("view-" + mode);
|
||||||
|
|
||||||
|
// Liste des IDs à déplacer entre topbar (classique) et sidebar (horizontal)
|
||||||
|
// Ordre = ordre visuel dans la sidebar en mode horizontal (haut → bas)
|
||||||
|
// v2026.5.37 : user-badge, app-title, theme-toggle déplacés aussi → la topbar
|
||||||
|
// devient vide en vue horizontale.
|
||||||
|
const ELEMENTS_TO_RELOCATE = [
|
||||||
|
"user-badge", // tout en haut de la sidebar
|
||||||
|
"app-title", // juste après user-badge
|
||||||
|
"date-nav", // (pas un id mais une classe, traité à part)
|
||||||
|
"app-clock",
|
||||||
|
"capture-info",
|
||||||
|
"stats", // conteneur stats globales (généré dynamiquement)
|
||||||
|
"absence-btn",
|
||||||
|
"douchette-btn",
|
||||||
|
"refresh-partial-btn",
|
||||||
|
"refresh-btn",
|
||||||
|
"abort-btn",
|
||||||
|
"clear-cache-btn",
|
||||||
|
"theme-toggle" // tout en bas de la sidebar
|
||||||
|
];
|
||||||
|
|
||||||
|
if (mode === "horizontal") {
|
||||||
|
_moveElementsToSidebar(ELEMENTS_TO_RELOCATE);
|
||||||
|
} else {
|
||||||
|
_restoreElementsToTopbar(ELEMENTS_TO_RELOCATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[viewMode] application terminée (mode=${mode})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v2026.5.36 : crée ou retrouve la sidebar, et y déplace les éléments listés.
|
||||||
|
* Les éléments sont déplacés dans l'ordre du tableau.
|
||||||
|
*/
|
||||||
|
function _moveElementsToSidebar(ids) {
|
||||||
|
// Créer (ou retrouver) le wrapper qui contiendra sidebar + main côte à côte
|
||||||
|
let wrapper = document.getElementById("horizontal-wrapper");
|
||||||
|
const mainEl = document.getElementById("main");
|
||||||
|
if (!wrapper) {
|
||||||
|
wrapper = document.createElement("div");
|
||||||
|
wrapper.id = "horizontal-wrapper";
|
||||||
|
wrapper.className = "horizontal-wrapper";
|
||||||
|
// Placer le wrapper à la place de <main>, puis mettre <main> DEDANS
|
||||||
|
if (mainEl && mainEl.parentNode) {
|
||||||
|
mainEl.parentNode.insertBefore(wrapper, mainEl);
|
||||||
|
wrapper.appendChild(mainEl);
|
||||||
|
console.log("[viewMode] wrapper créé, <main> encapsulé");
|
||||||
|
} else {
|
||||||
|
console.warn("[viewMode] <main> introuvable, wrapper ajouté à <body>");
|
||||||
|
document.body.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer la sidebar si elle n'existe pas, et la mettre EN PREMIER dans le wrapper
|
||||||
|
let sidebar = document.getElementById("horizontal-sidebar");
|
||||||
|
if (!sidebar) {
|
||||||
|
sidebar = document.createElement("aside");
|
||||||
|
sidebar.id = "horizontal-sidebar";
|
||||||
|
sidebar.className = "horizontal-sidebar";
|
||||||
|
wrapper.insertBefore(sidebar, wrapper.firstChild);
|
||||||
|
console.log("[viewMode] sidebar créée et insérée en tête du wrapper");
|
||||||
|
} else if (sidebar.parentNode !== wrapper) {
|
||||||
|
// Sidebar existe ailleurs, on la remet dans le wrapper
|
||||||
|
wrapper.insertBefore(sidebar, wrapper.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traitement spécial : .date-nav (classe, pas id)
|
||||||
|
// v2026.5.37 : en vue horizontale, on décompose .date-nav pour :
|
||||||
|
// - Mettre btn-today en haut
|
||||||
|
// - Intercaler app-clock entre btn-today et date-custom
|
||||||
|
// - Grouper les 2 flèches dans un wrapper #sidebar-arrows pour qu'elles
|
||||||
|
// soient côte à côte et même largeur
|
||||||
|
const dateNav = document.querySelector(".date-nav");
|
||||||
|
if (dateNav) {
|
||||||
|
_memorizeOriginalParent(dateNav);
|
||||||
|
sidebar.appendChild(dateNav);
|
||||||
|
console.log("[viewMode] déplacé dans sidebar : .date-nav");
|
||||||
|
|
||||||
|
// Créer le wrapper des flèches si pas déjà fait, et y mettre prev + next
|
||||||
|
let arrowsWrap = document.getElementById("sidebar-arrows");
|
||||||
|
if (!arrowsWrap) {
|
||||||
|
arrowsWrap = document.createElement("div");
|
||||||
|
arrowsWrap.id = "sidebar-arrows";
|
||||||
|
sidebar.appendChild(arrowsWrap);
|
||||||
|
console.log("[viewMode] wrapper flèches #sidebar-arrows créé");
|
||||||
|
}
|
||||||
|
const prevBtn = document.getElementById("nav-prev");
|
||||||
|
const nextBtn = document.getElementById("nav-next");
|
||||||
|
if (prevBtn && !arrowsWrap.contains(prevBtn)) {
|
||||||
|
_memorizeOriginalParent(prevBtn);
|
||||||
|
arrowsWrap.appendChild(prevBtn);
|
||||||
|
}
|
||||||
|
if (nextBtn && !arrowsWrap.contains(nextBtn)) {
|
||||||
|
_memorizeOriginalParent(nextBtn);
|
||||||
|
arrowsWrap.appendChild(nextBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pour chaque ID listé, trouver et déplacer
|
||||||
|
for (const id of ids) {
|
||||||
|
if (id === "date-nav") continue; // déjà traité
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el) {
|
||||||
|
console.log(`[viewMode] élément #${id} introuvable (optionnel) — skip`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_memorizeOriginalParent(el);
|
||||||
|
sidebar.appendChild(el);
|
||||||
|
console.log(`[viewMode] déplacé dans sidebar : #${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v2026.5.36 : restaure chaque élément à son parent d'origine (mémorisé dans
|
||||||
|
* data-orig-parent). Utilisé quand on revient en vue classique.
|
||||||
|
*/
|
||||||
|
function _restoreElementsToTopbar(ids) {
|
||||||
|
// v2026.5.37 : d'abord remettre les flèches dans .date-nav (avant de
|
||||||
|
// restaurer .date-nav à son parent d'origine), puis supprimer le wrapper
|
||||||
|
// #sidebar-arrows.
|
||||||
|
const dateNav = document.querySelector(".date-nav");
|
||||||
|
const prevBtn = document.getElementById("nav-prev");
|
||||||
|
const nextBtn = document.getElementById("nav-next");
|
||||||
|
if (dateNav && prevBtn && !dateNav.contains(prevBtn)) {
|
||||||
|
// Remettre prev en premier dans date-nav
|
||||||
|
dateNav.insertBefore(prevBtn, dateNav.firstChild);
|
||||||
|
}
|
||||||
|
if (dateNav && nextBtn && !dateNav.contains(nextBtn)) {
|
||||||
|
// Remettre next après date-custom-wrapper (position d'origine) :
|
||||||
|
// l'ordre d'origine est [prev, date-custom-wrapper, next, today].
|
||||||
|
const dateCustomWrap = dateNav.querySelector(".date-custom-wrapper");
|
||||||
|
if (dateCustomWrap && dateCustomWrap.nextSibling) {
|
||||||
|
dateNav.insertBefore(nextBtn, dateCustomWrap.nextSibling);
|
||||||
|
} else {
|
||||||
|
dateNav.appendChild(nextBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Supprimer le wrapper des flèches (normalement vide maintenant)
|
||||||
|
const arrowsWrap = document.getElementById("sidebar-arrows");
|
||||||
|
if (arrowsWrap) {
|
||||||
|
arrowsWrap.remove();
|
||||||
|
console.log("[viewMode] wrapper #sidebar-arrows supprimé");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restaurer .date-nav à son parent d'origine
|
||||||
|
if (dateNav) _restoreToOriginalParent(dateNav, ".date-nav");
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
if (id === "date-nav") continue;
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el) continue;
|
||||||
|
_restoreToOriginalParent(el, "#" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer la sidebar si elle existe et est vide
|
||||||
|
const sidebar = document.getElementById("horizontal-sidebar");
|
||||||
|
if (sidebar && sidebar.children.length === 0) {
|
||||||
|
sidebar.remove();
|
||||||
|
console.log("[viewMode] sidebar supprimée (vide)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sortir <main> du wrapper et supprimer le wrapper
|
||||||
|
const wrapper = document.getElementById("horizontal-wrapper");
|
||||||
|
if (wrapper) {
|
||||||
|
const mainEl = wrapper.querySelector("#main");
|
||||||
|
if (mainEl && wrapper.parentNode) {
|
||||||
|
wrapper.parentNode.insertBefore(mainEl, wrapper);
|
||||||
|
console.log("[viewMode] <main> restauré hors du wrapper");
|
||||||
|
}
|
||||||
|
wrapper.remove();
|
||||||
|
console.log("[viewMode] wrapper supprimé");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _memorizeOriginalParent(el) {
|
||||||
|
if (el.dataset.origParent) return; // déjà mémorisé
|
||||||
|
const parent = el.parentNode;
|
||||||
|
if (!parent || !parent.id) {
|
||||||
|
// Parent sans id : on mémorise par selector (parent tag + classes)
|
||||||
|
let selector = parent.tagName.toLowerCase();
|
||||||
|
if (parent.className) {
|
||||||
|
const cls = parent.className.split(/\s+/)[0];
|
||||||
|
if (cls) selector += "." + cls;
|
||||||
|
}
|
||||||
|
el.dataset.origParent = "SEL:" + selector;
|
||||||
|
} else {
|
||||||
|
el.dataset.origParent = "ID:" + parent.id;
|
||||||
|
}
|
||||||
|
// Mémoriser aussi l'index d'origine pour restaurer l'ordre
|
||||||
|
const siblings = Array.from(parent.children);
|
||||||
|
el.dataset.origIndex = String(siblings.indexOf(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _restoreToOriginalParent(el, label) {
|
||||||
|
const orig = el.dataset.origParent;
|
||||||
|
if (!orig) {
|
||||||
|
console.log(`[viewMode] ${label} n'a pas de parent d'origine mémorisé — skip`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let parent = null;
|
||||||
|
if (orig.startsWith("ID:")) {
|
||||||
|
parent = document.getElementById(orig.substring(3));
|
||||||
|
} else if (orig.startsWith("SEL:")) {
|
||||||
|
parent = document.querySelector(orig.substring(4));
|
||||||
|
}
|
||||||
|
if (!parent) {
|
||||||
|
console.warn(`[viewMode] parent d'origine pour ${label} introuvable (orig=${orig})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Insérer à l'index d'origine si possible
|
||||||
|
const idx = parseInt(el.dataset.origIndex || "0", 10);
|
||||||
|
const refChild = parent.children[idx] || null;
|
||||||
|
if (refChild && refChild !== el) {
|
||||||
|
parent.insertBefore(el, refChild);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(el);
|
||||||
|
}
|
||||||
|
console.log(`[viewMode] restauré à ${orig} : ${label}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// v5.0.0 : horloge HH:MM au milieu de la topbar. Mise à jour toutes les 30s
|
// v5.0.0 : horloge HH:MM au milieu de la topbar. Mise à jour toutes les 30s
|
||||||
|
|||||||
Reference in New Issue
Block a user