feat(r1.2): écrans web du référentiel — 7 écrans fidèles à maquette-r1

- Sites : liste + carte Leaflet/OSM réelle (pins maquette, positions
  PostGIS), création en modale avec position posée au clic sur la carte
- Fiche site : arbre site→zones, appareils rattachés, ajout de zone
- Ascenseurs : table dense, filtres site/statut, strie rouge à l'arrêt ;
  fiche appareil (organes ± ajout/retrait, statut, QR réel) ; création
  identité → rattachement → organes ; étiquette A6 imprimable (objet
  papier, window.print n'imprime qu'elle)
- Personnes & équipes : invitation → lien d'activation affiché à copier
  (pas d'email en R1, décision explicite), renvoi de lien, équipes ;
  page /activation (choix du mot de passe, connexion directe)
- Catégories : ajout, renommage inline, désactivation
- navigation et actions pilotées par la matrice (usePermissions) —
  l'API re-vérifie chaque requête
- e2e Playwright : recette R1 officielle rejouée intégralement (site →
  zone → appareil+organe → étiquette → invitation → activation) +
  parcours « la matrice pilote l'UI » ; purge idempotente en globalSetup
- alias Vite @siop/shared → source TS (exports nommés CJS ↯ workspace lié)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-16 13:06:45 +01:00
parent 266ffaaf1b
commit 9778629827
29 changed files with 2350 additions and 35 deletions

21
apps/web/src/lib/lieux.ts Normal file
View File

@@ -0,0 +1,21 @@
import type { LocationDto } from '@siop/shared';
export interface SiteAvecZones extends LocationDto {
zones: LocationDto[];
}
/** L'API renvoie une liste plate ; l'arbre (site → zones) se construit ici. */
export function construireArbre(locations: LocationDto[]): SiteAvecZones[] {
const sites = locations
.filter((l) => l.parentId === null)
.map((site) => ({
...site,
zones: locations.filter((l) => l.parentId === site.id),
}));
return sites.sort((a, b) => b.assetCount - a.assetCount || a.name.localeCompare(b.name));
}
export function formatDateFr(iso: string | null): string {
if (!iso) return '—';
return new Intl.DateTimeFormat('fr-FR', { dateStyle: 'long' }).format(new Date(iso));
}