1160 lines
50 KiB
Markdown
1160 lines
50 KiB
Markdown
# NAV V2 — Spec front-end
|
||
|
||
Date : 2026-04-14
|
||
Auteur : ATIS agent
|
||
Version : 1.0
|
||
|
||
---
|
||
|
||
## Sommaire
|
||
|
||
```
|
||
1. Wireframes ASCII
|
||
1.1 Page accueil desktop
|
||
1.2 Page accueil mobile
|
||
1.3 Page fiche /fiche/[id] desktop
|
||
1.4 Page fiche /fiche/[id] mobile
|
||
1.5 Bottom-sheet chatbot mobile (ouvert)
|
||
1.6 Bottom-sheet chatbot desktop (ouvert)
|
||
1.7 Modale formulaire "Ajouter une fiche"
|
||
1.8 Bandeau bas
|
||
2. Routes Nuxt
|
||
3. Composants à créer
|
||
4. States & data flow
|
||
5. Responsive breakpoints
|
||
6. Détails chatbot (bottom-sheet)
|
||
7. Bandeau bas : données affichées
|
||
8. Page fiche détaillée
|
||
9. Formulaire "Ajouter une fiche"
|
||
10. Accessibilité & sobriété
|
||
```
|
||
|
||
---
|
||
|
||
## 1. Wireframes ASCII
|
||
|
||
### 1.1 Page accueil — desktop (> 1024px)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ NAV [Contribuer une fiche +] │
|
||
│ Navigateur Architecture │
|
||
├───────────────────┬──────────────────────────────────────────────────────┤
|
||
│ SIDEBAR (320px) │ CARTE CENTRALE │
|
||
│ │ │
|
||
│ [ Recherche 🔍]│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ │ [Métropole] [Outre-mer ▼] Carte Leaflet│ │
|
||
│ ── ÉCHELLE ── │ │ │ │
|
||
│ ○ National │ │ · point carte · point │ │
|
||
│ ● Régional │ │ · point · │ │
|
||
│ ○ Départemental │ │ · point sélectionné ● │ │
|
||
│ ○ Local │ │ · · │ │
|
||
│ │ │ · · │ │
|
||
│ ── FONCTION ── │ │ │ │
|
||
│ [Juridique ×] │ │ [ + ] [-] [⟳] (OSM © contrib.) │ │
|
||
│ [Technique ×] │ └─────────────────────────────────────────────────┘ │
|
||
│ [Économique ] │ │
|
||
│ [Administratif ] │ Résultats : 23 fiches │
|
||
│ [Chantier ] │ ┌──────────────────┐ ┌──────────────────┐ │
|
||
│ [Comptabilité ] │ │ FicheCard │ │ FicheCard │ │
|
||
│ [Prospection ] │ │ Nom organisation │ │ Nom organisation │ │
|
||
│ [RH ] │ │ Échelle · Fonct. │ │ Échelle · Fonct. │ │
|
||
│ [Santé mentale ] │ │ Ville │ │ Ville │ │
|
||
│ │ └──────────────────┘ └──────────────────┘ │
|
||
│ 23 résultats │ │
|
||
├───────────────────┴──────────────────────────────────────────────────────┤
|
||
│ BandeauBas │
|
||
│ Ce mois-ci : 0,12€ / 8K tokens / ~0,001 kWh [♥ Soutenir NAV] Semaine : 3 fiches / 12 requêtes │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
[🤖 Aide IA] ← BD
|
||
```
|
||
|
||
### 1.2 Page accueil — mobile (< 768px)
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ NAV [≡] │
|
||
├─────────────────────────────┤
|
||
│ [Métropole] [Outre-mer ▼] │
|
||
├─────────────────────────────┤
|
||
│ │
|
||
│ Carte Leaflet │
|
||
│ (plein écran, ~60vh) │
|
||
│ │
|
||
│ · point · point │
|
||
│ · ●sélect. │
|
||
│ · · │
|
||
│ · │
|
||
│ [+] [-] (OSM ©) │
|
||
│ │
|
||
├─────────────────────────────┤
|
||
│ [Filtres ▼] 23 résultats │
|
||
├─────────────────────────────┤
|
||
│ ┌─────────────────────────┐│
|
||
│ │ FicheCard ││
|
||
│ │ Nom organisation ││
|
||
│ │ [Régional] [Juridique] ││
|
||
│ │ Paris ││
|
||
│ └─────────────────────────┘│
|
||
│ ┌─────────────────────────┐│
|
||
│ │ FicheCard ││
|
||
│ └─────────────────────────┘│
|
||
│ ┌─────────────────────────┐│
|
||
│ │ FicheCard ││
|
||
│ └─────────────────────────┘│
|
||
│ │
|
||
├─────────────────────────────┤
|
||
│ Ce mois-ci : 0,12€ [♥ Don] │
|
||
└─────────────────────────────┘
|
||
[🤖] ← BD
|
||
```
|
||
|
||
**Drawer sidebar mobile** — déclenché par [≡] :
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ ✕ Filtres │
|
||
│ │
|
||
│ Recherche : [ ] │
|
||
│ │
|
||
│ ÉCHELLE │
|
||
│ ○ National │
|
||
│ ● Régional │
|
||
│ ○ Départemental │
|
||
│ ○ Local │
|
||
│ │
|
||
│ FONCTION (1–5) │
|
||
│ ☑ Juridique ☐ Technique │
|
||
│ ☐ Économique ☐ Admin │
|
||
│ ☐ Chantier ☐ Compta │
|
||
│ ☐ Prospection ☐ RH │
|
||
│ ☐ Santé mentale │
|
||
│ │
|
||
│ [ Appliquer les filtres ] │
|
||
└─────────────────────────────┘
|
||
```
|
||
|
||
### 1.3 Page fiche `/fiche/[id]` — desktop
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ NAV [Contribuer une fiche +] │
|
||
├──────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ← Retour carte nav.trans-former.fr/fiche/[id] │
|
||
│ │
|
||
│ ┌──────────────────────────────────────────┬──────────────────────────┐ │
|
||
│ │ HEADER FICHE │ Mini-carte (250px) │ │
|
||
│ │ Nom de l'organisation │ │ │
|
||
│ │ [Régional] [Juridique] [Technique] │ · localisation │ │
|
||
│ │ ↳ Paris | Site : architectes-idf.org │ │ │
|
||
│ │ │ (Leaflet, zoom 10, │ │
|
||
│ │ Description courte (contributeur) │ non interactif) │ │
|
||
│ │ ────────────────────────────────── │ │ │
|
||
│ │ Description enrichie IA └──────────────────────────┘ │
|
||
│ │ (scraping + synthèse Mistral) │ │
|
||
│ │ │ │
|
||
│ │ Points clés : │ │
|
||
│ │ • [point clé 1] │ │
|
||
│ │ • [point clé 2] │ │
|
||
│ │ • [point clé 3] │ │
|
||
│ │ │ │
|
||
│ │ [→ Visiter le site] │ │
|
||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ COMMENTAIRES (3) │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ [Avatar] Prénom A. — 2026-03-01 │ │
|
||
│ │ "Très utile pour les questions de droit du travail en IDF." │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ [Avatar] Prénom B. — 2026-03-14 │ │
|
||
│ │ "Permanences juridiques réactives, réponse en 48h." │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ Ajouter un commentaire : │
|
||
│ [ ] (max 500 car.) │
|
||
│ Pseudo (optionnel) : [ ] [Envoyer] │
|
||
│ → Vos commentaires sont filtrés par une IA avant publication. │
|
||
│ │
|
||
├──────────────────────────────────────────────────────────────────────────┤
|
||
│ BandeauBas │
|
||
│ Ce mois-ci : 0,12€ / 8K tokens [♥ Soutenir NAV] 3 fiches / 12 req │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
[🤖 Aide IA]
|
||
```
|
||
|
||
### 1.4 Page fiche `/fiche/[id]` — mobile
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ ← Retour NAV [≡] │
|
||
├─────────────────────────────┤
|
||
│ Nom de l'organisation │
|
||
│ [Régional] [Juridique] │
|
||
│ [Technique] │
|
||
│ Paris | architectes-idf.fr │
|
||
├─────────────────────────────┤
|
||
│ Mini-carte (200px height) │
|
||
│ · localisation │
|
||
├─────────────────────────────┤
|
||
│ Description courte │
|
||
│ ───────────────────────── │
|
||
│ Description enrichie IA │
|
||
│ │
|
||
│ Points clés : │
|
||
│ • point clé 1 │
|
||
│ • point clé 2 │
|
||
│ • point clé 3 │
|
||
│ │
|
||
│ [→ Visiter le site] │
|
||
├─────────────────────────────┤
|
||
│ COMMENTAIRES (3) │
|
||
│ ───────────────────────── │
|
||
│ Prénom A. — 2026-03-01 │
|
||
│ "Très utile pour les..." │
|
||
│ ───────────────────────── │
|
||
│ Ajouter un commentaire : │
|
||
│ [ ] │
|
||
│ [Envoyer] │
|
||
├─────────────────────────────┤
|
||
│ Ce mois-ci : 0,12€ [♥ Don]│
|
||
└─────────────────────────────┘
|
||
[🤖] ← BD
|
||
```
|
||
|
||
### 1.5 Bottom-sheet chatbot — mobile (ouvert)
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ (carte visible en fond) │
|
||
│ (opacifiée 30%) │
|
||
│ │
|
||
│ │
|
||
├─────────────────────────────┤ ← drag handle
|
||
│ 🤖 Aide IA ✕ Fermer │
|
||
│ │
|
||
│ ┌─────────────────────────┐│
|
||
│ │ Ce chatbot fonctionne ││
|
||
│ │ sur un serveur EU ││
|
||
│ │ souverain (Mistral FR, ││
|
||
│ │ zéro rétention). ││
|
||
│ │ ││
|
||
│ │ Formule ta requête : ││
|
||
│ │ • Besoin : [ce que...] ││
|
||
│ │ • Thématique : [...] ││
|
||
│ │ • Lieu : [région/ville] ││
|
||
│ │ ││
|
||
│ │ Exemple : "Salarié ││
|
||
│ │ d'agence, litige ││
|
||
│ │ employeur, juridique ││
|
||
│ │ droit du travail, IDF." ││
|
||
│ └─────────────────────────┘│
|
||
│ │
|
||
│ ┌─────────────────────────┐│
|
||
│ │ (historique messages) ││
|
||
│ │ ││
|
||
│ │ ││
|
||
│ └─────────────────────────┘│
|
||
│ │
|
||
│ [ Écris ta question... ] │
|
||
│ [ Envoyer →] │
|
||
└─────────────────────────────┘
|
||
```
|
||
|
||
### 1.6 Bottom-sheet chatbot — desktop (ouvert)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ (carte + sidebar en fond, opacifiée 20%) │
|
||
│ │
|
||
│ │
|
||
├──────────────────────────────────────────┬───────────────────────────────┤
|
||
│ │ 🤖 Aide IA ✕ │
|
||
│ │ │
|
||
│ │ ┌─────────────────────────┐ │
|
||
│ │ │ Ce chatbot fonctionne │ │
|
||
│ │ │ sur un serveur EU │ │
|
||
│ │ │ souverain (Mistral FR, │ │
|
||
│ │ │ zéro rétention). │ │
|
||
│ │ │ │ │
|
||
│ │ │ Formule ta requête : │ │
|
||
│ │ │ • Besoin : │ │
|
||
│ │ │ • Thématique : │ │
|
||
│ │ │ • Lieu : │ │
|
||
│ │ └─────────────────────────┘ │
|
||
│ │ │
|
||
│ │ (historique messages) │
|
||
│ │ │
|
||
│ │ [ Écris ta question... ] │
|
||
│ │ [ Envoyer →] │
|
||
├──────────────────────────────────────────┴───────────────────────────────┤
|
||
│ BandeauBas │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
Note : sur desktop, le chatbot s'ouvre en panneau latéral droit (400px) plutôt qu'en bottom-sheet plein écran. Même logique UX, adaptation de surface.
|
||
|
||
### 1.7 Modale formulaire "Ajouter une fiche"
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ Proposer une ressource ✕ Fermer │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Nom de l'organisation * │
|
||
│ [ ] │
|
||
│ │
|
||
│ URL du site (optionnel, recommandé) │
|
||
│ [ https://... ] │
|
||
│ │
|
||
│ Description courte * (max 200 caractères) │
|
||
│ [ │
|
||
│ ] │
|
||
│ 0/200 │
|
||
│ │
|
||
│ Échelle * (une seule) │
|
||
│ ○ National ○ Régional ○ Départemental ○ Local │
|
||
│ │
|
||
│ Fonctions * (1 à 5, l'ordre de clic = priorité) │
|
||
│ ☐ Juridique ☐ Technique ☐ Économique │
|
||
│ ☐ Administratif ☐ Chantier ☐ Comptabilité │
|
||
│ ☐ Prospection ☐ RH ☐ Santé mentale │
|
||
│ │
|
||
│ Territoire * │
|
||
│ ☑ Métropole ☐ Guadeloupe ☐ Martinique │
|
||
│ ☐ Guyane ☐ Réunion ☐ Mayotte │
|
||
│ │
|
||
│ Ville (pour géolocalisation) * │
|
||
│ [ ] │
|
||
│ │
|
||
│ Votre email (optionnel — pour le suivi de modération) │
|
||
│ [ ] │
|
||
│ │
|
||
│ ──────────────────────────────────────────────────────── │
|
||
│ [ Annuler ] [ Proposer la fiche → ] │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Message post-submit** (remplace le contenu de la modale) :
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ Merci ! ✕ Fermer │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Ta fiche est en cours de traitement. │
|
||
│ │
|
||
│ Une IA va scraper le site et enrichir la description. │
|
||
│ Jules (et bientôt une équipe de modération) valide │
|
||
│ sous 7 jours. │
|
||
│ │
|
||
│ Tu peux suivre l'avancement ici : │
|
||
│ nav.trans-former.fr/suivi/[token-temp] │
|
||
│ │
|
||
│ [ Fermer ] │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 1.8 Bandeau bas
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ Ce mois-ci : 0,12€ consommés [♥ Soutenir NAV] Cette semaine : │
|
||
│ 8 423 tokens · ~0,001 kWh 3 nouvelles fiches │
|
||
│ 12 requêtes chatbot │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
Sur mobile (< 768px) : bandeau réduit à 1 ligne, seul le bouton don et le résumé très court.
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ 0,12€ · 8K tok [♥ Don] │
|
||
└─────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Routes Nuxt
|
||
|
||
```
|
||
/ → page accueil (carte + sidebar + liste fiches)
|
||
/fiche/[id] → page fiche détaillée (SSR, OG meta, partage)
|
||
/a-propos → page souveraineté + gouvernance + crédits
|
||
/contribuer → formulaire soumission fiche (page dédiée mobile) ou
|
||
redirige vers / avec ouverture modale (desktop)
|
||
/suivi/[token] → page de suivi statut modération (token temp)
|
||
|
||
/api/search → POST : recherche IA (chatbot) → réponse Mistral Small
|
||
/api/submit → POST : soumission nouvelle fiche → NocoDB + worker
|
||
/api/comment → POST : ajout commentaire → filtre éthique → NocoDB
|
||
/api/stats → GET : récupération données bandeau bas (stats_usage)
|
||
/api/geocode → POST : ville → coordonnées (via Nominatim, gratuit)
|
||
```
|
||
|
||
Notes :
|
||
- `/contribuer` route duale : en mobile elle rend une page dédiée pour l'UX formulaire ; en desktop elle redirige vers `/` avec un query param `?contribute=1` qui ouvre la modale automatiquement.
|
||
- `/suivi/[token]` : page légère affichant le statut de la fiche soumise (en attente / enrichie IA / validée / rejetée).
|
||
- Toutes les routes `/api/*` sont des server routes Nuxt (dossier `server/api/`).
|
||
|
||
---
|
||
|
||
## 3. Composants à créer
|
||
|
||
### 3.1 Composants de layout
|
||
|
||
**`AppHeader.vue`**
|
||
- Rôle : barre de navigation principale (logo + bouton "Contribuer")
|
||
- Props : aucune (état global géré par store)
|
||
- Slots : aucun
|
||
- Note : sticky, z-index au-dessus de la sidebar
|
||
|
||
**`BandeauBas.vue`**
|
||
- Rôle : bandeau bas fixe avec stats coûts/tokens/carbone, bouton don, stats activité
|
||
- Props : `stats: StatsUsage` (objet issu de `/api/stats`)
|
||
- Émettre : rien (lecture seule)
|
||
- Note : hauteur fixe 48px desktop, 36px mobile ; `position: fixed; bottom: 0`
|
||
|
||
### 3.2 Sidebar & filtres
|
||
|
||
**`NavSidebar.vue`**
|
||
- Rôle : sidebar gauche avec recherche texte + sélection ÉCHELLE + sélection FONCTION
|
||
- Props :
|
||
- `modelValue: Filters` (v-model)
|
||
- `resultCount: number`
|
||
- Émet : `update:modelValue` à chaque changement de filtre
|
||
- Contient : `SearchInput`, `EchelleFilter`, `FonctionFilter`
|
||
- Note : sur mobile, ce composant est rendu dans `DrawerSidebar`
|
||
|
||
**`DrawerSidebar.vue`**
|
||
- Rôle : enveloppe modale/drawer pour la sidebar en mobile
|
||
- Props : `open: boolean`
|
||
- Émet : `close`
|
||
- Contient : `NavSidebar`
|
||
|
||
**`EchelleFilter.vue`**
|
||
- Rôle : ligne de filtres radio ÉCHELLE (mono-sélection obligatoire)
|
||
- Props :
|
||
- `modelValue: string | null` (valeur sélectionnée)
|
||
- `options: string[]` (National / Régional / Départemental / Local)
|
||
- Émet : `update:modelValue`
|
||
- Note : le premier click sur une valeur déjà sélectionnée la désélectionne (équivalent "tout")
|
||
|
||
**`FonctionFilter.vue`**
|
||
- Rôle : grille de tags FONCTION (multi-sélection 0–5, ordre de clic = priorité)
|
||
- Props :
|
||
- `modelValue: string[]` (tableau ordonné par priorité)
|
||
- `options: string[]`
|
||
- `maxSelected: number` (défaut: 5)
|
||
- Émet : `update:modelValue`
|
||
- Note : afficher le rang (1, 2, 3...) sur les tags sélectionnés
|
||
|
||
**`SearchInput.vue`**
|
||
- Rôle : champ de recherche textuelle (filtre côté client ou appel API)
|
||
- Props :
|
||
- `modelValue: string`
|
||
- `placeholder: string`
|
||
- Émet : `update:modelValue`, `search` (sur Enter ou debounce 300ms)
|
||
|
||
### 3.3 Carte
|
||
|
||
**`NavMap.vue`**
|
||
- Rôle : carte Leaflet avec markers, clustering, synchronisation avec filtres sidebar
|
||
- Props :
|
||
- `orgs: Organisation[]` (fiches filtrées)
|
||
- `territoireActif: string` (Métropole | Guadeloupe | Martinique | Guyane | Réunion | Mayotte)
|
||
- `highlightedIds: string[]` (fiches à mettre en avant, émises par chatbot)
|
||
- `selectedId: string | null`
|
||
- Émet :
|
||
- `selectOrg(id: string)` — clic sur un marker
|
||
- `mapMoved(bounds: LatLngBounds)` — si on veut filtrer par bbox à terme
|
||
- Note : utiliser `leaflet.markercluster` pour le clustering des points proches
|
||
- Note SSR : Leaflet est client-only (`<ClientOnly>` ou plugin avec `process.client`)
|
||
|
||
**`TerritoireToggle.vue`**
|
||
- Rôle : toggle Métropole / Outre-mer avec sous-onglets DOM-TOM si Outre-mer actif
|
||
- Props :
|
||
- `modelValue: string` (territoire actif)
|
||
- `territoires: string[]` (Métropole + 5 DOM-TOM)
|
||
- Émet : `update:modelValue`
|
||
- Note : quand Outre-mer sélectionné, afficher un second rang de sous-onglets (Guadeloupe, Martinique, Guyane, Réunion, Mayotte). La carte recentre automatiquement sur le territoire sélectionné.
|
||
|
||
**`MarkerPopup.vue`**
|
||
- Rôle : popup Leaflet affichée au clic sur un marker (mini-fiche)
|
||
- Props : `org: Organisation`
|
||
- Contient : nom, tags échelle/fonction, lien vers `/fiche/[id]`
|
||
- Note : composant Vue rendu dans le popup Leaflet via `teleport` ou mounting manuel
|
||
|
||
### 3.4 Fiches
|
||
|
||
**`FicheCard.vue`**
|
||
- Rôle : carte de résumé d'une fiche dans la liste sidebar
|
||
- Props :
|
||
- `org: Organisation`
|
||
- `selected: boolean`
|
||
- Émet : `select(id: string)`
|
||
- Contient : `TagBadge` pour échelle et fonctions
|
||
|
||
**`TagBadge.vue`**
|
||
- Rôle : badge visuel pour un tag (échelle ou fonction)
|
||
- Props :
|
||
- `label: string`
|
||
- `type: 'echelle' | 'fonction'`
|
||
- `priority?: number` (rang si fonction prioritaire)
|
||
- `active?: boolean`
|
||
- Note : couleurs distinctes entre les deux types de tags
|
||
|
||
**`FicheHeader.vue`**
|
||
- Rôle : header de la page fiche (nom + tags + lien retour + meta)
|
||
- Props : `org: Organisation`
|
||
|
||
**`FicheDescription.vue`**
|
||
- Rôle : corps de la page fiche (description user + description IA + points clés)
|
||
- Props :
|
||
- `descriptionUser: string`
|
||
- `descriptionIA: string | null`
|
||
- `pointsCles: string[]`
|
||
- `siteUrl: string | null`
|
||
- Note : si `descriptionIA` est null (fiche pas encore traitée), afficher un placeholder "Enrichissement IA en cours..."
|
||
|
||
**`FicheMiniMap.vue`**
|
||
- Rôle : mini-carte Leaflet centrée sur la localisation d'une fiche
|
||
- Props :
|
||
- `lat: number`
|
||
- `lng: number`
|
||
- `nom: string`
|
||
- Note : non interactif (scrollWheelZoom: false, dragging: false), zoom fixe 10
|
||
|
||
**`CommentsList.vue`**
|
||
- Rôle : liste des commentaires validés d'une fiche
|
||
- Props : `comments: Comment[]`
|
||
- Contient : `CommentItem`
|
||
|
||
**`CommentItem.vue`**
|
||
- Rôle : affichage d'un commentaire individuel
|
||
- Props : `comment: Comment` (pseudo, date, texte)
|
||
|
||
**`CommentForm.vue`**
|
||
- Rôle : formulaire d'ajout de commentaire
|
||
- Props : `ficheId: string`
|
||
- Émet : `submitted`
|
||
- Note : affiche un message de confirmation post-soumission avec mention du filtre IA
|
||
|
||
### 3.5 Chatbot
|
||
|
||
**`ChatbotBubble.vue`**
|
||
- Rôle : bubble fixe bottom-right déclenchant l'ouverture du chatbot
|
||
- Props : `open: boolean`
|
||
- Émet : `toggle`
|
||
- Note : icône robot + label "Aide IA" sur desktop, icône seule sur mobile
|
||
|
||
**`ChatbotSheet.vue`**
|
||
- Rôle : bottom-sheet chatbot (mobile) / panneau latéral droit (desktop)
|
||
- Props :
|
||
- `open: boolean`
|
||
- `messages: ChatMessage[]`
|
||
- `loading: boolean`
|
||
- Émet :
|
||
- `close`
|
||
- `sendMessage(text: string)`
|
||
- `highlightOrgs(ids: string[])` — si la réponse IA mentionne des fiches
|
||
- Contient : `ChatOnboarding`, `ChatMessageList`, `ChatInput`
|
||
|
||
**`ChatOnboarding.vue`**
|
||
- Rôle : message initial affiché avant la première interaction
|
||
- Props : aucune
|
||
- Note : texte souveraineté + format de requête recommandé (voir section 6)
|
||
|
||
**`ChatMessageList.vue`**
|
||
- Rôle : liste scrollable des messages échangés
|
||
- Props : `messages: ChatMessage[]`
|
||
- Note : scroll automatique vers le bas à chaque nouveau message
|
||
|
||
**`ChatInput.vue`**
|
||
- Rôle : champ de saisie + bouton envoi
|
||
- Props : `loading: boolean`
|
||
- Émet : `send(text: string)`
|
||
- Note : Enter pour envoyer, Shift+Enter pour saut de ligne
|
||
|
||
### 3.6 Formulaire soumission
|
||
|
||
**`SubmitModal.vue`**
|
||
- Rôle : modale d'ajout d'une fiche (desktop) ou page `/contribuer` (mobile)
|
||
- Props :
|
||
- `open: boolean` (uniquement en mode modale)
|
||
- Émet : `close`, `submitted`
|
||
- Contient : `SubmitForm`
|
||
|
||
**`SubmitForm.vue`**
|
||
- Rôle : formulaire de soumission d'une nouvelle fiche
|
||
- Props : aucune
|
||
- Émet : `submitted(submissionId: string)`
|
||
- Note : validation côté client avant envoi (champs obligatoires) ; appel `/api/submit`
|
||
|
||
**`SubmitSuccess.vue`**
|
||
- Rôle : message de confirmation post-soumission
|
||
- Props : `trackingUrl: string`
|
||
- Note : remplace `SubmitForm` dans la modale après succès
|
||
|
||
### 3.7 Divers
|
||
|
||
**`OrgList.vue`**
|
||
- Rôle : liste des fiches filtrées sous la carte (desktop) ou sous la carte (mobile)
|
||
- Props :
|
||
- `orgs: Organisation[]`
|
||
- `loading: boolean`
|
||
- `selectedId: string | null`
|
||
- Émet : `select(id: string)`
|
||
- Contient : `FicheCard`
|
||
|
||
---
|
||
|
||
## 4. States & data flow
|
||
|
||
### 4.1 Architecture du store
|
||
|
||
Trois stores Pinia :
|
||
|
||
```
|
||
useFiltersStore → état des filtres sidebar
|
||
useOrgsStore → données fiches (fetch NocoDB + réactivité)
|
||
useChatbotStore → état chatbot + messages
|
||
```
|
||
|
||
**`useFiltersStore`** :
|
||
```typescript
|
||
{
|
||
searchText: string,
|
||
echelle: string | null, // National | Régional | Départemental | Local | null
|
||
fonctions: string[], // tableau ordonné (ordre de clic = priorité)
|
||
territoire: string, // Métropole | Guadeloupe | ...
|
||
}
|
||
```
|
||
|
||
**`useOrgsStore`** :
|
||
```typescript
|
||
{
|
||
orgs: Organisation[], // toutes les fiches validées
|
||
filteredOrgs: Organisation[], // calculé (computed) via getters
|
||
selectedId: string | null,
|
||
loading: boolean,
|
||
highlightedIds: string[], // fiches mises en avant par le chatbot
|
||
}
|
||
```
|
||
|
||
**`useChatbotStore`** :
|
||
```typescript
|
||
{
|
||
open: boolean,
|
||
messages: ChatMessage[],
|
||
loading: boolean,
|
||
}
|
||
```
|
||
|
||
### 4.2 URL comme source de vérité pour les filtres
|
||
|
||
Les filtres actifs sont reflétés dans l'URL via query params. Cela permet :
|
||
- de partager un lien filtré (ex : `/?echelle=Regional&fonctions=Juridique,Technique`)
|
||
- de conserver les filtres au rechargement de page
|
||
- de naviguer avec le bouton "retour" du navigateur
|
||
|
||
Mapping :
|
||
```
|
||
?q= → searchText
|
||
?echelle= → echelle (National | Regional | Departemental | Local)
|
||
?fonctions= → fonctions séparées par virgule, ordre = priorité
|
||
?territoire= → territoire actif
|
||
```
|
||
|
||
Implémentation : `useRoute` + `useRouter` de Nuxt dans `useFiltersStore`, watchers bidirectionnels store ↔ URL.
|
||
|
||
### 4.3 Interaction filtres → carte
|
||
|
||
```
|
||
Sidebar (EchelleFilter | FonctionFilter | SearchInput)
|
||
↓ update:modelValue
|
||
useFiltersStore.update(filters)
|
||
↓ watch (computed)
|
||
useOrgsStore.filteredOrgs (getter réactif)
|
||
↓ prop :orgs
|
||
NavMap.vue → re-render markers
|
||
OrgList.vue → re-render liste
|
||
```
|
||
|
||
Le filtrage est côté client (toutes les fiches sont chargées au démarrage). Pour des volumes > 500 fiches, envisager un filtrage serveur via `/api/search`.
|
||
|
||
### 4.4 Chatbot → highlight carte
|
||
|
||
Quand la réponse du chatbot mentionne des organisations (détection par ID ou nom), l'API `/api/search` renvoie une liste d'IDs. Le chatbot store propage ces IDs :
|
||
|
||
```
|
||
ChatbotSheet émet highlightOrgs(ids)
|
||
↓
|
||
useChatbotStore → useOrgsStore.setHighlightedIds(ids)
|
||
↓ prop :highlightedIds
|
||
NavMap.vue → markers concernés passent en surbrillance (icône colorée différente)
|
||
OrgList.vue → fiches concernées remontées en haut de liste
|
||
```
|
||
|
||
### 4.5 Sélection d'une fiche
|
||
|
||
```
|
||
Clic marker (NavMap) ──┐
|
||
Clic FicheCard (OrgList) ─┤→ useOrgsStore.setSelected(id)
|
||
│
|
||
↓
|
||
MarkerPopup visible + FicheCard en surbrillance
|
||
+ bouton "Voir la fiche" → navigate('/fiche/[id]')
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Responsive breakpoints
|
||
|
||
| Breakpoint | Taille | Comportement |
|
||
|------------|--------|--------------|
|
||
| Mobile | < 768px | Sidebar en drawer (hors-canvas), carte plein écran (~60vh), liste fiches sous la carte, chatbot en bottom-sheet plein largeur, bandeau bas réduit (1 ligne) |
|
||
| Tablet | 768–1024px | Sidebar réduite (220px), carte occupe le reste, chatbot en bottom-sheet 80% largeur centrée, même layout que desktop mais compressé |
|
||
| Desktop | > 1024px | Sidebar fixe 320px, carte prend tout l'espace restant, chatbot en panneau latéral droit 400px (remonte depuis le bas), bandeau bas 3 colonnes |
|
||
|
||
### Grille Tailwind utilisée
|
||
|
||
```css
|
||
/* Mobile first */
|
||
.sidebar { @apply hidden } /* < 768 : drawer */
|
||
.map { @apply h-[60vh] w-full }
|
||
.org-list { @apply w-full }
|
||
|
||
/* Tablet */
|
||
@media (min-width: 768px) {
|
||
.sidebar { @apply block w-[220px] flex-shrink-0 }
|
||
.map { @apply flex-1 h-full }
|
||
}
|
||
|
||
/* Desktop */
|
||
@media (min-width: 1024px) {
|
||
.sidebar { @apply w-[320px] }
|
||
}
|
||
```
|
||
|
||
### Hauteur disponible
|
||
|
||
Le layout global est `100dvh` (dynamic viewport height) pour gérer les barres mobiles. Structure :
|
||
```
|
||
AppHeader (48px) + [Sidebar | Carte + Liste] (flex-1) + BandeauBas (48px)
|
||
```
|
||
La carte et la liste scrollent indépendamment dans leur conteneur (overflow-y: auto).
|
||
|
||
---
|
||
|
||
## 6. Détails chatbot (bottom-sheet)
|
||
|
||
### États
|
||
|
||
```
|
||
fermé → ChatbotBubble visible en bas à droite (fixed, z-50)
|
||
icône robot + label "Aide IA" (desktop) ou icône seule (mobile)
|
||
|
||
ouvert → ChatbotSheet monte à 70% de la hauteur écran (mobile)
|
||
ou panneau latéral droit 400px (desktop)
|
||
ChatbotBubble disparaît (remplacé par le bouton ✕ dans le sheet)
|
||
|
||
chargement → spinner dans ChatInput pendant la requête Mistral Small
|
||
```
|
||
|
||
### Animation
|
||
|
||
```css
|
||
/* Mobile : slide-up depuis le bas */
|
||
.chatbot-sheet {
|
||
transition: transform 300ms ease-out;
|
||
}
|
||
.chatbot-sheet[data-open="false"] {
|
||
transform: translateY(100%);
|
||
}
|
||
.chatbot-sheet[data-open="true"] {
|
||
transform: translateY(0);
|
||
}
|
||
```
|
||
|
||
Respecter `prefers-reduced-motion` : si activé, supprimer la transition (affichage/masquage instantané).
|
||
|
||
### Drag handle mobile
|
||
|
||
Le sheet mobile expose un drag handle en haut (barre grise 40px de large, 4px de haut). Le glisser vers le bas ferme le sheet si le déplacement dépasse 30% de la hauteur du sheet.
|
||
|
||
### Message d'onboarding
|
||
|
||
Affiché avant la première question, disparaît après envoi du premier message :
|
||
|
||
```
|
||
Ce chatbot fonctionne sur un serveur européen souverain
|
||
(Mistral FR, zéro rétention), conçu sobre en énergie.
|
||
|
||
Pour m'aider à te répondre efficacement,
|
||
formule ta requête ainsi :
|
||
|
||
• Besoin : [ce que tu cherches]
|
||
• Thématique : [juridique / technique / économique / ...]
|
||
• Lieu : [région ou ville]
|
||
|
||
Exemple : "Je suis salarié d'agence, litige avec mon
|
||
employeur, besoin conseil juridique droit du travail,
|
||
Île-de-France."
|
||
```
|
||
|
||
### Prompt système envoyé à Mistral Small (`/api/search`)
|
||
|
||
Le prompt système doit :
|
||
1. Présenter le contexte (base de ressources pour architectes FR)
|
||
2. Injecter les fiches filtrées correspondant à la requête (retrieved via Nemo)
|
||
3. Demander de répondre en citant les organisations par ID ou nom exact
|
||
4. Indiquer de ne pas inventer d'organisations absentes de la base
|
||
|
||
La réponse de l'API inclut les IDs des organisations mentionnées dans un champ structuré (JSON) pour alimenter le highlight de la carte.
|
||
|
||
### Stratégie deux modèles
|
||
|
||
- **Mistral Nemo** (filtre/worker) : enrichissement de fiches post-soumission, filtre éthique commentaires → coût $0,02/$0,04 par 1M tokens
|
||
- **Mistral Small** (chatbot) : réponses conversationnelles → coût $0,20/$0,60 par 1M tokens
|
||
|
||
---
|
||
|
||
## 7. Bandeau bas : données affichées
|
||
|
||
### Table NocoDB à créer : `stats_usage`
|
||
|
||
| Colonne | Type | Description |
|
||
|---------|------|-------------|
|
||
| `id` | AutoNumber | clé primaire |
|
||
| `period_start` | DateTime | début de la période (mois ou semaine) |
|
||
| `period_type` | SingleLineText | `monthly` ou `weekly` |
|
||
| `tokens_input` | Number | tokens d'entrée consommés |
|
||
| `tokens_output` | Number | tokens de sortie consommés |
|
||
| `cost_eur` | Decimal | coût estimé en euros |
|
||
| `kwh_estimated` | Decimal | consommation estimée kWh (calcul : ~0,001 kWh / 1000 tokens, à affiner) |
|
||
| `chatbot_requests` | Number | nombre de requêtes chatbot |
|
||
| `new_fiches` | Number | nouvelles fiches validées |
|
||
| `new_comments` | Number | nouveaux commentaires validés |
|
||
| `donations_count` | Number | nombre de dons reçus ce mois (Liberapay webhook si disponible) |
|
||
|
||
### Calcul kWh et carbone
|
||
|
||
Formule approximative (à afficher avec mention "estimé") :
|
||
```
|
||
kWh = (tokens_input + tokens_output) / 1 000 000 × 0,5 kWh/1M tokens
|
||
(Mistral Nemo sur infrastructure FR, estimation conservative)
|
||
|
||
CO₂e = kWh × 40 gCO₂e/kWh (mix électrique France, données RTE 2024)
|
||
```
|
||
|
||
### Mise à jour des stats
|
||
|
||
Chaque appel à `/api/search`, `/api/submit`, `/api/comment` incrémente la table `stats_usage` via un helper serveur. La route `/api/stats` retourne l'agrégat du mois courant et de la semaine courante.
|
||
|
||
### Affichage desktop (3 colonnes)
|
||
|
||
```
|
||
[Gauche] [Milieu] [Droite]
|
||
Ce mois-ci : Cette semaine :
|
||
0,12 € consommés [♥ Soutenir NAV] 3 nouvelles fiches
|
||
8 423 tokens (→ Liberapay) 12 req. chatbot
|
||
~0,004 kWh · ~0,16 gCO₂e 2 commentaires
|
||
```
|
||
|
||
### Affichage mobile (1 ligne compressée)
|
||
|
||
```
|
||
0,12€ · 8K tok [♥ Soutenir] 3 fiches · 12 req
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Page fiche détaillée (`/fiche/[id]`)
|
||
|
||
### Données chargées (SSR)
|
||
|
||
La page est rendue en SSR (Nuxt `useFetch` côté serveur) pour le SEO et le partage.
|
||
|
||
Données récupérées depuis NocoDB :
|
||
```typescript
|
||
interface Organisation {
|
||
id: string
|
||
nom: string
|
||
url?: string
|
||
description_user: string
|
||
description_ia?: string // null si pas encore traité
|
||
points_cles?: string[] // array JSON
|
||
echelle: string // National | Régional | Départemental | Local
|
||
fonctions: string[] // tableau ordonné
|
||
territoire: string[] // Métropole + DOM-TOM
|
||
ville: string
|
||
lat?: number
|
||
lng?: number
|
||
statut: 'pending' | 'enrichi' | 'valide' | 'rejete'
|
||
created_at: string
|
||
}
|
||
```
|
||
|
||
### SEO
|
||
|
||
Balises `useHead` dans la page `/fiche/[id].vue` :
|
||
```typescript
|
||
useHead({
|
||
title: `${org.nom} — NAV, Navigateur Architecture`,
|
||
meta: [
|
||
{ name: 'description', content: org.description_user.slice(0, 160) },
|
||
{ property: 'og:title', content: org.nom },
|
||
{ property: 'og:description', content: org.description_user.slice(0, 160) },
|
||
{ property: 'og:url', content: `https://nav.trans-former.fr/fiche/${org.id}` },
|
||
{ property: 'og:type', content: 'article' },
|
||
]
|
||
})
|
||
```
|
||
|
||
### Fil d'Ariane / retour carte
|
||
|
||
Le bouton "← Retour" utilise `router.back()` si l'utilisateur vient de la carte, sinon `navigateTo('/')`. Détecter via `document.referrer` ou un query param `?from=carte`.
|
||
|
||
### Commentaires
|
||
|
||
Les commentaires sont chargés côté client (pas SSR) pour ne pas bloquer le rendu initial.
|
||
|
||
Filtre éthique avant publication :
|
||
1. POST `/api/comment` avec le texte
|
||
2. Appel Mistral Nemo : vérifier absence de contenu offensant, spam, non-pertinent
|
||
3. Si OK → insert NocoDB avec `statut: 'pending'` (Jules valide manuellement dans un premier temps)
|
||
4. Si KO → message d'erreur explicatif côté client
|
||
|
||
Message de confirmation post-commentaire :
|
||
```
|
||
Merci pour ton commentaire. Il sera publié après validation
|
||
(généralement sous 48h).
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Formulaire "Ajouter une fiche"
|
||
|
||
### Champs et validation
|
||
|
||
| Champ | Type | Requis | Validation |
|
||
|-------|------|--------|------------|
|
||
| `nom` | text | oui | min 3 chars, max 150 chars |
|
||
| `url` | url | non | format URL valide si renseigné |
|
||
| `description_user` | textarea | oui | min 20 chars, max 200 chars |
|
||
| `echelle` | radio | oui | une des 4 valeurs |
|
||
| `fonctions` | checkbox | oui | 1 à 5 sélectionnées |
|
||
| `territoire` | checkbox | oui | au moins 1 |
|
||
| `ville` | text | oui | min 2 chars (géocodage côté serveur) |
|
||
| `email` | email | non | format email valide si renseigné |
|
||
|
||
### Flow post-submit
|
||
|
||
```
|
||
SubmitForm → POST /api/submit
|
||
↓
|
||
Server : geocode ville → NocoDB insert (statut: 'pending')
|
||
↓
|
||
Server : trigger worker async → scraping url + enrichissement Mistral Nemo
|
||
↓
|
||
Réponse : { submissionId, trackingUrl }
|
||
↓
|
||
SubmitForm → SubmitSuccess (afficher trackingUrl)
|
||
```
|
||
|
||
### Géocodage
|
||
|
||
L'API `/api/geocode` appelle **Nominatim** (OpenStreetMap, gratuit, usage raisonnable) :
|
||
```
|
||
GET https://nominatim.openstreetmap.org/search?q={ville}&format=json&limit=1
|
||
```
|
||
Header User-Agent obligatoire : `NAV/2.0 contact@trans-former.fr`
|
||
|
||
Fallback : si géocodage échoue, la fiche est stockée sans coordonnées et Jules complète manuellement dans NocoDB.
|
||
|
||
### Worker d'enrichissement IA
|
||
|
||
Processus asynchrone déclenché après insert NocoDB :
|
||
1. Scraping URL avec `node-fetch` (ou `cheerio` pour extraction propre)
|
||
2. Prompt Mistral Nemo :
|
||
```
|
||
Tu enrichis une fiche d'une ressource pour architectes FR.
|
||
Contenu scraped : {contenu}
|
||
Description courte saisie : {description_user}
|
||
|
||
Produis :
|
||
1. Description enrichie (max 400 caractères, neutre, factuelle)
|
||
2. Points clés : liste de 3 à 5 items (chacun < 80 caractères)
|
||
|
||
Format JSON :
|
||
{ "description_ia": "...", "points_cles": ["...", "..."] }
|
||
```
|
||
3. Update NocoDB : `description_ia`, `points_cles`, `statut: 'enrichi'`
|
||
4. Jules valide dans NocoDB → `statut: 'valide'`
|
||
|
||
---
|
||
|
||
## 10. Accessibilité & sobriété
|
||
|
||
### Accessibilité
|
||
|
||
**Contraste AA minimum :**
|
||
- Tous les textes > 14px : ratio 4,5:1 minimum
|
||
- Textes larges (> 18px bold) : ratio 3:1 minimum
|
||
- Les tags colorés (TagBadge) : vérifier le contraste texte/fond avec un outil (ex. Colour Contrast Analyser)
|
||
|
||
**Navigation clavier :**
|
||
- Tous les éléments interactifs ont un `tabindex` naturel (pas de `tabindex > 0`)
|
||
- Focus visible sur tous les éléments (ne pas supprimer `outline`, styliser avec `ring` Tailwind)
|
||
- Le drawer sidebar (mobile) et la modale SubmitModal implémentent le **focus trap** : Tab reste dans le composant ouvert
|
||
- Fermeture des modales et drawers sur `Escape`
|
||
- Le chatbot est accessible au clavier : Tab pour naviguer dans les messages, Enter pour envoyer
|
||
|
||
**Semantic HTML :**
|
||
- `<nav>` pour AppHeader et NavSidebar
|
||
- `<main>` pour le contenu principal
|
||
- `<aside>` pour la sidebar
|
||
- `<section>` pour les grandes zones de la page fiche
|
||
- `<h1>` unique par page, hiérarchie `h2/h3` cohérente
|
||
|
||
**ARIA :**
|
||
- `aria-label` sur les boutons icône (ChatbotBubble, boutons fermeture modales)
|
||
- `aria-expanded` sur les filtres accordéon (mobile)
|
||
- `aria-live="polite"` sur la zone de résultats (nombre de fiches) et les messages chatbot
|
||
- `role="dialog"` + `aria-modal="true"` sur SubmitModal et DrawerSidebar
|
||
|
||
### Sobriété numérique
|
||
|
||
**Polices :**
|
||
- Utiliser les system fonts : `font-family: system-ui, -apple-system, sans-serif`
|
||
- Pas de Google Fonts, pas de chargement de police externe
|
||
- Si une police spécifique est requise : self-host avec `font-display: swap`
|
||
|
||
**Images :**
|
||
- Pas d'image décorative hero
|
||
- Si logos d'organisations ajoutés à terme : WebP, lazy loading (`loading="lazy"`), dimensions explicites pour éviter le CLS
|
||
- Pas d'icônes SVG externes : utiliser une bibliothèque locale (Heroicons via `@heroicons/vue` ou SVG inline)
|
||
|
||
**JavaScript :**
|
||
- Leaflet est lourd (~150KB gzippé) : importer uniquement côté client (`<ClientOnly>` ou `import('leaflet')` dynamique)
|
||
- Pas de bibliothèques d'animation lourdes (GSAP, etc.) — transitions CSS suffisent
|
||
|
||
**`prefers-reduced-motion` :**
|
||
```css
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.chatbot-sheet,
|
||
.drawer-sidebar,
|
||
.submit-modal {
|
||
transition: none;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Lazy loading des composants lourds :**
|
||
- `NavMap.vue` : importé dynamiquement (`defineAsyncComponent`) — ne bloque pas le First Contentful Paint
|
||
- `ChatbotSheet.vue` : importé dynamiquement, monté uniquement au premier clic sur la bubble
|
||
|
||
**Cache des données :**
|
||
- Les fiches sont chargées une fois au mount de la page d'accueil, mises en cache dans `useOrgsStore`
|
||
- Revalidation : au rechargement de page ou après soumission d'une nouvelle fiche
|
||
- Pas de polling — les stats du bandeau se rechargent au mount (pas en temps réel)
|
||
|
||
**Tuiles cartographiques :**
|
||
- Utiliser **OpenStreetMap** (Raster Tiles) via le CDN OpenStreetMap, avec attribution obligatoire
|
||
- Envisager à terme un tile server auto-hébergé (Hetzner) si volumes importants — pas nécessaire au lancement
|
||
|
||
---
|
||
|
||
## Types TypeScript partagés
|
||
|
||
À définir dans `types/nav.ts` (utilisé par composants et API routes) :
|
||
|
||
```typescript
|
||
interface Organisation {
|
||
id: string
|
||
nom: string
|
||
url?: string
|
||
description_user: string
|
||
description_ia?: string
|
||
points_cles?: string[]
|
||
echelle: 'National' | 'Régional' | 'Départemental' | 'Local'
|
||
fonctions: string[]
|
||
territoire: string[]
|
||
ville: string
|
||
lat?: number
|
||
lng?: number
|
||
statut: 'pending' | 'enrichi' | 'valide' | 'rejete'
|
||
created_at: string
|
||
}
|
||
|
||
interface Filters {
|
||
searchText: string
|
||
echelle: Organisation['echelle'] | null
|
||
fonctions: string[]
|
||
territoire: string
|
||
}
|
||
|
||
interface ChatMessage {
|
||
id: string
|
||
role: 'user' | 'assistant'
|
||
content: string
|
||
timestamp: number
|
||
highlightedOrgIds?: string[]
|
||
}
|
||
|
||
interface Comment {
|
||
id: string
|
||
ficheId: string
|
||
pseudo?: string
|
||
texte: string
|
||
created_at: string
|
||
statut: 'pending' | 'valide' | 'rejete'
|
||
}
|
||
|
||
interface StatsUsage {
|
||
period_type: 'monthly' | 'weekly'
|
||
cost_eur: number
|
||
tokens_total: number
|
||
kwh_estimated: number
|
||
co2_g: number
|
||
chatbot_requests: number
|
||
new_fiches: number
|
||
new_comments: number
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Dépendances Nuxt à installer
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"leaflet": "^1.9.x",
|
||
"leaflet.markercluster": "^1.5.x",
|
||
"@pinia/nuxt": "^0.5.x",
|
||
"@heroicons/vue": "^2.x",
|
||
"nuxt": "^3.x"
|
||
},
|
||
"devDependencies": {
|
||
"@types/leaflet": "^1.9.x"
|
||
}
|
||
}
|
||
```
|
||
|
||
Note : `@mistral-ai/client` n'est **pas** une dépendance front-end — les appels Mistral se font uniquement dans les server routes Nuxt (`server/api/`), jamais côté client.
|
||
|
||
---
|
||
|
||
## Ce qui n'est pas dans cette spec (à traiter séparément)
|
||
|
||
- Authentification / espace modération Jules (NocoDB admin ou interface dédiée)
|
||
- Système de notifications email (soumission reçue, fiche validée)
|
||
- Webhooks Liberapay pour mettre à jour `donations_count` en temps réel
|
||
- Internationalisation (i18n) — non nécessaire au lancement
|
||
- Tests end-to-end (Playwright) — à prévoir pour les parcours critiques (soumission, chatbot)
|
||
- Monitoring et alertes (Sentry, UptimeRobot) — infrastructure, pas front-end
|
||
- Migration des 94 fiches V1 vers le schéma V2 (opération de base de données)
|