feat: implement native drag & drop planning and responsive performance spinners v3.1.0
This commit is contained in:
parent
250a1f1617
commit
fefe280b9f
|
|
@ -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
|
||||
|
||||
### Ajouté
|
||||
|
|
|
|||
88
app.js
88
app.js
|
|
@ -1,44 +1,60 @@
|
|||
/**
|
||||
* RYM Horizon - Core Navigation Script
|
||||
* @description Gestionnaire d'onglets découplé conforme aux standards W3C
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// --- GESTION DES ONGLETS EXISTANTE ---
|
||||
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 => {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
link.addEventListener("click", (e) => {
|
||||
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
|
||||
const targetTab = link.getAttribute('data-tab');
|
||||
link.classList.add("active");
|
||||
const tabId = link.getAttribute("data-tab");
|
||||
document.getElementById(tabId).classList.add("active");
|
||||
});
|
||||
});
|
||||
|
||||
if (targetTab) {
|
||||
// Gestion de l'état graphique de la navigation
|
||||
navLinks.forEach(item => item.classList.remove('active'));
|
||||
link.classList.add('active');
|
||||
// --- OPTION 1 : DRAG & DROP ENGINE (AAA SOUVERAIN) ---
|
||||
const draggables = document.querySelectorAll(".rym-draggable-workout");
|
||||
const dropZones = document.querySelectorAll(".drop-zone");
|
||||
|
||||
// Exécution de la bascule logique
|
||||
switchTab(targetTab);
|
||||
draggables.forEach(draggable => {
|
||||
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>
|
||||
</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">
|
||||
<h5 class="mb-3 text-primary"><i class="bi bi-calendar-week me-2"></i>Vue Hebdomadaire (Standard)</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered text-center align-middle">
|
||||
<thead class="bg-dark text-white">
|
||||
<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>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5 class="mb-3 text-primary"><i class="bi bi-calendar-week me-2"></i>Vue Hebdomadaire Dynamique</h5>
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 row-cols-lg-7 g-2" id="planning-kanban">
|
||||
|
||||
<div class="col">
|
||||
<div class="bg-light p-2 rounded border h-100 drop-zone" data-day="Lun">
|
||||
<div class="fw-bold text-center border-bottom pb-1 mb-2 bg-dark text-white rounded-top small">Lundi</div>
|
||||
<div class="rym-draggable-workout bg-primary text-white p-2 rounded mb-2 shadow-sm small" draggable="true" id="w1">
|
||||
<i class="bi bi-water me-1"></i> Natation : 2500m
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 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">
|
||||
<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); }
|
||||
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