# 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
{toasts.map((toast) => (
{toast.withProgress ? (
<>
{toast.message}
>
) : (
{toast.message}
)}
))}
```
- [ ] **É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 && (
` 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 (
{/* 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 */}
{toasts.map((toast) => (
{toast.withProgress ? (
<>
{toast.message}
>
) : (
{toast.message}
)}
))}
);
```
**Important :** déplacer les éléments JSX de la grille `.competences-grid` et de `.evaluation-summary-section` à l'intérieur de ``. Supprimer `` (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 ``, 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 `` 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 ``, ajouter en premier enfant (avant `.competences-grid`) :
```jsx