gestion_table_tournante_libre/src/interface/qualite/VuePerformance.svelte
Mathieu Benoit 01c9296c73 [ADD] quality page views: header, profiles, memberships, pairs, goals
The Qualité tab now draws its five views from the placement's detail.
A numeric header with each figure's percentage and worst case; the
profile as counts with the diversity bound dotted, and as percentages
on a second chart; membership collisions and a table grid behind a
three-position selector, seats by default; repeated pairs with matrix
and a card section; goals per round on servable couples and
facilitators over the evening and per round; the course of the search.
Each chart has its twin table and keyboard access.
Checked: node 5083, navigateur 1102, node-long 128; captures stable.

--- FR ---

[ADD] vues de la qualité : en-tête, profils, appartenances, paires

L'onglet Qualité dessine désormais ses cinq vues à partir du détail du
placement : un en-tête chiffré avec le pourcentage et le pire cas de
chaque chiffre ; le profil en comptes, borne de diversité en pointillé,
et en pourcentages sur un second graphique ; les collisions et la grille
des tables derrière un sélecteur à trois positions, les sièges par
défaut ; les paires répétées, leur matrice et la fiche ; les objectifs
par tour et les animateurs sur la soirée et par tour ; la course. Chaque
graphique a son tableau jumeau et se parcourt au clavier.
Vérifié : node 5083, navigateur 1102, longues 128 ; captures stables.

Assisted-by: Claude Opus 5.5
2026-10-12 11:26:02 -04:00

155 lines
5.8 KiB
Svelte

<!-- © 2026 TechnoLibre (http://www.technolibre.ca)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
<script>
// La vue « Performance du calcul » (§ 12.14, B23, D40, D49) de la page de
// qualité : relancer plus longtemps aurait-il donné mieux ? Elle rend ce
// que vuePerformance (performance.js) tire de la course du détail, sans
// rien calculer (contrainte 41) :
//
// - la phrase de la source, sous [data-phrase-qualite] : sur le retenu,
// la proposition qu'elle mesure ; sans course gardée, que sa course ne
// l'est pas ; d'un seul point de relevé, qu'il n'y a rien à relier ;
// - la ligne de tête [data-course], un morceau par élément de liste :
// mouvements évalués sur le compte, puis durée, cœurs, cadence et
// nombre de propositions de la génération, ou « durée inconnue » ;
// - les petits multiples : un Escalier en forme points par valeur, sur
// l'abscisse logarithmique des mouvements, graduée aux points de
// relevé ; ils partagent leurs marges — l'abscisse alignée, point
// contre point — et le point pointé, désigné dans tous à la fois ;
// chaque graphique a son seul axe vertical, de zéro à sa plus grande
// valeur, et son infobulle porte la ligne du jumeau ;
// - la phrase du dernier changement [data-dernier-changement] ;
// - le jumeau, une ligne par point de relevé, le chemin du clavier et du
// lecteur d'écran vers chaque valeur.
//
// Elle reçoit le contrat de propriétés des vues — detail, le détail du
// placement mesuré, null tant qu'il n'est pas calculé ; ligne, sa ligne
// d'évaluation ; table, la table d'évaluation de la charge — et ne lit que
// detail : le dernier point est le plan de la proposition, mesuré sous la
// même charge que sa ligne. Son <div> porte data-vue-qualite, que la carte
// ne transmet pas : il enveloppe la carte.
import { getContext } from 'svelte';
import Carte from '../primitives/Carte.svelte';
import Escalier from './graphiques/Escalier.svelte';
import Jumeau from './graphiques/Jumeau.svelte';
import { margesCommunes } from './graphiques/pointage.js';
import { vuePerformance } from './performance.js';
let { detail = null, ligne = null, table = null } = $props();
const t = getContext('t');
const format = getContext('format');
const vue = $derived(vuePerformance(detail, { t, format }));
const remplie = $derived(vue.phrases.length > 0 || vue.tete !== null);
// L'infobulle de chaque petit multiple : la ligne du jumeau du point
// pointé.
const infobulle = $derived(vue.jumeau === null ? null : { colonnes: vue.jumeau.colonnes, lignes: vue.jumeau.lignes });
// Les marges que chaque petit multiple demande pour son texte, par rang,
// absentes tant qu'il ne les a pas mesurées — la liaison lit alors null ;
// tous reçoivent les plus grandes, et leurs abscisses s'alignent.
let marges = $state([]);
const alignees = $derived(margesCommunes(marges));
// Le point de relevé pointé, commun aux petits multiples.
let pointe = $state(null);
let ouvert = $state(false);
</script>
<div data-vue-qualite="performance">
{#if remplie}
<Carte titre={t('qualite.vue.performance.titre')} sousTitre={t('qualite.vue.performance.question')} niveau={3}>
<div class="performance">
{#each vue.phrases as phrase (phrase.code)}
<p data-phrase-qualite={phrase.code}>{phrase.texte}</p>
{/each}
{#if vue.tete !== null}
<ul class="tete" data-course>
{#each vue.tete as morceau, rang (rang)}
<li>{morceau}</li>
{/each}
</ul>
{/if}
{#if vue.multiples.length > 0}
<p class="synthese">{vue.synthese}</p>
<div class="multiples">
{#each vue.multiples as multiple, rang (multiple.nom)}
<div class="multiple">
<Escalier
graphique={multiple.graphique}
nom={multiple.titre}
synthese={multiple.synthese}
series={[{ libelle: multiple.libelle, valeurs: multiple.valeurs }]}
forme="points"
abscisses={vue.abscisses}
hauteur={150}
titreX={t('qualite.performance.axe')}
{infobulle}
alignerSur={alignees}
bind:marges={() => marges[rang] ?? null, (requises) => (marges[rang] = requises)}
bind:pointe
/>
</div>
{/each}
</div>
{/if}
{#if vue.dernierChangement !== null}
<p data-dernier-changement>{vue.dernierChangement}</p>
{/if}
{#if vue.jumeau !== null}
<Jumeau tableaux={[vue.jumeau]} bind:ouvert />
{/if}
</div>
</Carte>
{:else}
<Carte titre={t('qualite.vue.performance.titre')} sousTitre={t('qualite.vue.performance.question')} niveau={3} />
{/if}
</div>
<style>
.performance {
display: flex;
flex-direction: column;
gap: var(--espace-3);
min-width: 0;
}
p {
margin: 0;
}
/* La ligne de tête : ses morceaux côte à côte, séparés par un filet,
repliés quand ils ne tiennent pas. */
.tete {
display: flex;
flex-wrap: wrap;
gap: var(--espace-1) var(--espace-3);
margin: 0;
padding: 0;
list-style: none;
font-weight: bold;
}
.tete li + li {
padding-left: var(--espace-3);
border-left: 1px solid var(--couleur-carte-filet);
}
.synthese {
color: var(--couleur-page-texte-secondaire);
}
/* Les petits multiples : deux par ligne quand la carte le permet, un
seul sinon. */
.multiples {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr));
gap: var(--espace-4);
min-width: 0;
}
.multiple {
min-width: 0;
}
</style>