# Design — Page publique `/ateliers` (DA 2.0)

**Date** : 2026-07-20
**Statut** : ~~validé en brainstorm~~ → **v1 rejetée en validation finale, remplacée par la Révision v2 (voir fin de document)**
**Sous-projet** : 1/3 de la refonte « Ateliers, Réservation & Bons Cadeaux »

---

## 1. Contexte & objectif

Le site n'a aucune page client présentant les ateliers/stages de coutellerie : seul le
back-office (`Gestion\AtelierController`, 12 routes) gère ateliers, créneaux et
réservations (saisies manuellement, téléphone/comptoir). Le modèle de données est
complet et prêt : `Atelier` → `AtelierCreneau` → `AtelierReservation`, avec
`getPlacesRestantes()`, `CreneauStatut` (OUVERT/FERME/ANNULE) et
`ReservationSource::SITE` déjà anticipé pour la réservation en ligne.

Objectif : créer la page vitrine publique `/ateliers` en DA 2.0, affichant les
ateliers actifs et leurs prochains créneaux avec places restantes. La réservation en
ligne arrive au sous-projet 2 ; en attendant, le CTA renvoie vers le contact
(cohérent avec l'existant : les réservations se prennent déjà par téléphone).

## 2. Décomposition du projet global

| # | Sous-projet | Contenu | Dépend de |
|---|-------------|---------|-----------|
| 1 | **Page ateliers publique** (ce doc) | Vitrine DA 2.0, créneaux visibles, CTA contact | — |
| 2 | Panier & réservation | Refonte panier en lignes typées (produit / résa atelier / bon cadeau), audit sécu checkout, mise à jour PayPal, flow résa en ligne complet (formulaire, emails confirmation + rappel J-1, back-office) | 1 |
| 3 | Bons cadeaux | Génération sécurisée, montant libre (paliers 50–500 €) + bon « activité », option envoi papier +5 €, envoi par email post-achat, rubrique compte client, intégration page produits | 2 |

Chaque sous-projet suit son propre cycle spec → plan → implémentation.

Contraintes techniques découvertes qui pèseront sur le sous-projet 2 (notées ici pour
mémoire, hors scope du présent design) :

- Panier actuel = tableau session d'IDs produits, quantité 1 codée en dur
  (pièces uniques) — la refonte en lignes typées est un prérequis résa/bons cadeaux.
- `PayPalService` utilise déjà Orders v2 + OAuth2, mais `return_url` localhost
  codée en dur (bug bloquant prod connu) et durcissement à faire.
- L'achat force inscription + activation email (pas de guest checkout) — à trancher
  pour le parcours résa.

## 3. Décisions actées (brainstorm)

| Question | Décision |
|----------|----------|
| Ordre des chantiers | Ordre du spec : ateliers → panier/résa → bons cadeaux |
| Structure page | Page unique immersive `/ateliers` (pas de liste + détail — petit catalogue) |
| Créneaux en phase 1 | Affichés avec places restantes ; CTA intérim vers contact |
| Visuels | Photos d'ambiance statiques communes (aucun champ image par atelier, ajoutable plus tard) |
| Approche technique | Server-side pur (Twig), pas d'endpoint AJAX — YAGNI, le JSON arrivera avec le picker du sous-projet 2 |
| Créneaux complets | Affichés avec badge « Complet » (rareté visible), pas masqués |
| Granularité CTA | Un CTA global par atelier, pas par créneau (aucune sélection réelle en P1) |

## 4. Design

### S1 — Routing & données

- Nouveau controller public `App\Controller\AtelierController` (namespace racine,
  distinct de `App\Controller\Gestion\AtelierController` — même séparation
  public/gestion que l'affûtage).
- Route `GET /ateliers`, nom `app_ateliers`.
- `AtelierRepository::findActifsAvecCreneauxAVenir()` :
  - ateliers `actif = true`, triés par `createdAt` croissant ;
  - `LEFT JOIN` fetch des créneaux `statut = OUVERT` et `startAt > now`,
    triés par `startAt` croissant ;
  - fetch-join des réservations des créneaux retenus pour que
    `getPlacesRestantes()` ne déclenche aucun N+1 ;
  - une seule requête au total.
- Le controller tronque à **5 prochains créneaux** par atelier avant rendu
  (la requête reste simple, le template reçoit des données prêtes).
- Places restantes = `capacite − Σ places des réservations non annulées`
  (logique existante `AtelierCreneau::getPlacesRestantes()`).

### S2 — Layout DA 2.0

Structure de haut en bas :

1. **Hero immersif** : image d'ambiance existante — `photo_boutique.webp` par
   défaut (ajustable au rendu visuel) —, titre + sous-titre, scroll cue, même
   langage que `newaffutage` / `newpresentation`.
2. **Une section par atelier actif**, alternance visuel gauche/droite :
   - titre + description (contenu saisi en admin) ;
   - chips méta : durée (`dureeMinutes` formatée), prix, capacité max,
     « à Clermont-Ferrand » ;
   - visuel d'ambiance statique ;
   - **bloc créneaux** : cartes date (jour + mois abrégé, heure, badge
     « X places restantes » ou « Complet ») ;
   - **CTA global** « Réserver — par téléphone ou message » → `app_contact`,
     avec mention discrète « Réservation en ligne bientôt disponible ».
3. **Bandeau final réassurance** : savoir-faire depuis 1981, petits groupes,
   matériel fourni + CTA contact.

Intégration technique :

- CSS : nouvelle section commentée dans `ds-v2-pages.css`, préfixe de classes
  `.atl-` (pattern des autres pages v2).
- JS : animations reveal via les patterns IntersectionObserver existants ;
  aucun JS de données.
- Template : `templates/ateliers/index.html.twig` (pluriel, aligné sur la
  route ; le back-office reste dans `templates/gestion/ateliers/`), étend
  `base.html.twig`.
- Responsive : sections empilées en mobile, créneaux en scroll horizontal.

### S3 — Navigation & SEO

- Entrée « Ateliers » dans la nav desktop (entre Affûtage et Nos Produits,
  `base.html.twig`) et dans le menu mobile.
- `<title>` : « Ateliers & stages de coutellerie — Vulcan'Affûtage » ;
  meta description dédiée ; un seul H1.

### S4 — Edge cases & robustesse

- Aucun atelier actif → la page rend le hero + un état vide élégant
  (texte vitrine générique + CTA contact), pas de 404.
- Atelier sans créneau futur → « Prochaines dates à venir — contactez-nous »
  à la place du bloc créneaux.
- Créneaux `FERME` / `ANNULE` et créneaux passés exclus **au niveau SQL**.
- Page en lecture seule : aucune écriture DB, aucun formulaire, surface
  d'attaque nulle.

### S5 — Tests

`tests/Gestion/AtelierPublicTest.php` (WebTestCase, pattern
`AffutageProPublicTest` + `GestionTestHelper`) :

1. `GET /ateliers` → 200.
2. Atelier actif visible ; atelier inactif absent.
3. Créneau futur OUVERT visible avec places restantes exactes
   (capacité − réservations non annulées).
4. Créneau passé et créneau FERME absents.
5. Badge « Complet » quand 0 place restante.
6. État vide (aucun atelier) → 200 + message vitrine.
7. Lien nav « Ateliers » présent dans le layout.

La suite existante (137 tests) doit rester verte.

## 5. Hors scope (explicitement)

- Réservation en ligne, sélection de créneau, panier — sous-projet 2.
- Endpoint JSON créneaux — créé au sous-projet 2 avec le picker.
- Champ image par atelier en admin — extension future si besoin.
- Teaser ateliers sur la homepage — évolution ultérieure éventuelle.
- Bons cadeaux « activité » liés aux ateliers — sous-projet 3.

## 6. Critères de succès

- Page `/ateliers` en production visuelle DA 2.0, cohérente avec les pages v2
  existantes, mobile + desktop.
- Données réelles : ateliers et créneaux administrés apparaissent sans action
  supplémentaire ; places restantes justes.
- Tous les tests passent (existants + nouveaux).

---

## 7. RÉVISION v2 (2026-07-20 soir) — après rejet de la v1

La v1 (sections vitrine + créneaux affichés d'emblée) est rejetée : « fade,
pas clair, ne donne pas envie », créneaux directs refusés. Référence design :
branche `origin/atelier-montage-couteau` du repo v1 (page créée par le user —
hero 75vh, timeline programme, effets riches, sticky CTA). Décisions :

| Question | Décision |
|----------|----------|
| Direction | Esprit de la page référence, habillage DA 2.0 (tokens v2) |
| Structure | Page épurée : hero fort + strip valeurs + une carte séduisante par atelier, **aucun créneau visible d'emblée** |
| Par atelier | 2 CTA : « En savoir plus » (modal) + « Choisir un créneau » (modal) |
| Modal « En savoir plus » | Déroulement étape par étape en timeline (rail + pastilles + cartes), **contenu administrable** ; fallback description |
| Modal « Choisir un créneau » | Cartes dates + places ; clic créneau → actions **mailto pré-rempli** (atelier + date) et **appel** (la page contact n'a pas de formulaire) ; remplacé par le panier au sous-projet 2 |
| Compo (carte blanche) | + teaser bon cadeau, FAQ (contenu adapté de la page référence), sticky bar CTA, JSON-LD Course |
| Admin | Nouveau champ `Atelier.etapes` (JSON nullable, liste {titre, texte}), éditeur dynamique dans le form admin, migration dédiée |

Conservé de la v1 : route `app_ateliers`, repository fetch-join, formatage
controller, marqueurs de test `data-atl-*`, entrée nav.

---

## 8. RÉVISION v3 (2026-07-20, décision client) — atelier unique

Le client ne proposera **qu'un seul atelier** : « Création de votre couteau
personnalisé ». Directive : reprendre la page de la branche
`atelier-montage-couteau` (repo v1) **sans changer profondément sa
structure** — il l'appréciait — en la passant en DA 2.0 (réorga +
amélioration). L'admin multi-ateliers reste tel quel ; l'ouverture
multi-ateliers côté client reste possible plus tard.

Structure single long-form (celle de sa page) :

1. Hero promesse + CTA « Réserver votre stage ({prix}) » (ancre #reservation)
   et « Découvrir l'atelier » (ancre #programme).
2. Strip 4 valeurs en mini-cartes (durée, couteau « rosace », petit groupe, prix).
3. Programme : timeline à plat (étapes admin de l'atelier ; fallback = les
   6 étapes de la page de référence).
4. Matières : accordéon horizontal full-bleed hover-expand (6 matières
   statiques, images de la branche v1), tap-open en mobile.
5. Infos utiles (3 cartes) + bloc « Offrir l'atelier » (mailto).
6. Bloc réservation focal : cartes créneaux réels → confirmation
   email/téléphone pré-remplis (mécanisme v2 conservé, sans modal).
7. FAQ, réassurance 1981, sticky CTA, JSON-LD Course.

Données : premier atelier actif (createdAt ASC) alimente titre, prix, durée,
capacité, étapes, créneaux ; **fallback statique complet** si aucun atelier
actif (contenu de la page de référence). Les autres ateliers actifs sont
ignorés côté client pour l'instant. Modals v2 supprimés.
