30 KiB
Bilan Redesign — Plan d'implémentation
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Remplacer la mise en page plate et non hiérarchisée par une structure header-sticky + panneaux qui guide clairement le flux formateur (config → évaluation → synthèse/export).
Architecture: Deux fichiers modifiés uniquement — Bilan.jsx (restructuration JSX + état toast) et Bilan.css (nouvelles classes de layout, suppression des anciennes). La logique métier (niveaux, drag & drop, PDF, OpenAI) reste intacte. Le test à chaque tâche est visuel : lancer le dev server et vérifier le rendu.
Tech Stack: React 18, Vite 6, CSS pur (pas de framework CSS), Plus Jakarta Sans (Google Fonts), pdf-lib
Global Constraints
- Ne jamais toucher à la logique métier (handlers, état des items, appels OpenAI, génération PDF)
- Conserver toutes les variables CSS existantes dans
:root - Conserver le responsive mobile (media query
max-width: 768px) - Conserver le drag & drop et les level pills existants
- Dev server :
npm run dev(port 5173 par défaut)
Fichiers touchés
| Fichier | Action | Responsabilité |
|---|---|---|
src/components/Bilan.jsx |
Modifier | Restructuration JSX + remplacement état status par toasts[] |
src/components/Bilan.css |
Modifier | Nouvelles classes layout, suppression .bilan-wrapper/.bilan-container/.controls/.header/.status |
Task 1 : Système de toasts flottants
Remplace l'état status / setStatus par un tableau toasts[] avec auto-dismiss. Les notifications apparaissent en bas à droite, hors du flux de contenu.
Files:
- Modify:
src/components/Bilan.jsx(état, showStatus, JSX toasts, finally blocks) - Modify:
src/components/Bilan.css(nouvelles classes.toast-container,.toast,.toast-info/success/error)
Interfaces:
- Consumes: —
- Produces:
toastsstate,showStatus(message, type, withProgress)(même signature qu'avant),dismissProgressToasts()(appelé dans les blocs finally)
- Étape 1 : Remplacer l'état
statuspartoastsdans Bilan.jsx
Localiser ligne ~324 dans src/components/Bilan.jsx. Remplacer :
const [status, setStatus] = useState(null);
par :
const [toasts, setToasts] = useState([]);
- Étape 2 : Supprimer le useEffect d'auto-dismiss (lignes ~355-362)
Supprimer entièrement ce bloc :
useEffect(() => {
if (!status || status.withProgress) return;
const timeout = setTimeout(
() => setStatus(null),
status.type === "info" ? 3000 : 5000
);
return () => clearTimeout(timeout);
}, [status]);
- Étape 3 : Remplacer
showStatuspar la version toast
Localiser et remplacer le callback showStatus (ligne ~346) :
const showStatus = useCallback((message, type = "info", withProgress = false) => {
const id = Date.now();
setToasts((prev) => [...prev, { id, message, type, withProgress }]);
if (!withProgress) {
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, type === "info" ? 3000 : 5000);
}
}, []);
- Étape 4 : Ajouter
dismissProgressToastsaprèsshowStatus
const dismissProgressToasts = useCallback(() => {
setToasts((prev) => prev.filter((t) => !t.withProgress));
}, []);
- Étape 5 : Mettre à jour les blocs
finallydansgenerateAISummaryOpenAI
Localiser le bloc finally de generateAISummaryOpenAI (ligne ~686). Remplacer :
} finally {
setStatus((prev) => (prev ? { ...prev, withProgress: false } : prev));
setIsAiLoading(false);
}
par :
} finally {
dismissProgressToasts();
setIsAiLoading(false);
}
- Étape 6 : Mettre à jour le bloc
finallydansdownloadPDF
Localiser le bloc finally de downloadPDF (ligne ~912). Remplacer :
} finally {
setStatus((prev) => (prev ? { ...prev, withProgress: false } : prev));
}
par :
} finally {
dismissProgressToasts();
}
- Étape 7 : Ajouter le JSX des toasts dans le return
Dans le return de Bilan, ajouter juste avant la fermeture de la div racine (peu importe laquelle pour l'instant) :
<div className="toast-container">
{toasts.map((toast) => (
<div key={toast.id} className={`toast toast-${toast.type}`}>
{toast.withProgress ? (
<>
<span>{toast.message}</span>
<div className="toast-progress">
<div className="toast-progress-bar" />
</div>
</>
) : (
<span>{toast.message}</span>
)}
</div>
))}
</div>
- Étape 8 : Supprimer le JSX du statut inline existant
Dans le JSX du return, supprimer le bloc conditionnel {status && (...)} qui rend le statut à l'intérieur de .evaluation-summary-section. Localiser :
{status && (
<div className={`status ${status.type}`}>
...
</div>
)}
et le supprimer entièrement.
- Étape 9 : Ajouter le CSS des toasts dans Bilan.css
Ajouter à la fin du fichier, avant les media queries :
.toast-container {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 8px;
max-width: 360px;
pointer-events: none;
}
.toast {
padding: 12px 16px;
border-radius: var(--radius-md);
font-size: 13.5px;
font-weight: 700;
font-family: var(--font-sans);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.06);
display: flex;
flex-direction: column;
gap: 8px;
animation: toast-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.toast-info {
background: var(--color-indigo-50);
border-left: 3px solid var(--color-indigo-500);
color: var(--color-indigo-700);
}
.toast-success {
background: #dcfce7;
border-left: 3px solid #10b981;
color: #15803d;
}
.toast-error {
background: #fee2e2;
border-left: 3px solid #ef4444;
color: #b91c1c;
}
.toast-progress {
width: 100%;
height: 3px;
background: rgba(0, 0, 0, 0.08);
border-radius: 999px;
overflow: hidden;
}
.toast-progress-bar {
width: 100%;
height: 100%;
background: currentColor;
opacity: 0.6;
animation: progress-stripes 1.5s linear infinite;
}
@keyframes toast-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
- Étape 10 : Vérifier visuellement
npm run dev
Déclencher une action (ex : cliquer "Générer le Bilan"). Un toast doit apparaître en bas à droite et disparaître automatiquement. Aucun message de statut ne doit apparaître dans le corps de la page.
- Étape 11 : Commit
git add src/components/Bilan.jsx src/components/Bilan.css
git commit -m "feat: replace inline status with floating toast notifications"
Task 2 : App shell + Topbar sticky
Restructure le wrapper racine en .app-shell + .app-topbar (header sticky 64px) contenant logo, CP selector pill, titre, inputs identité et boutons d'action.
Files:
- Modify:
src/components/Bilan.jsx(nouvelle structure return, déplacement CP selector + identité + boutons dans topbar) - Modify:
src/components/Bilan.css(nouvelles classes.app-shell,.app-topbar,.topbar-*,.cp-select,.topbar-input,.btn-topbar-*)
Interfaces:
- Consumes:
dismissProgressToasts(Task 1),toastsstate (Task 1) - Produces: structure
.app-shell > .app-topbar + .app-mainutilisée par les tâches 3, 4, 5
- Étape 1 : Ajouter le CSS du app-shell et du topbar dans Bilan.css
Remplacer les règles .bilan-wrapper et .bilan-container existantes par :
.app-shell {
min-height: 100vh;
background-color: #f8fafc;
background-image:
radial-gradient(circle at 10% 20%, rgba(98, 95, 255, 0.06) 0%, transparent 40%),
radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.04) 0%, transparent 45%);
font-family: var(--font-sans);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
}
.app-topbar {
position: sticky;
top: 0;
z-index: 100;
height: 64px;
background: rgba(255, 255, 255, 0.97);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-slate-200);
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.04);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
gap: 16px;
}
.topbar-left {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.topbar-logo {
height: 28px;
width: auto;
display: block;
}
.topbar-divider {
width: 1px;
height: 24px;
background: var(--border-slate-200);
}
.cp-select {
appearance: none;
-webkit-appearance: none;
background: var(--color-indigo-50);
color: var(--color-indigo-600);
border: 1px solid rgba(98, 95, 255, 0.2);
border-radius: 999px;
padding: 6px 28px 6px 12px;
font-size: 13px;
font-weight: 700;
font-family: var(--font-sans);
cursor: pointer;
outline: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7L10 12L15 7' stroke='%234f46e5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 14px;
transition: var(--transition);
}
.cp-select:hover {
background-color: var(--color-indigo-100);
border-color: var(--color-indigo-500);
}
.topbar-center {
display: flex;
flex-direction: column;
align-items: center;
gap: 1px;
flex: 1;
min-width: 0;
}
.topbar-title {
font-size: 15px;
font-weight: 800;
color: var(--text-primary);
letter-spacing: -0.02em;
white-space: nowrap;
}
.topbar-subtitle {
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.topbar-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.topbar-input {
width: 110px;
height: 36px;
padding: 0 12px;
border-radius: var(--radius-md);
border: 1px solid var(--border-slate-200);
background: #f8fafc;
color: var(--text-primary);
font-size: 13px;
font-weight: 500;
font-family: var(--font-sans);
outline: none;
transition: var(--transition);
}
.topbar-input::placeholder {
color: var(--text-muted);
font-weight: 400;
}
.topbar-input:focus {
border-color: var(--color-indigo-500);
background: #ffffff;
box-shadow: 0 0 0 3px rgba(98, 95, 255, 0.1);
}
.topbar-actions {
display: flex;
align-items: center;
gap: 8px;
padding-left: 12px;
border-left: 1px solid var(--border-slate-200);
margin-left: 4px;
}
.btn-topbar-secondary {
height: 36px;
padding: 0 14px;
border-radius: var(--radius-md);
border: 1px solid var(--border-slate-300);
background: #ffffff;
color: var(--text-secondary);
font-size: 13px;
font-weight: 700;
font-family: var(--font-sans);
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
box-shadow: var(--shadow-flat);
}
.btn-topbar-secondary:hover {
border-color: var(--color-indigo-500);
color: var(--color-indigo-600);
box-shadow: 0 2px 8px rgba(98, 95, 255, 0.1);
}
.btn-topbar-primary {
height: 36px;
padding: 0 14px;
border-radius: var(--radius-md);
border: none;
background: linear-gradient(135deg, #6d28d9 0%, #a855f7 50%, #db2777 100%);
color: #ffffff;
font-size: 13px;
font-weight: 800;
font-family: var(--font-sans);
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
box-shadow: 0 4px 14px rgba(168, 85, 247, 0.3);
}
.btn-topbar-primary:hover:not(:disabled) {
background: linear-gradient(135deg, #7c3aed 0%, #c084fc 50%, #ec4899 100%);
box-shadow: 0 6px 20px rgba(168, 85, 247, 0.45);
transform: translateY(-1px);
}
.btn-topbar-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.btn-topbar-success {
height: 36px;
padding: 0 14px;
border-radius: var(--radius-md);
border: none;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: #ffffff;
font-size: 13px;
font-weight: 700;
font-family: var(--font-sans);
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
box-shadow: 0 4px 14px rgba(16, 185, 129, 0.2);
}
.btn-topbar-success:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
box-shadow: 0 6px 18px rgba(16, 185, 129, 0.35);
transform: translateY(-1px);
}
.app-main {
max-width: 1240px;
margin: 0 auto;
padding: 32px 24px 64px;
}
- Étape 2 : Restructurer le JSX du return dans Bilan.jsx
Remplacer la div racine <div className="bilan-wrapper"> et son contenu jusqu'à la fermeture de .bilan-container (hors grille et section synthèse) par la nouvelle structure. Le return complet doit ressembler à :
return (
<div className="app-shell">
<header className="app-topbar">
<div className="topbar-left">
<img src={LOGO_URL} alt="Arinfo" className="topbar-logo" />
<div className="topbar-divider" />
<select
id="cpSelect"
className="cp-select"
value={currentCp}
onChange={(event) => setCurrentCp(event.target.value)}
>
{Object.keys(allDefaults).map((cp) => (
<option key={cp} value={cp}>{cp}</option>
))}
</select>
</div>
<div className="topbar-center">
<span className="topbar-title">{currentTitle.h1}</span>
<span className="topbar-subtitle">{currentTitle.h2}</span>
</div>
<div className="topbar-right">
<input
className="topbar-input"
placeholder="Prénom"
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
/>
<input
className="topbar-input"
placeholder="Nom"
value={lastName}
onChange={(event) => setLastName(event.target.value)}
/>
<div className="topbar-actions">
<button
type="button"
className="btn-topbar-secondary"
onClick={handleGenerateObservation}
>
📝 Générer
</button>
<button
type="button"
className="btn-topbar-primary"
onClick={generateAISummaryOpenAI}
disabled={isAiLoading}
>
✨ Résumer avec l'IA
</button>
{anySelected && (
<button
type="button"
className="btn-topbar-success"
onClick={downloadPDF}
>
📄 PDF
</button>
)}
</div>
</div>
</header>
<main className="app-main">
{/* La toolbar critères (Task 3), la grille et la section synthèse viennent ici */}
{/* Conserver la grille .competences-grid et .evaluation-summary-section existantes pour l'instant */}
</main>
<div className="toast-container">
{toasts.map((toast) => (
<div key={toast.id} className={`toast toast-${toast.type}`}>
{toast.withProgress ? (
<>
<span>{toast.message}</span>
<div className="toast-progress">
<div className="toast-progress-bar" />
</div>
</>
) : (
<span>{toast.message}</span>
)}
</div>
))}
</div>
</div>
);
Important : déplacer les éléments JSX de la grille .competences-grid et de .evaluation-summary-section à l'intérieur de <main className="app-main">. Supprimer <img src={LOGO_URL} alt="Logo watermark" className="logo-bg" /> (watermark supprimé). Supprimer les anciens blocs .controls.identity, .controls.actions et .header du JSX (ils sont maintenant dans le topbar).
- Étape 3 : Vérifier visuellement
npm run dev
Résultats attendus :
-
Header blanc sticky visible en haut (64px)
-
Logo Arinfo à gauche, CP selector pill indigo, titre du CP au centre
-
Inputs Prénom/Nom compacts à droite + boutons d'action visibles
-
En scrollant, le header reste fixé en haut
-
La grille de critères s'affiche dans la zone principale (même si encore sans toolbar)
-
Étape 4 : Commit
git add src/components/Bilan.jsx src/components/Bilan.css
git commit -m "feat: add sticky app topbar with cp selector, identity inputs and action buttons"
Task 3 : Toolbar critères
Ajoute une barre fine (44px) sous le topbar contenant les actions de sélection groupée et l'ajout de critère. Supprime les anciens boutons/inputs équivalents du corps de page.
Files:
- Modify:
src/components/Bilan.jsx(ajout.criteria-toolbardans<main>, suppression des anciens.controls.actions) - Modify:
src/components/Bilan.css(nouvelles classes.criteria-toolbar,.btn-text,.toolbar-add-input,.btn-toolbar-add)
Interfaces:
- Consumes: structure
.app-shell > .app-topbar + .app-main(Task 2) - Produces: toolbar rendu dans
<main>avant la grille
- Étape 1 : Ajouter le CSS de la toolbar dans Bilan.css
Ajouter après les classes .app-main :
.criteria-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 4px;
margin-bottom: 20px;
gap: 16px;
}
.criteria-toolbar-left {
display: flex;
align-items: center;
gap: 6px;
}
.btn-text {
background: none;
border: none;
padding: 4px 8px;
font-size: 12.5px;
font-weight: 600;
color: var(--text-muted);
cursor: pointer;
font-family: var(--font-sans);
transition: var(--transition);
border-radius: var(--radius-sm);
box-shadow: none;
}
.btn-text:hover {
color: var(--color-indigo-600);
background: var(--color-indigo-50);
transform: none;
box-shadow: none;
}
.toolbar-sep {
color: var(--border-slate-300);
font-size: 14px;
user-select: none;
}
.criteria-toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-add-input {
height: 34px;
width: 240px;
padding: 0 12px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-slate-200);
background: #ffffff;
color: var(--text-primary);
font-size: 13px;
font-family: var(--font-sans);
outline: none;
transition: var(--transition);
box-shadow: var(--shadow-flat);
}
.toolbar-add-input::placeholder {
color: var(--text-muted);
}
.toolbar-add-input:focus {
border-color: var(--color-indigo-500);
box-shadow: 0 0 0 3px rgba(98, 95, 255, 0.1);
}
.btn-toolbar-add {
height: 34px;
width: 34px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-slate-300);
background: #ffffff;
color: var(--text-secondary);
font-size: 20px;
font-weight: 300;
font-family: var(--font-sans);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
box-shadow: var(--shadow-flat);
line-height: 1;
padding: 0;
}
.btn-toolbar-add:hover {
background: var(--color-indigo-50);
border-color: var(--color-indigo-500);
color: var(--color-indigo-600);
transform: none;
box-shadow: var(--shadow-flat);
}
- Étape 2 : Ajouter le JSX de la toolbar dans Bilan.jsx
Dans <main className="app-main">, ajouter en premier enfant (avant .competences-grid) :
<div className="criteria-toolbar">
<div className="criteria-toolbar-left">
<button
type="button"
className="btn-text"
onClick={() => handleToggleAll(true)}
>
Tout sélectionner
</button>
<span className="toolbar-sep">·</span>
<button
type="button"
className="btn-text"
onClick={() => handleToggleAll(false)}
>
Tout désélectionner
</button>
</div>
<div className="criteria-toolbar-right">
<input
className="toolbar-add-input"
placeholder="Ajouter un critère…"
value={newCriterion}
onChange={(event) => setNewCriterion(event.target.value)}
onKeyDown={(event) => event.key === "Enter" && handleAddCriterion()}
/>
<button
type="button"
className="btn-toolbar-add"
onClick={handleAddCriterion}
title="Ajouter"
>
+
</button>
</div>
</div>
- Étape 3 : Vérifier visuellement
npm run dev
Résultats attendus :
-
Barre fine entre le topbar et la grille avec "Tout sélectionner · Tout désélectionner" à gauche
-
Input + bouton "+" à droite, alignés
-
Les deux actions fonctionnent (cocher/décocher tous)
-
Ajouter un critère via Enter ou clic "+" crée un item dans "À classer"
-
Étape 4 : Commit
git add src/components/Bilan.jsx src/components/Bilan.css
git commit -m "feat: add criteria toolbar with selection controls and add-criterion input"
Task 4 : Layout interne des items en flex-row
Transforme le layout vertical de chaque .competence-item en ligne horizontale : checkbox → label (flex-grow) → level pills → bouton supprimer. Réduit la hauteur de page et améliore la densité.
Files:
- Modify:
src/components/Bilan.css(.competence-item,.competence-label,.level-group,.remove-field-btn)
Interfaces:
- Consumes: structure
.competence-itemexistante dans le JSX (inchangée) - Produces: items en flex-row, visuellement plus compacts
- Étape 1 : Modifier
.competence-itempour flex-row
Localiser la règle .competence-item dans Bilan.css et remplacer par :
.competence-item {
border: 1px solid var(--border-slate-200);
padding: 10px 12px;
border-radius: var(--radius-md);
cursor: grab;
background: #ffffff;
transition: var(--transition);
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
box-shadow: var(--shadow-card);
position: relative;
}
- Étape 2 : Modifier
.competence-labelpour occuper l'espace disponible
Localiser .competence-item .competence-label et remplacer par :
.competence-item .competence-label {
flex: 1;
min-width: 0;
font-weight: 600;
color: var(--text-primary);
font-size: 13px;
cursor: pointer;
line-height: 1.4;
padding-right: 0;
}
- Étape 3 : Modifier
.level-grouppour s'intégrer inline
Localiser .level-group et remplacer par :
.level-group {
display: flex;
flex-wrap: nowrap;
gap: 4px;
align-items: center;
flex-shrink: 0;
border-top: none;
padding-top: 0;
margin-top: 0;
}
- Étape 4 : Supprimer
.level-titledu JSX
Dans Bilan.jsx, dans le rendu de chaque item, localiser et supprimer la ligne :
<span className="level-title">Niveau :</span>
Ce label textuel prend de la place inutilement en layout row.
- Étape 5 : Modifier
.remove-field-btnpour sortir du positionnement absolu
Remplacer la règle .remove-field-btn par :
.remove-field-btn {
background: #fee2e2;
color: #ef4444;
border: 0 !important;
border-radius: 50% !important;
width: 20px !important;
height: 20px !important;
padding: 0 !important;
font-size: 12px !important;
cursor: pointer;
display: flex !important;
align-items: center !important;
justify-content: center !important;
flex-shrink: 0;
opacity: 0;
transform: scale(0.8);
transition: var(--transition);
box-shadow: none !important;
position: static;
}
- Étape 6 : Vérifier visuellement
npm run dev
Résultats attendus :
-
Chaque item de critère est maintenant une seule ligne horizontale
-
Checkbox à gauche, label au centre (stretch), level pills (NA/EC/AC/MA) à droite
-
Le bouton × apparaît au hover, après les pills
-
La grille est sensiblement plus compacte
-
Étape 7 : Commit
git add src/components/Bilan.css src/components/Bilan.jsx
git commit -m "feat: switch criteria items to horizontal flex-row layout"
Task 5 : Section divider + polish de la synthèse
Ajoute le séparateur visuel "Synthèse & Export", supprime le h3 redondant, améliore le header des cards de critères, et nettoie la section synthèse.
Files:
- Modify:
src/components/Bilan.jsx(ajout.section-divider, suppression<h3>Synthèse & Observations</h3>) - Modify:
src/components/Bilan.css(.section-divider, amélioration.competence-card h4, loader inline)
Interfaces:
- Consumes: structure
.evaluation-summary-sectionexistante - Produces: séparateur visuel rendu, section synthèse sans titre redondant
- Étape 1 : Ajouter le CSS du divider et polisher les card headers
Ajouter dans Bilan.css après les classes .criteria-toolbar-* :
.section-divider {
display: flex;
align-items: center;
gap: 16px;
margin: 48px 0 32px;
color: var(--text-muted);
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.section-divider::before,
.section-divider::after {
content: '';
flex: 1;
height: 1px;
background: var(--border-slate-200);
}
Modifier .competence-card h4 pour ajouter un fond coloré léger au header de chaque card :
.competence-card h4 {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 -24px 0 -24px;
padding: 12px 24px;
font-size: 12px;
font-weight: 800;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 0.06em;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
background: rgba(248, 250, 252, 0.8);
border-bottom: 1px solid var(--border-slate-200);
}
- Étape 2 : Ajouter le JSX du divider dans Bilan.jsx
Dans <main className="app-main">, avant .evaluation-summary-section, ajouter :
<div className="section-divider">
<span>Synthèse & Export</span>
</div>
- Étape 3 : Supprimer le h3 redondant dans .evaluation-summary-section
Dans le JSX de .evaluation-summary-section, supprimer :
<h3>Synthèse & Observations</h3>
- Étape 4 : Supprimer le bloc
.ai-status-loaderdu JSX (déjà remplacé par toasts)
Localiser et supprimer du JSX :
{isAiLoading && (
<div className="ai-status-loader">
<span className="spinner" />
<span>Génération du résumé en cours…</span>
</div>
)}
- Étape 5 : Vérifier visuellement
npm run dev
Résultats attendus :
-
Ligne horizontale avec "Synthèse & Export" centré entre la grille et la section de synthèse
-
Headers des cards de critères ont un fond légèrement teinté
-
Pas de h3 redondant dans la section synthèse
-
Le loader inline a disparu (remplacé par toast en Task 1)
-
Étape 6 : Commit
git add src/components/Bilan.jsx src/components/Bilan.css
git commit -m "feat: add section divider, polish card headers, remove redundant labels"
Task 6 : Nettoyage CSS final + responsive
Supprime toutes les classes CSS devenues inutilisées et adapte les media queries au nouveau layout.
Files:
- Modify:
src/components/Bilan.css(suppression.bilan-wrapper,.bilan-container,.header,.controls,.logo-bg,.status,.back-btn,.ai-loader,.ai-status-loader; mise à jour media queries)
Interfaces:
- Consumes: tout le CSS final des tasks 1–5
- Produces: fichier CSS sans classes orphelines, responsive fonctionnel
- Étape 1 : Supprimer les classes obsolètes de Bilan.css
Supprimer entièrement les blocs CSS suivants (ils ne sont plus référencés dans le JSX) :
-
.bilan-wrapperet.logo-bg -
.bilan-container -
.header,.header h1,.header h2 -
.back-btn,.back-btn:hover -
.controls,.controls.controls-select,.controls.controls-select label,.controls.identity input,.controls.actions input,.controls.actions button:last-of-type,.controls input,.controls select -
.status,.status.info,.status.success,.status.error,.status .progress,.status .progress-bar -
.ai-loader -
.ai-status-loader -
.bilan-container button,.bilan-container button:hover,.bilan-container button:disabled(les boutons du topbar ont leurs propres classes) -
.bilan-container select,.bilan-container select option -
.bilan-container textarea(remplacé par les règles dans.summary-inputs-panelet.summary-output-panel) -
Étape 2 : Mettre à jour les media queries
Localiser le bloc @media (max-width: 768px) et le remplacer par :
@media (max-width: 768px) {
.app-topbar {
height: auto;
flex-direction: column;
align-items: flex-start;
padding: 12px 16px;
gap: 10px;
}
.topbar-center {
align-items: flex-start;
}
.topbar-right {
flex-wrap: wrap;
gap: 8px;
}
.topbar-input {
width: 90px;
}
.topbar-actions {
border-left: none;
padding-left: 0;
margin-left: 0;
flex-wrap: wrap;
}
.app-main {
padding: 16px 16px 48px;
}
.criteria-toolbar {
flex-direction: column;
align-items: flex-start;
height: auto;
padding: 8px 0;
gap: 8px;
}
.toolbar-add-input {
width: 100%;
}
.criteria-toolbar-right {
width: 100%;
}
.summary-grid {
grid-template-columns: 1fr;
gap: 24px;
}
.competences-grid {
grid-template-columns: 1fr;
}
.competence-item {
flex-wrap: wrap;
}
.level-group {
flex-wrap: wrap;
margin-left: 28px;
}
.toast-container {
bottom: 12px;
right: 12px;
left: 12px;
max-width: none;
}
}
- Étape 3 : Vérification finale desktop
npm run dev
Vérifier le workflow complet :
- Changer de CP → topbar se met à jour immédiatement
- Renseigner Prénom/Nom dans le topbar
- Cocher plusieurs critères + assigner des niveaux
- Cliquer "📝 Générer" → toast succès apparaît en bas à droite
- Cliquer "✨ Résumer avec l'IA" (sans clé) → toast erreur apparaît
- Le bouton "📄 PDF" apparaît dès qu'un critère est coché
- Ajouter un critère custom via la toolbar → apparaît dans "À classer"
- Drag & drop entre colonnes fonctionne toujours
- Étape 4 : Vérification mobile (DevTools, 375px)
Redimensionner à 375px. Vérifier :
-
Topbar passe en colonne, reste lisible
-
La grille passe en 1 colonne
-
La summary grid passe en 1 colonne
-
Les toasts s'affichent en bas en pleine largeur
-
Étape 5 : Commit final
git add src/components/Bilan.jsx src/components/Bilan.css
git commit -m "chore: remove unused CSS classes and update responsive breakpoints"