feat: implement native drag & drop planning and responsive performance spinners v3.1.0
This commit is contained in:
parent
250a1f1617
commit
346fef85f4
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"block-no-empty": true,
|
||||||
|
"color-no-invalid-hex": true,
|
||||||
|
"comment-no-empty": true,
|
||||||
|
"declaration-block-no-duplicate-properties": true,
|
||||||
|
"declaration-block-no-shorthand-property-overrides": true
|
||||||
|
}
|
||||||
|
}
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -7,6 +7,16 @@ et ce projet respecte le [Versionnement Sémantique](https://semver.org/lang/fr/
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [3.1.0] - 2026-05-27
|
||||||
|
### Ajouté
|
||||||
|
- **Moteur Drag & Drop Natif :** Implémentation d'un système de planification agile (Kanban) dans `app.js` permettant de glisser-déposer les séances d'entraînement à la souris sans aucune dépendance logicielle externe.
|
||||||
|
- **Cockpit Physiologique & Matériel :** Intégration de 4 indicateurs de performance (Fraîcheur VRC, Charge Hebdo, Usure Chaîne Vélo, Paires Carbone) basés sur des tracés SVG natifs et légers.
|
||||||
|
- **Gouvernance & Linters :** Déploiement des fichiers de configuration modernes `eslint.config.js` (Format Flat Config de niveau industriel) et `.stylelintrc.json` à la racine du projet.
|
||||||
|
|
||||||
|
### Modifié
|
||||||
|
- **Design System Responsif :** Optimisation de la structure Bootstrap 5 pour forcer l'alignement des "spinners" sur une seule ligne (`row`) sur écran desktop et une répartition fluide sur mobile.
|
||||||
|
- **Pont de Développement :** Restructuration du conteneur de validation openSUSE Leap 15.6 via un montage de volume Podman sécurisé (`:Z`) permettant l'audit du code en temps réel depuis l'hôte Fedora.
|
||||||
|
|
||||||
## [3.0.0] - 2026-05-23
|
## [3.0.0] - 2026-05-23
|
||||||
|
|
||||||
### Ajouté
|
### Ajouté
|
||||||
|
|
|
||||||
88
app.js
88
app.js
|
|
@ -1,44 +1,60 @@
|
||||||
/**
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
* RYM Horizon - Core Navigation Script
|
// --- GESTION DES ONGLETS EXISTANTE ---
|
||||||
* @description Gestionnaire d'onglets découplé conforme aux standards W3C
|
const navLinks = document.querySelectorAll(".nav-link");
|
||||||
*/
|
const tabContents = document.querySelectorAll(".tab-content");
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
|
|
||||||
// Sélecteurs d'éléments de navigation
|
|
||||||
const navLinks = document.querySelectorAll('.sidebar .nav-link');
|
|
||||||
const tabContents = document.querySelectorAll('.tab-content');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bascule l'affichage vers l'onglet ciblé
|
|
||||||
* @param {string} targetTabId - L'identifiant de la section à afficher
|
|
||||||
*/
|
|
||||||
const switchTab = (targetTabId) => {
|
|
||||||
// 1. Masquer tous les contenus actifs
|
|
||||||
tabContents.forEach(content => content.classList.remove('active'));
|
|
||||||
|
|
||||||
// 2. Afficher le contenu demandé
|
|
||||||
const activeTarget = document.getElementById(targetTabId);
|
|
||||||
if (activeTarget) {
|
|
||||||
activeTarget.classList.add('active');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attribution dynamique des écouteurs d'événements
|
|
||||||
navLinks.forEach(link => {
|
navLinks.forEach(link => {
|
||||||
link.addEventListener('click', (event) => {
|
link.addEventListener("click", (e) => {
|
||||||
event.preventDefault();
|
e.preventDefault();
|
||||||
|
navLinks.forEach(l => l.classList.remove("active"));
|
||||||
|
tabContents.forEach(tc => tc.classList.remove("active"));
|
||||||
|
|
||||||
// Récupération de l'ID cible via le dataset
|
link.classList.add("active");
|
||||||
const targetTab = link.getAttribute('data-tab');
|
const tabId = link.getAttribute("data-tab");
|
||||||
|
document.getElementById(tabId).classList.add("active");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (targetTab) {
|
// --- OPTION 1 : DRAG & DROP ENGINE (AAA SOUVERAIN) ---
|
||||||
// Gestion de l'état graphique de la navigation
|
const draggables = document.querySelectorAll(".rym-draggable-workout");
|
||||||
navLinks.forEach(item => item.classList.remove('active'));
|
const dropZones = document.querySelectorAll(".drop-zone");
|
||||||
link.classList.add('active');
|
|
||||||
|
|
||||||
// Exécution de la bascule logique
|
draggables.forEach(draggable => {
|
||||||
switchTab(targetTab);
|
draggable.addEventListener("dragstart", (e) => {
|
||||||
|
e.dataTransfer.setData("text/plain", draggable.id);
|
||||||
|
// Petit délai pour l'effet visuel de déplacement
|
||||||
|
setTimeout(() => {
|
||||||
|
draggable.style.display = "none";
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
draggable.addEventListener("dragend", () => {
|
||||||
|
draggable.style.display = "block";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZones.forEach(zone => {
|
||||||
|
zone.addEventListener("dragover", (e) => {
|
||||||
|
e.preventDefault(); // Indispensable pour autoriser le drop
|
||||||
|
zone.classList.add("drag-over");
|
||||||
|
});
|
||||||
|
|
||||||
|
zone.addEventListener("dragleave", () => {
|
||||||
|
zone.classList.remove("drag-over");
|
||||||
|
});
|
||||||
|
|
||||||
|
zone.addEventListener("drop", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.remove("drag-over");
|
||||||
|
|
||||||
|
const id = e.dataTransfer.getData("text/plain");
|
||||||
|
const draggableElement = document.getElementById(id);
|
||||||
|
|
||||||
|
if (draggableElement) {
|
||||||
|
zone.appendChild(draggableElement);
|
||||||
|
// Ici se branchera l'appel API asynchrone vers ton Gitea/Serveur
|
||||||
|
const targetDay = zone.getAttribute("data-day");
|
||||||
|
console.log(`Séance déplacée avec succès sur le jour : ${targetDay}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
window: "readonly",
|
||||||
|
document: "readonly",
|
||||||
|
console: "readonly",
|
||||||
|
setTimeout: "readonly"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-undef": "error",
|
||||||
|
"no-console": "off",
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"curly": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
138
index.html
138
index.html
|
|
@ -69,36 +69,124 @@
|
||||||
<span class="badge bg-primary">Saison : Triathlon</span>
|
<span class="badge bg-primary">Saison : Triathlon</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-rym p-4 shadow-sm mb-4">
|
||||||
|
<h5 class="mb-3 text-primary"><i class="bi bi-activity me-2"></i>Indicateurs de Performance & Souveraineté</h5>
|
||||||
|
<div class="row row-cols-2 row-cols-sm-2 row-cols-md-4 g-3 text-center justify-content-center">
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="rym-spinner-wrapper">
|
||||||
|
<svg class="rym-spinner-svg" viewBox="0 0 36 36">
|
||||||
|
<path class="circle-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
<path class="circle-stroke text-success" stroke-dasharray="82, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
</svg>
|
||||||
|
<div class="rym-spinner-text">
|
||||||
|
<span class="fw-bold fs-5">82</span><small>%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small fw-semibold mt-2">Fraîcheur VRC</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="rym-spinner-wrapper">
|
||||||
|
<svg class="rym-spinner-svg" viewBox="0 0 36 36">
|
||||||
|
<path class="circle-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
<path class="circle-stroke text-warning" stroke-dasharray="65, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
</svg>
|
||||||
|
<div class="rym-spinner-text">
|
||||||
|
<span class="fw-bold fs-5">65</span><small>%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small fw-semibold mt-2">Charge Hebdo</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="rym-spinner-wrapper">
|
||||||
|
<svg class="rym-spinner-svg" viewBox="0 0 36 36">
|
||||||
|
<path class="circle-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
<path class="circle-stroke text-info" stroke-dasharray="45, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
</svg>
|
||||||
|
<div class="rym-spinner-text">
|
||||||
|
<span class="fw-bold fs-5">45</span><small>%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small fw-semibold mt-2">Chaîne Vélo</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="rym-spinner-wrapper">
|
||||||
|
<svg class="rym-spinner-svg" viewBox="0 0 36 36">
|
||||||
|
<path class="circle-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
<path class="circle-stroke text-danger" stroke-dasharray="90, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||||
|
</svg>
|
||||||
|
<div class="rym-spinner-text">
|
||||||
|
<span class="fw-bold fs-5">90</span><small>%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small fw-semibold mt-2">Paires Carbone</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card card-rym p-4 shadow-sm mb-5">
|
<div class="card card-rym p-4 shadow-sm mb-5">
|
||||||
<h5 class="mb-3 text-primary"><i class="bi bi-calendar-week me-2"></i>Vue Hebdomadaire (Standard)</h5>
|
<h5 class="mb-3 text-primary"><i class="bi bi-calendar-week me-2"></i>Vue Hebdomadaire Dynamique</h5>
|
||||||
<div class="table-responsive">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 row-cols-lg-7 g-2" id="planning-kanban">
|
||||||
<table class="table table-bordered text-center align-middle">
|
|
||||||
<thead class="bg-dark text-white">
|
<div class="col">
|
||||||
<tr><th>Moment</th><th>Lun</th><th>Mar</th><th>Mer</th><th>Jeu</th><th>Ven</th><th>Sam</th><th>Dim</th></tr>
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Lun">
|
||||||
</thead>
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Lundi</div>
|
||||||
<tbody>
|
<div class="rym-draggable-workout bg-primary text-white p-2 rounded mb-2 shadow-sm small" draggable="true" id="w1">
|
||||||
<tr><td class="bg-light fw-bold">Matin</td><td><i class="bi bi-water"></i></td><td>-</td><td><i class="bi bi-bicycle"></i></td><td><i class="bi bi-water"></i></td><td>-</td><td class="bg-info-subtle">Loisir</td><td class="bg-danger-subtle">Repos</td></tr>
|
<i class="bi bi-water me-1"></i> Natation : 2500m
|
||||||
<tr><td class="bg-light fw-bold">A-M</td><td>-</td><td><i class="bi bi-lightning"></i></td><td>-</td><td>-</td><td><i class="bi bi-lightning"></i></td><td><i class="bi bi-bicycle"></i></td><td>-</td></tr>
|
</div>
|
||||||
<tr><td class="bg-light fw-bold">Soir</td><td>-</td><td>-</td><td><i class="bi bi-person-walking"></i></td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Mar">
|
||||||
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Mardi</div>
|
||||||
|
<div class="rym-draggable-workout bg-warning text-dark p-2 rounded mb-2 shadow-sm small" draggable="true" id="w2">
|
||||||
|
<i class="bi bi-lightning me-1"></i> VMA Balayage
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Mer">
|
||||||
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Mercredi</div>
|
||||||
|
<div class="rym-draggable-workout bg-info text-dark p-2 rounded mb-2 shadow-sm small" draggable="true" id="w3">
|
||||||
|
<i class="bi bi-bicycle me-1"></i> Vélo Tempête
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Jeu">
|
||||||
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Jeudi</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Ven">
|
||||||
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Vendredi</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Sam">
|
||||||
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Samedi</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Dim">
|
||||||
|
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Dimanche</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card card-rym p-4 premium-zone shadow-lg">
|
<div class="card card-rym p-4 premium-zone shadow-lg">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
||||||
<h5 class="fw-bold text-dark"><i class="bi bi-clock me-2"></i>Agenda Précision Premium (24h/24)</h5>
|
|
||||||
<button class="btn btn-warning btn-sm fw-bold">Passer au Premium</button>
|
|
||||||
</div>
|
|
||||||
<div class="bg-light rounded p-3 agenda-scroll">
|
|
||||||
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">06:00</div><div class="col-11 ps-3 text-muted">Sommeil / Réveil</div></div>
|
|
||||||
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">07:00</div><div class="col-11 ps-3 bg-primary-subtle text-primary">Natation : 2500m Seuil</div></div>
|
|
||||||
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">08:00</div><div class="col-11 ps-3">Petit-Déjeuner & Hydratation</div></div>
|
|
||||||
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2 text-primary">...</div><div class="col-11 ps-3">...</div></div>
|
|
||||||
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">23:00</div><div class="col-11 ps-3 text-muted">Repos / Nuit</div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="clients" class="tab-content">
|
<section id="clients" class="tab-content">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
|
|
||||||
57
style.css
57
style.css
|
|
@ -91,3 +91,60 @@ body {
|
||||||
from { opacity: 0; transform: translateY(4px); }
|
from { opacity: 0; transform: translateY(4px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- CONFIGURATION DES SPINNERS (OPTION 2) --- */
|
||||||
|
.rym-spinner-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rym-spinner-svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-bg {
|
||||||
|
fill: none;
|
||||||
|
stroke: #e6e6e6;
|
||||||
|
stroke-width: 2.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-stroke {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2.8;
|
||||||
|
stroke-linecap: round;
|
||||||
|
transition: stroke-dasharray 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rym-spinner-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- DRAG AND DROP KANBAN (OPTION 1) --- */
|
||||||
|
.drop-zone {
|
||||||
|
min-height: 140px;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.drag-over {
|
||||||
|
background-color: #e2e8f0 !important;
|
||||||
|
border: 2px dashed #0dcaf0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rym-draggable-workout {
|
||||||
|
cursor: grab;
|
||||||
|
transition: transform 0.1s ease, opacity 0.1s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rym-draggable-workout:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue