Files
bilan-ECF/docs/superpowers/plans/2026-06-21-bilan-redesign.md
T
2026-06-21 16:38:56 +02:00

1180 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: `toasts` state, `showStatus(message, type, withProgress)` (même signature qu'avant), `dismissProgressToasts()` (appelé dans les blocs finally)
---
- [ ] **Étape 1 : Remplacer l'état `status` par `toasts` dans Bilan.jsx**
Localiser ligne ~324 dans `src/components/Bilan.jsx`. Remplacer :
```jsx
const [status, setStatus] = useState(null);
```
par :
```jsx
const [toasts, setToasts] = useState([]);
```
- [ ] **Étape 2 : Supprimer le useEffect d'auto-dismiss (lignes ~355-362)**
Supprimer entièrement ce bloc :
```jsx
useEffect(() => {
if (!status || status.withProgress) return;
const timeout = setTimeout(
() => setStatus(null),
status.type === "info" ? 3000 : 5000
);
return () => clearTimeout(timeout);
}, [status]);
```
- [ ] **Étape 3 : Remplacer `showStatus` par la version toast**
Localiser et remplacer le callback `showStatus` (ligne ~346) :
```jsx
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 `dismissProgressToasts` après `showStatus`**
```jsx
const dismissProgressToasts = useCallback(() => {
setToasts((prev) => prev.filter((t) => !t.withProgress));
}, []);
```
- [ ] **Étape 5 : Mettre à jour les blocs `finally` dans `generateAISummaryOpenAI`**
Localiser le bloc `finally` de `generateAISummaryOpenAI` (ligne ~686). Remplacer :
```jsx
} finally {
setStatus((prev) => (prev ? { ...prev, withProgress: false } : prev));
setIsAiLoading(false);
}
```
par :
```jsx
} finally {
dismissProgressToasts();
setIsAiLoading(false);
}
```
- [ ] **Étape 6 : Mettre à jour le bloc `finally` dans `downloadPDF`**
Localiser le bloc `finally` de `downloadPDF` (ligne ~912). Remplacer :
```jsx
} finally {
setStatus((prev) => (prev ? { ...prev, withProgress: false } : prev));
}
```
par :
```jsx
} 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) :
```jsx
<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 :
```jsx
{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 :
```css
.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**
```bash
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**
```bash
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), `toasts` state (Task 1)
- Produces: structure `.app-shell > .app-topbar + .app-main` utilisé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 :
```css
.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 à :
```jsx
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**
```bash
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**
```bash
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-toolbar` dans `<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` :
```css
.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`) :
```jsx
<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**
```bash
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**
```bash
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-item` existante dans le JSX (inchangée)
- Produces: items en flex-row, visuellement plus compacts
---
- [ ] **Étape 1 : Modifier `.competence-item` pour flex-row**
Localiser la règle `.competence-item` dans `Bilan.css` et remplacer par :
```css
.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-label` pour occuper l'espace disponible**
Localiser `.competence-item .competence-label` et remplacer par :
```css
.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-group` pour s'intégrer inline**
Localiser `.level-group` et remplacer par :
```css
.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-title` du JSX**
Dans `Bilan.jsx`, dans le rendu de chaque item, localiser et supprimer la ligne :
```jsx
<span className="level-title">Niveau :</span>
```
Ce label textuel prend de la place inutilement en layout row.
- [ ] **Étape 5 : Modifier `.remove-field-btn` pour sortir du positionnement absolu**
Remplacer la règle `.remove-field-btn` par :
```css
.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**
```bash
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**
```bash
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-section` existante
- 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-*` :
```css
.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 :
```css
.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 :
```jsx
<div className="section-divider">
<span>Synthèse &amp; Export</span>
</div>
```
- [ ] **Étape 3 : Supprimer le h3 redondant dans .evaluation-summary-section**
Dans le JSX de `.evaluation-summary-section`, supprimer :
```jsx
<h3>Synthèse & Observations</h3>
```
- [ ] **Étape 4 : Supprimer le bloc `.ai-status-loader` du JSX (déjà remplacé par toasts)**
Localiser et supprimer du JSX :
```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**
```bash
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**
```bash
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 15
- 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-wrapper` et `.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-panel` et `.summary-output-panel`)
- [ ] **Étape 2 : Mettre à jour les media queries**
Localiser le bloc `@media (max-width: 768px)` et le remplacer par :
```css
@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**
```bash
npm run dev
```
Vérifier le workflow complet :
1. Changer de CP → topbar se met à jour immédiatement
2. Renseigner Prénom/Nom dans le topbar
3. Cocher plusieurs critères + assigner des niveaux
4. Cliquer "📝 Générer" → toast succès apparaît en bas à droite
5. Cliquer "✨ Résumer avec l'IA" (sans clé) → toast erreur apparaît
6. Le bouton "📄 PDF" apparaît dès qu'un critère est coché
7. Ajouter un critère custom via la toolbar → apparaît dans "À classer"
8. 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**
```bash
git add src/components/Bilan.jsx src/components/Bilan.css
git commit -m "chore: remove unused CSS classes and update responsive breakpoints"
```