diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..be4ee7c --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +VITE_API_URL=API_ADRESS +VITE_APP_URL=APP_ADDRESS diff --git a/.gitignore b/.gitignore index 7f37a56..62243e6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ node_modules dist dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/docs/guides/README.md b/docs/guides/README.md new file mode 100644 index 0000000..9822589 --- /dev/null +++ b/docs/guides/README.md @@ -0,0 +1,28 @@ +# Documentation modules + +Ce dossier contient la documentation technique par domaine fonctionnel. + +## Utilisation de la doc + +- **Onboarding nouveau dev** : commencer par `docs/guides/onboarding.md` +- **Reference technique** : utiliser les fichiers de `docs/modules/` et `docs/technical/` selon le perimetre modifie + +## Index + +- `docs/modules/front-office.md` : parcours public, recherche, resultats, fiche organisme +- `docs/modules/back-office-edition.md` : edition des organismes (CRUD, archivage, filtres) +- `docs/modules/back-office-users.md` : gestion des utilisateurs et droits +- `docs/technical/auth.md` : authentification, session, securite client +- `docs/technical/zones.md` : fonctionnement des zones et impact metier +- `docs/technical/api.md` : organisation des appels API et conventions +- `docs/technical/vigilance.md` : liste centralisee des points a surveiller/corriger + +## Convention de mise a jour + +Pour chaque PR, mettre a jour les fichiers impactes: + +1. Objectif / perimetre +2. Routes et composants touches +3. Flux de donnees / API +4. Regles de droits +5. Cas limites et points de vigilance diff --git a/docs/guides/onboarding.md b/docs/guides/onboarding.md new file mode 100644 index 0000000..279df6e --- /dev/null +++ b/docs/guides/onboarding.md @@ -0,0 +1,54 @@ +# Onboarding developpeur + +Ce guide est le point d'entree rapide pour contribuer a la webapp. + +## 1) Contexte du projet + +- Frontend: ce repo (`web_app`) +- Backend: Directus (`https://github.com/Watizat/directus`) +- Le frontend depend du backend pour fonctionner (auth, donnees, edition, roles, zones). + +## 2) Setup local + +```bash +npm install +npm run dev +``` + +Puis ouvrir `http://localhost:5173`. + +## 3) Parcours de lecture conseille (30 min) + +1. `docs/specifications.md` (vue d'ensemble) +2. `docs/modules/front-office.md` +3. `docs/modules/back-office-edition.md` +4. `docs/modules/back-office-users.md` +5. `docs/technical/auth.md` +6. `docs/technical/zones.md` +7. `docs/technical/api.md` +8. `docs/technical/vigilance.md` + +## 4) Fichiers source a connaitre + +- `src/router.tsx` : routes front/back +- `src/context/AppStateContext.tsx` : state global +- `src/components/App/FrontOffice.tsx` : shell front +- `src/components/App/BackOffice.tsx` : shell back + guard auth +- `src/utils/axios.ts` : auth bearer + refresh + +## 5) Regles de contribution doc + +A chaque PR, mettre a jour au moins un fichier dans `docs/modules/` ou `docs/technical/` si le comportement change. + +## 6) Verification minimale avant PR + +```bash +npm run lint +npm run build +``` + +Si un flux metier est touche, verifier manuellement: + +- login/logout +- parcours recherche front (zone + categorie) +- ecran admin impacte diff --git a/docs/modules/README.md b/docs/modules/README.md new file mode 100644 index 0000000..76d4ee5 --- /dev/null +++ b/docs/modules/README.md @@ -0,0 +1,9 @@ +# Modules fonctionnels + +Ce dossier contient uniquement les modules metier du produit. + +- `docs/modules/front-office.md` +- `docs/modules/back-office-edition.md` +- `docs/modules/back-office-users.md` + +Les sujets transversaux (auth, zones, API) sont dans `docs/technical/`. diff --git a/docs/modules/back-office-edition.md b/docs/modules/back-office-edition.md new file mode 100644 index 0000000..3b7a32e --- /dev/null +++ b/docs/modules/back-office-edition.md @@ -0,0 +1,42 @@ +# Module Back Office - Edition + +## Objectif + +Gerer le referentiel d'organismes: creation, edition, gestion des services/contacts, archivage. + +## Routes concernees + +- `/admin/edition` + +## Composants / fichiers cles + +- `src/components/BackOffice/Edition/Edition.tsx` +- `src/components/BackOffice/Edition/SideList.tsx` +- `src/components/BackOffice/Edition/DataPanel/*` +- `src/components/BackOffice/SlideOvers/Edition/*` +- `src/components/Modals/ArchiveOrganism.tsx` +- `src/context/BackOfficeContext.tsx` +- `src/api/admin.ts` +- `src/api/crud.ts` + +## Flux principal + +1. Chargement de la liste des organismes selon la zone active (`fetchAdminOrganisms`). +2. Selection d'un organisme -> chargement detail (`fetchAdminOrganism`). +3. Edition via slide-over (general, infos, services, contacts, visibilite). +4. Archivage/desarchivage via `editOrganismVisibility`. +5. Rafraichissement liste/detail apres mutation. + +## Etat UI local (BackOfficeContext) + +- `isOpenSlideNewOrga` +- `isOpenFiltersOrga` +- `isDisplayArchivedOrga` + +## Regles metier principales + +- La liste est filtree par zone et par visibilite. +- Un organisme archive est `visible = false` avec message `visible_comment` possible. +- Creation/edition geolocalise l'adresse via `api-adresse.data.gouv.fr`. + +Points de vigilance de ce module: `docs/technical/vigilance.md`. diff --git a/docs/modules/back-office-users.md b/docs/modules/back-office-users.md new file mode 100644 index 0000000..d913aa6 --- /dev/null +++ b/docs/modules/back-office-users.md @@ -0,0 +1,56 @@ +# Module Back Office - Utilisateurs + +## Objectif + +Gerer les comptes membres, leur role, leur zone de rattachement et leur cycle de vie. + +## Routes concernees + +- `/admin/users` +- `/admin/profil` +- `/account-request` (entree creation de compte) + +## Composants / fichiers cles + +- `src/components/BackOffice/Users/Users.tsx` +- `src/components/BackOffice/Users/UserLine.tsx` +- `src/components/BackOffice/SlideOvers/Users/EditUser.tsx` +- `src/components/BackOffice/Profil.tsx` +- `src/components/BackOffice/SlideOvers/Profil/EditProfil.tsx` +- `src/api/admin.ts` (`fetchUsers`, `fetchRoles`) +- `src/api/user.ts` (`registerUser`, `editUser`, `updateUserStatus`, `fetchMe`) +- `src/components/BackOffice/Sidebar/SideBase.tsx` +- `src/components/BackOffice/Dashboard/Dashboard.tsx` + +## Roles observes et droits frontend + +- `Administrator` +- `RefLocal` +- `Edition` +- `NewUser` +- `UserToDelete` + +## Matrice des droits (frontend) + +| Action | Administrator | RefLocal | Edition | NewUser | UserToDelete | +|---|---|---|---|---|---| +| Acceder au back-office (`/admin/*`) | Oui | Oui | Oui | Non | Non | +| Acceder a `/admin/users` | Oui | Oui | Non | Non | Non | +| Changer la zone active dans le header back-office | Oui | Oui | Non | Non | Non | +| Voir l'entree "Back-end" dans la navigation | Oui | Non | Non | Non | Non | +| Modifier un utilisateur (nom/email/role/zone) | Oui | Oui | Non | Non | Non | +| Attribuer le role `Administrator` depuis l'UI users | Oui | Oui* | Non | Non | Non | +| Archiver / reactiver un utilisateur | Oui | Oui | Non | Non | Non | + +\* Cote frontend actuel, `RefLocal` a acces a la meme UI de role/zone qu'`Administrator`. Les droits effectifs finaux dependent des regles backend Directus. + +## Flux principal gestion users + +1. Recuperation du role courant via `/users/me`. +2. Chargement users selon zone: + - admin/reflocal: zone active (ou toutes si pas de zone selectionnee) + - autres: zone du user connecte +3. Edition d'un user via slide-over (`editUser`). +4. Changement de statut via `updateUserStatus` (`active`, `suspended`, `archived`). + +Points de vigilance de ce module: `docs/technical/vigilance.md`. diff --git a/docs/modules/front-office.md b/docs/modules/front-office.md new file mode 100644 index 0000000..d8f6b72 --- /dev/null +++ b/docs/modules/front-office.md @@ -0,0 +1,42 @@ +# Module Front Office + +## Objectif + +Permettre la consultation publique du guide: recherche d'organismes, consultation des resultats et fiche detail. + +## Routes concernees + +- `/` (home et formulaire de recherche) +- `/resultats` +- `/organisme/:slug` +- `/mentions-legales` +- `/guides-papier` + +## Composants / fichiers cles + +- `src/components/App/FrontOffice.tsx` +- `src/components/FrontOffice/Home/SearchBox.tsx` +- `src/components/FrontOffice/Resultats/Resultats.tsx` +- `src/components/FrontOffice/Organisme/Organisme.tsx` +- `src/api/organisms.ts` + +## Flux principal + +1. Chargement initial des categories, zones et jours (`FrontOffice.tsx`). +2. L'utilisateur choisit zone + categorie sur la home. +3. Navigation vers `/resultats?city=...&category=...`. +4. `Resultats.tsx` charge: + - la position de la zone (`fetchCityPosition`) + - la liste des organismes (`fetchOrganisms`) +5. Clic sur un organisme -> `organisme/:slug` puis `fetchOrganism`. + +## Donnees d'etat cle + +- `organismState.categories` +- `organismState.organisms` +- `organismState.filteredOrganisms` +- `organismState.organism` +- `organismState.days` +- `adminState.zones` + +Points de vigilance de ce module: `docs/technical/vigilance.md`. diff --git a/docs/specifications.md b/docs/specifications.md index de49291..5210975 100644 --- a/docs/specifications.md +++ b/docs/specifications.md @@ -176,3 +176,9 @@ Rien à faire par ici *Newsletter* : Non *Réseaux sociaux* : Forte présence et forte communauté (Facebook, Instagram, Twitter) + +### Variables d'environnement (frontend) + +Le frontend necessite la variable suivante pour fonctionner : + +- `VITE_API_URL` : URL de l'API Directus (exemple dans `.env.example`). diff --git a/docs/technical/README.md b/docs/technical/README.md new file mode 100644 index 0000000..a95380f --- /dev/null +++ b/docs/technical/README.md @@ -0,0 +1,10 @@ +# Documentation technique transversale + +Ce dossier contient les sujets techniques qui traversent plusieurs modules. + +- `docs/technical/auth.md` +- `docs/technical/zones.md` +- `docs/technical/api.md` +- `docs/technical/vigilance.md` + +Les modules metier sont dans `docs/modules/`. diff --git a/docs/technical/api.md b/docs/technical/api.md new file mode 100644 index 0000000..c2f64b5 --- /dev/null +++ b/docs/technical/api.md @@ -0,0 +1,65 @@ +# Technique - API + +## Objectif + +Centraliser les conventions d'appel API et la cartographie des principaux endpoints utilises par le frontend. + +## Organisation des fichiers + +- `src/utils/axios.ts` : instance Axios + interceptors auth/refresh +- `src/api/user.ts` : auth et users +- `src/api/admin.ts` : zones, roles, listes admin +- `src/api/organisms.ts` : consultation front des organismes +- `src/api/crud.ts` : mutations organisme/service/contact +- `src/api/navitia.ts` : transport (si active) + +## Base URL et auth + +- Base URL: `https://api.watizat.app` +- Bearer token injecte automatiquement si session presente +- Refresh token via `POST /auth/refresh` +- En cas d'echec refresh: purge session + redirection login + +## Endpoints principaux utilises + +### Auth / user + +- `POST /auth/login` +- `POST /auth/logout` +- `POST /auth/password/request` +- `GET /users/me` +- `POST /users` +- `PATCH /users/:id` + +### Zones / roles / users admin + +- `GET /items/zone` +- `GET /roles` +- `GET /users` (filtre par zone) + +### Organismes / contenu guide + +- `GET /items/organisme` (liste/front + liste/admin + detail) +- `PATCH /items/organisme/:id` +- `POST /items/organisme` +- `POST/PATCH/DELETE` sur: + - `/items/service` + - `/items/service_translation` + - `/items/contact` + - `/items/schedule` + +### Service externe + +- Geocodage adresse: `https://api-adresse.data.gouv.fr/search/?q=...` +- Releases app (modal versions): `https://api.github.com/repos/Watizat/web_app/releases` + +## Caching local existant + +- `fetchZones`: cache memoire (`zonesCache`, `zonesPromise`) +- `fetchMe`: cache memoire (`meCache`, `mePromise`) + +## Cas limites / vigilance + +- Les champs `fields` Directus sont tres verbeux et repetes; risque de divergence entre endpoints. +- Quelques champs semblent typo/incomplets (`services.categorie_id.translations.`). +- Harmoniser les filtres de zone entre front/back pour eviter des comportements differents. diff --git a/docs/technical/auth.md b/docs/technical/auth.md new file mode 100644 index 0000000..0556969 --- /dev/null +++ b/docs/technical/auth.md @@ -0,0 +1,45 @@ +# Technique - Auth + +## Objectif + +Gerer l'authentification des membres, la persistance de session et la protection du back office. + +## Routes concernees + +- `/login` +- `/account-request` +- `/forgotten-password` +- `/recover-password` +- `/new-user` +- toutes les routes `/admin/*` (acces conditionnel) + +## Composants / fichiers cles + +- `src/components/FrontOffice/Login/SignIn.tsx` +- `src/components/FrontOffice/Login/AccountRequest.tsx` +- `src/components/FrontOffice/Login/NewUser.tsx` +- `src/components/App/BackOffice.tsx` +- `src/components/InactivityDetector/InactivityDetector.tsx` +- `src/utils/axios.ts` +- `src/api/user.ts` +- `src/utils/user.ts` + +## Flux principal + +1. Login via `POST /auth/login` (`login` dans `src/api/user.ts`). +2. Token stocke dans `localStorage.user`. +3. `BackOffice.tsx` appelle `/users/me` pour valider l'acces. +4. L'intercepteur Axios ajoute le bearer et gere le refresh (`POST /auth/refresh`). +5. Si refresh impossible: purge session + redirection `/login`. + +## Regles de droits appliquees a l'entree back-office + +- Role `NewUser` : acces refuse, deconnexion, retour login. +- Role `UserToDelete` : acces refuse, deconnexion, retour login. +- Roles valides pour le back-office: `Administrator`, `RefLocal`, `Edition` (avec menus differents). + +## Cas limites / vigilance + +- Le code melange noms de roles et UUID selon les composants. +- La suppression de compte utilisateur est un passage en statut (`suspended`/`archived`) selon contexte. +- L'inactivite deconnecte la session apres timeout (5h par defaut cote state). diff --git a/docs/technical/vigilance.md b/docs/technical/vigilance.md new file mode 100644 index 0000000..3415282 --- /dev/null +++ b/docs/technical/vigilance.md @@ -0,0 +1,20 @@ +# Technique - Points de vigilance + +Ce document centralise les points de vigilance/corrections a suivre. + +## Front Office + +- `fetchOrganism` force actuellement `zone_id.name = Toulouse` dans le filtre API. +- La zone est memorisee dans `localStorage.city` et reutilisee par la carte/resultats. +- Les categories/translations reposent sur l'index `[0]` de `translations` dans plusieurs composants. + +## Back Office - Edition + +- Plusieurs refreshs de liste sont dupliques selon composants. +- Le flag `isDisplayArchivedOrga` impacte les requetes et le rendu, verifier coherence lors d'evolutions. + +## Back Office - Utilisateurs + +- Le code utilise a la fois UUID de roles et noms de roles. +- Les menus dashboard/sidebar filtrent par `roleName`; garder la meme table de droits partout. +- Certaines operations de suppression sont en pratique des changements de statut. diff --git a/docs/technical/zones.md b/docs/technical/zones.md new file mode 100644 index 0000000..e54397c --- /dev/null +++ b/docs/technical/zones.md @@ -0,0 +1,38 @@ +# Technique - Zones + +## Objectif + +Documenter le role des zones (antennes/territoires) dans la navigation, le filtrage et les droits. + +## Donnees et API + +- Collection Directus: `zone` +- Chargement via `fetchZones` (`src/api/admin.ts`) +- Position d'une zone via `fetchCityPosition` (`src/api/organisms.ts`) + +## Usages front office + +- Select de recherche sur la home (`SearchBox.tsx`) +- Filtre des organismes en resultats (`fetchOrganisms`) +- Centrage carte resultats (lat/lng de la zone) +- Persistance de la zone dans `localStorage.city` + +## Usages back office + +- Filtre des organismes dans Edition selon zone active +- Filtre des utilisateurs dans Users selon zone active / zone user +- Zone editable sur: + - creation organisme + - edition utilisateur (si droits suffisants) + - profil (lecture) + +## Regles de droits + +- `Administrator` / `RefLocal`: peuvent changer la zone active dans le header back-office. +- `Edition`: zone de travail en pratique contrainte a son rattachement. + +## Cas limites / vigilance + +- Plusieurs composants lisent la zone depuis `localStorage.city`; garantir une valeur coherente. +- Le filtre resultats utilise `city` en query params et localStorage en parallele. +- Penser a invalider le cache zones (`clearZonesCache`) si le referentiel zone evolue a chaud. diff --git a/package.json b/package.json index ab81118..fcd9ce9 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,7 @@ "react-markdown": "^9.0.0", "react-progressive-graceful-image": "^0.7.0", "react-router": "^6.14.2", - "react-router-dom": "^6.14.1", - "vite": "^4.3.9" + "react-router-dom": "^6.14.1" }, "devDependencies": { "@types/leaflet": "^1.9.3", @@ -50,6 +49,6 @@ "prettier": "^2.8.8", "tailwindcss": "^3.3.5", "typescript": "^5.1.3", - "vite": "^4.3.9" + "vite": "^7.3.1" } } diff --git a/src/api/admin.ts b/src/api/admin.ts index a5a62c3..c1ef35f 100644 --- a/src/api/admin.ts +++ b/src/api/admin.ts @@ -39,7 +39,17 @@ export const fetchAdminOrganisms = async ({ export const fetchUsers = async (zone: string | null) => { const { data } = await axiosInstance.get<{ data: DirectusUser[] }>('/users', { params: { - fields: ['*'].join(','), + fields: [ + 'id', + 'first_name', + 'last_name', + 'email', + 'role.id', + 'role.name', + 'zone', + 'last_access', + 'status', + ].join(','), filter: { zone, }, diff --git a/src/components/BackOffice/Dashboard/Button.tsx b/src/components/BackOffice/Dashboard/Button.tsx index ba9cf43..19996de 100644 --- a/src/components/BackOffice/Dashboard/Button.tsx +++ b/src/components/BackOffice/Dashboard/Button.tsx @@ -25,69 +25,46 @@ export default function Button({ item }: Props) { setMe(meData); } catch (error) { // eslint-disable-next-line no-console - console.error( - "Erreur lors de la récupération des données de l'utilisateur : ", - error - ); + console.error("Erreur lors de la recuperation des donnees de l'utilisateur : ", error); setMe(null); } } getUserInfos(); }, []); + const roleName = typeof me?.role === 'string' ? me.role : me?.role?.name; + const canAccessPrivilegedItems = roleName === 'RefLocal' || roleName === 'Administrator'; + const isDisabled = item.active === false || ((item.refLocalOnly || item.devOnly) && !canAccessPrivilegedItems); + return (
-
+
- +

