This commit is contained in:
2026-04-02 14:15:26 +02:00
parent 0cc8ab8540
commit af75a09c18
11 changed files with 734 additions and 472 deletions
+118 -20
View File
@@ -1,4 +1,25 @@
<detail-view>
<!--
==========================================================================
COMPOSANT <detail-view>
RÔLE : fiche détaillée d'une formation avec :
- Informations générales (établissement, ville, capacité...)
- Tableau de la phase principale d'admission (par type de bac)
- Tableau de la phase complémentaire
- Timeline de vitesse de remplissage (30 mai / 16 juin / 11 juillet)
- Graphiques Charts.css : répartition bac, mentions, profil sociologique
- Historique 20202025 (appel à chargerHistoriqueFormation via l'API)
PROPS reçues depuis <app> :
- formation : objet Formation normalisé (via creerFormation dans formation.js)
- onback : callback() → retour à la liste de résultats
BIBLIOTHÈQUES UTILISÉES :
- Charts.css : graphiques déclaratifs via HTML/CSS uniquement (pas de JS)
Les graphiques sont construits dynamiquement via innerHTML
==========================================================================
-->
<div if={ props.formation } class="detail-page">
<h2>Formation</h2>
<h1 class="formation-title">{ props.formation.etablissement } - { props.formation.nom }</h1>
@@ -197,57 +218,87 @@
<script>
export default {
// ========================================================================
// ÉTAT LOCAL du composant
// historique : tableau des données historiques 2020-2025
// chargementHistorique : true pendant la requête API historique
// ========================================================================
state: {
historique: [],
historique: [],
chargementHistorique: false
},
// Cycle de vie Riot : appelé une fois après l'insertion dans le DOM
// On lance les graphiques ET le chargement de l'historique en parallèle
onMounted() {
this.afficherGraphiques();
this.chargerHistorique();
this.chargerHistorique(); // appel API asynchrone (ne bloque pas le rendu)
},
// Cycle de vie Riot : appelé après chaque mise à jour des props ou de l'état
// Nécessaire car Charts.css construit le HTML des graphiques via innerHTML
onUpdated() {
this.afficherGraphiques();
// On attend d'avoir les données historiques avant de construire ces graphiques
if (this.state.historique.length > 0) {
this.afficherGraphiquesHistoriques();
}
},
// Retour à la liste des résultats
// Retour à la liste des résultats (délègue au parent via props.onback)
retourListe() {
this.props.onback();
},
// Limiter une valeur entre 0 et 1 pour Charts.css
// ========================================================================
// limiterValeur(val)
// RÔLE : convertir un pourcentage (0100) en proportion (01) pour Charts.css.
//
// Charts.css utilise la propriété CSS custom --size (entre 0 et 1)
// pour déterminer la hauteur/largeur d'une barre.
// Ex: 75% d'accès → --size: 0.75 → barre à 75% de hauteur
//
// On clamp la valeur entre 0 et 1 pour éviter les débordements.
// ========================================================================
limiterValeur(val) {
if (val === null || val === undefined || isNaN(val)) {
return 0;
return 0; // valeur manquante → barre à 0
}
var v = val / 100;
var v = val / 100; // conversion % → proportion
if (v > 1) {
return 1;
}
if (v < 0) {
return 0;
}
if (v > 1) { return 1; } // max : barre pleine
if (v < 0) { return 0; } // min : barre vide
return Math.round(v * 100) / 100;
return Math.round(v * 100) / 100; // arrondi à 2 décimales
},
// ========================================================================
// afficherGraphiques()
// RÔLE : construire et injecter les 3 graphiques Charts.css de la formation.
//
// Pourquoi innerHTML ? Charts.css nécessite une structure HTML <table> précise
// avec des CSS custom properties (--size, --color) sur chaque <td>.
// Ces tables sont générées dynamiquement et injectées dans les divs ref=...
//
// Les 3 graphiques :
// - graphBac : colonnes → répartition par type de bac des admis
// - graphMentions : colonnes → répartition par mention au bac des admis
// - graphProfil : barres horizontales → % femmes, boursiers, néo-bacs
// ========================================================================
afficherGraphiques() {
var f = this.props.formation;
if (!f) {
return;
return; // pas de formation chargée → rien à afficher
}
// this.$() est la méthode Riot pour sélectionner un élément dans le DOM du composant
var graphBac = this.$('[ref="graphBac"]');
var graphMentions = this.$('[ref="graphMentions"]');
var graphProfil = this.$('[ref="graphProfil"]');
// Graphique 1 : répartition par type de bac (colonnes verticales)
if (graphBac) {
graphBac.innerHTML = this.construireGraphiqueColonnes([
{ label: 'Général', valeur: f.pctGeneral, couleur: '#3d7fff' },
@@ -256,6 +307,7 @@
]);
}
// Graphique 2 : répartition par mention au bac (colonnes verticales)
if (graphMentions) {
graphMentions.innerHTML = this.construireGraphiqueColonnes([
{ label: 'Sans', valeur: f.pctSansMention, couleur: '#94a3b8' },
@@ -266,6 +318,7 @@
]);
}
// Graphique 3 : profil sociologique (barres horizontales)
if (graphProfil) {
graphProfil.innerHTML = this.construireGraphiqueBarres([
{ label: 'Femmes', valeur: f.pctFemmes, couleur: '#a78bfa' },
@@ -275,23 +328,34 @@
}
},
// ========================================================================
// chargerHistorique()
// RÔLE : charger les données 20202025 depuis api.js pour les graphiques
// d'évolution historique.
//
// Le code UAI est extrait de l'ID de la formation (format "CODUAI-NomFormation")
// On le passe à chargerHistoriqueFormation() qui fait 6 appels API en série.
// ========================================================================
async chargerHistorique() {
var f = this.props.formation;
if (!f) {
return;
}
// L'ID de la formation est construit comme "COD_UAI-NomFormation"
// On extrait la partie avant le premier tiret pour avoir le code UAI
var codUai = f.id.split('-')[0];
if (!codUai || !window.chargerHistoriqueFormation) {
return;
return; // pas de code UAI ou fonction non disponible
}
this.update({ chargementHistorique: true });
this.update({ chargementHistorique: true }); // affiche le spinner de chargement
try {
var historique = await window.chargerHistoriqueFormation(codUai, f.nom);
this.update({ historique: historique, chargementHistorique: false });
// onUpdated() est automatiquement appelé après update() → affichera les graphiques
} catch (e) {
console.error('Erreur chargement historique :', e);
this.update({ historique: [], chargementHistorique: false });
@@ -382,32 +446,65 @@
}
},
// ========================================================================
// construireGraphiqueColonnes(elements)
// RÔLE : générer le HTML d'un graphique en colonnes verticales (Charts.css).
//
// PARAMÈTRE : tableau d'objets { label, valeur, couleur }
// - label : étiquette affichée sous la colonne
// - valeur : pourcentage (0100) → converti en proportion par limiterValeur()
// - couleur : couleur CSS de la colonne (hex ou nom)
//
// FORMAT Charts.css :
// <table class="charts-css column ...">
// <tbody>
// <tr>
// <th scope="row">Général</th>
// <td style="--size: 0.75; --color: #3d7fff;"><span class="data">75%</span></td>
// </tr>
// </tbody>
// </table>
// ========================================================================
construireGraphiqueColonnes(elements) {
var lignes = '';
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
var taille = this.limiterValeur(el.valeur);
var affiche = el.valeur || 0;
var taille = this.limiterValeur(el.valeur); // proportion 01 pour --size
var affiche = el.valeur || 0; // valeur brute à afficher dans le tooltip
lignes += '<tr>';
lignes += '<th scope="row">' + el.label + '</th>';
lignes += '<td style="--size: ' + taille + '; --color: ' + el.couleur + ';">';
lignes += '<span class="data">' + affiche + '%</span>';
lignes += '<span class="data">' + affiche + '%</span>'; // affiché au survol
lignes += '</td></tr>';
}
// Classes Charts.css expliquées :
// column → graphique en colonnes verticales
// show-labels → affiche les étiquettes (th)
// show-primary-axis → affiche l'axe horizontal du bas
// show-4-secondary-axes → affiche 4 lignes de grille horizontales
// data-spacing-10 → espace de 10px entre les colonnes
return '<table class="charts-css column show-labels show-primary-axis show-4-secondary-axes data-spacing-10">'
+ '<thead><tr><th scope="col">Type</th><th scope="col">%</th></tr></thead>'
+ '<tbody>' + lignes + '</tbody></table>';
},
// ========================================================================
// construireGraphiqueBarres(elements)
// RÔLE : générer le HTML d'un graphique en barres horizontales (Charts.css).
//
// Identique à construireGraphiqueColonnes() mais avec class="charts-css bar"
// → les barres s'étendent horizontalement au lieu de verticalement.
// Utilisé pour le profil sociologique (femmes, boursiers, néo-bacs).
// ========================================================================
construireGraphiqueBarres(elements) {
var lignes = '';
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
var taille = this.limiterValeur(el.valeur);
var taille = this.limiterValeur(el.valeur); // proportion 01
var affiche = el.valeur || 0;
lignes += '<tr>';
@@ -417,6 +514,7 @@
lignes += '</td></tr>';
}
// "bar" à la place de "column" → barres horizontales
return '<table class="charts-css bar show-labels show-primary-axis show-4-secondary-axes data-spacing-14">'
+ '<thead><tr><th scope="col">Catégorie</th><th scope="col">%</th></tr></thead>'
+ '<tbody>' + lignes + '</tbody></table>';