diff --git a/docs/superpowers/plans/2026-06-21-bilan-redesign.md b/docs/superpowers/plans/2026-06-21-bilan-redesign.md new file mode 100644 index 0000000..034f597 --- /dev/null +++ b/docs/superpowers/plans/2026-06-21-bilan-redesign.md @@ -0,0 +1,1179 @@ +# 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 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 `
` 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 ( +
+
+
+ Arinfo +
+ +
+ +
+ {currentTitle.h1} + {currentTitle.h2} +
+ +
+ setFirstName(event.target.value)} + /> + setLastName(event.target.value)} + /> +
+ + + {anySelected && ( + + )} +
+
+
+ +
+ {/* 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 `Logo watermark` (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 +
+
+ + · + +
+
+ setNewCriterion(event.target.value)} + onKeyDown={(event) => event.key === "Enter" && handleAddCriterion()} + /> + +
+
+``` + +- [ ] **É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 +Niveau : +``` + +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 `

Synthèse & Observations

`) +- 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 `
`, avant `.evaluation-summary-section`, ajouter : + +```jsx +
+ Synthèse & Export +
+``` + +- [ ] **Étape 3 : Supprimer le h3 redondant dans .evaluation-summary-section** + +Dans le JSX de `.evaluation-summary-section`, supprimer : + +```jsx +

Synthèse & Observations

+``` + +- [ ] **Étape 4 : Supprimer le bloc `.ai-status-loader` du JSX (déjà remplacé par toasts)** + +Localiser et supprimer du JSX : + +```jsx +{isAiLoading && ( +
+ + Génération du résumé en cours… +
+)} +``` + +- [ ] **É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 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-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" +``` diff --git a/docs/superpowers/specs/2026-06-21-bilan-redesign-design.md b/docs/superpowers/specs/2026-06-21-bilan-redesign-design.md new file mode 100644 index 0000000..3cbd00f --- /dev/null +++ b/docs/superpowers/specs/2026-06-21-bilan-redesign-design.md @@ -0,0 +1,93 @@ +# Redesign Professionnel — Bilan d'Évaluation + +**Date :** 2026-06-21 +**Contexte :** Application React/Vite standalone utilisée par un formateur seul pour évaluer un apprenant (CP1–CP9), puis exporter un PDF. + +--- + +## Problèmes identifiés + +1. Pas de hiérarchie visuelle — tout est au même niveau +2. Workflow non évident — l'ordre des actions n'est pas guidé +3. Aspect peu professionnel à montrer +4. Actions importantes (Générer, IA, PDF) noyées en bas de page + +--- + +## Architecture générale + +### Topbar sticky (64px) + +Structure en trois zones : + +- **Gauche :** logo Arinfo (32px hauteur) + séparateur + CP selector stylisé en pill indigo +- **Centre :** titre dynamique `Bilan CPX — [sous-titre]` en font-weight 600, taille 15px +- **Droite :** champs Prénom + Nom (inputs compacts, 36px, fond transparent) + trois boutons d'action principaux + +Boutons topbar (hiérarchie visuelle explicite) : +- `📝 Générer` — style outline, secondaire +- `✨ Résumer avec l'IA` — gradient violet→rose, bouton dominant +- `📄 PDF` — gradient emerald, visible uniquement si `anySelected === true` + +Le topbar reste visible au scroll via `position: sticky; top: 0; z-index: 100`. + +### Toolbar critères (44px) + +Barre fine sous le topbar, fond `#f8fafc`, `border-bottom` subtile : +- Gauche : liens texte "Tout sélectionner" / "Tout désélectionner" +- Droite : input "Ajouter un critère…" + bouton `+` compact + +### Grille de critères + +Trois colonnes (Performance / Compétence / Connaissance), layout identique à l'existant avec amélioration du layout interne des items. + +**Header de card :** titre bucket uppercase + badge compteur sur fond coloré très léger (teinte propre à chaque bucket). + +**Layout interne d'un item :** passage de `flex-column` à `flex-row` : +``` +[☐ checkbox] [label texte — flex-grow] [NA][EC][AC][MA] [× supprimer] +``` +Cela rend les items plus denses, plus lisibles, et réduit la hauteur de page. + +### Section synthèse + +Séparée du reste par un divider visuel centré : `──── Synthèse & Export ────` + +Layout deux colonnes : +- **Gauche :** notes formateur (textarea), tonalité (select), checkbox "insérer en tête", config API OpenAI (accordéon collapsible) +- **Droite :** textarea bilan (monospace, `min-height: 380px`, hauteur libre) + +### Notifications de statut + +Les messages de statut (`success`, `error`, `info`) passent en **toast flottant** positionné `fixed` en bas à droite, avec disparition automatique. Ils ne s'insèrent plus dans le flux de contenu. + +--- + +## Changements CSS + +- Ajout de `.app-topbar` (sticky header) +- Ajout de `.criteria-toolbar` +- Ajout de `.section-divider` +- Modification de `.competence-item` : `flex-direction: row`, `align-items: center` +- Modification de `.level-group` : suppression du `border-top`, intégration inline +- Ajout de `.toast-container` + `.toast` (positionnement fixed) +- Suppression des `.controls` génériques (remplacés par topbar et toolbar dédiés) + +--- + +## Changements JSX + +- Le rendu passe de `.bilan-wrapper > .bilan-container` à `.app-shell > .app-topbar + .app-main` +- Le `