{item.name}

-
-
Description
-
- {item.descript} -
+
+
Description
+
{item.descript}
diff --git a/src/components/BackOffice/Dashboard/Dashboard.tsx b/src/components/BackOffice/Dashboard/Dashboard.tsx index f84c87f..a081d9f 100644 --- a/src/components/BackOffice/Dashboard/Dashboard.tsx +++ b/src/components/BackOffice/Dashboard/Dashboard.tsx @@ -10,6 +10,8 @@ import Button from './Button'; import BackColor from '../../Container/BackColor'; import { useAppState } from '../../../hooks/appState'; +const BACKEND_URL = import.meta.env.VITE_API_URL; + const navigation = [ { name: 'Edition', @@ -58,7 +60,7 @@ const navigation = [ }, { name: 'Back-end', - href: 'https://api.watizat.app', + href: BACKEND_URL, descript: 'Back-end (Directus)', target: '_blank', icon: CircleStackIcon, diff --git a/src/components/BackOffice/Sidebar/LinkLarge.tsx b/src/components/BackOffice/Sidebar/LinkLarge.tsx index 7b65f79..7286b4d 100644 --- a/src/components/BackOffice/Sidebar/LinkLarge.tsx +++ b/src/components/BackOffice/Sidebar/LinkLarge.tsx @@ -26,27 +26,24 @@ export default function LinkLarge({ item }: Props) { setMe(meData); } catch (error) { // eslint-disable-next-line no-console - console.error( - "Erreur lors de la récupération des données de l'utilisateur : ", - error - ); + console.error("Erreur lors de la recuperation des donnees de l'utilisateur : ", error); setMe(null); } } getUserInfos(); }, []); + const roleName = typeof me?.role === 'string' ? me.role : me?.role?.name; + const canAccessPrivilegedItems = roleName === 'RefLocal' || roleName === 'Administrator'; + const isDisabled = item.active === false || ((item.refLocalOnly || item.devOnly) && !canAccessPrivilegedItems); + return (
  • ); diff --git a/src/components/BackOffice/SlideOvers/Users/EditUser.tsx b/src/components/BackOffice/SlideOvers/Users/EditUser.tsx index a179c09..6de393c 100644 --- a/src/components/BackOffice/SlideOvers/Users/EditUser.tsx +++ b/src/components/BackOffice/SlideOvers/Users/EditUser.tsx @@ -1,12 +1,10 @@ -import jwt_decode from 'jwt-decode'; import { useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { Inputs } from '../../../../@types/formInputs'; -import { DirectusUser, UserSession } from '../../../../@types/user'; +import { DirectusUser } from '../../../../@types/user'; import { useAppState } from '../../../../hooks/appState'; import { fetchUsers } from '../../../../api/admin'; import { editUser, updateUserStatus, fetchMe } from '../../../../api/user'; -import { getUserDataFromLocalStorage } from '../../../../utils/user'; import { validateEmail } from '../../../../utils/form/form'; import Slide from '../components/Slide'; import Header from '../components/Header'; @@ -50,32 +48,18 @@ export default function SlideEditUser({ const cityId = cityLocal ? zones.find((zone) => zone.name === cityLocal) : zones.find((zone) => zone.name === city); - const localUser = getUserDataFromLocalStorage(); const me = await fetchMe(); - const { zone } = me; - - if (!localUser?.token) { - return; - } - try { - const decodedUser = jwt_decode( - localUser.token.access_token - ) as UserSession; - if (decodedUser.role === '53de6ec2-6d70-48c8-8532-61f96133f139') { - if (cityId !== undefined) { - const usersList = await fetchUsers(cityId.id.toString()); - setAdminState((prev) => ({ ...prev, users: usersList })); - } else { - const usersList = await fetchUsers(null); - setAdminState((prev) => ({ ...prev, users: usersList })); - } - } else { - const usersList = await fetchUsers(zone.toString()); - setAdminState((prev) => ({ ...prev, users: usersList })); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error while decoding JWT or dispatching actions:', error); + if (isAdmin) { + if (cityId !== undefined) { + const usersList = await fetchUsers(cityId.id.toString()); + setAdminState((prev) => ({ ...prev, users: usersList })); + } else { + const usersList = await fetchUsers(null); + setAdminState((prev) => ({ ...prev, users: usersList })); + } + } else if (me?.zone) { + const usersList = await fetchUsers(me.zone.toString()); + setAdminState((prev) => ({ ...prev, users: usersList })); } setIsOpenSlide(false); }; @@ -189,9 +173,7 @@ export default function SlideEditUser({ {!isAdmin && roles .filter( - (filteredRole) => - filteredRole.id !== - '53de6ec2-6d70-48c8-8532-61f96133f139' + (filteredRole) => filteredRole.name !== 'Administrator' ) .map((role) => (