Files
siop2/apps/web/src/pages/ascenseurs.tsx
pr-daaif 9778629827 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>
2026-07-16 13:06:45 +01:00

98 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ASSET_STATUS_LABELS, ASSET_STATUSES, type AssetStatus } from '@siop/shared';
import { useAssets } from '@/api/referentiel';
import { usePermissions } from '@/auth/use-permissions';
import { StatutEquipement } from '@/components/statut-equipement';
import { Button } from '@/components/ui/button';
/** Écran 3 des maquettes R1 : inventaire dense du parc. */
export default function PageAscenseurs() {
const navigate = useNavigate();
const { data: assets } = useAssets();
const { can } = usePermissions();
const [site, setSite] = useState('');
const [statut, setStatut] = useState<'' | AssetStatus>('');
const tous = assets ?? [];
const sites = [...new Set(tous.map((a) => a.siteName))].sort();
const visibles = tous.filter(
(a) => (!site || a.siteName === site) && (!statut || a.status === statut),
);
const arret = tous.filter((a) => a.status === 'OUT_OF_SERVICE').length;
return (
<>
<div className="entete-page">
<h1>Ascenseurs</h1>
<span className="filajout">
{tous.length} appareils{arret ? ` · ${arret} à larrêt` : ''}
</span>
<div className="actions">
{can('ASSETS', 'create') ? (
<Button variant="prim" onClick={() => navigate('/ascenseurs/nouveau')}>
+ Nouvel ascenseur
</Button>
) : null}
</div>
</div>
<div className="filtres">
<select aria-label="Filtrer par site" value={site} onChange={(e) => setSite(e.target.value)}>
<option value="">Site : tous</option>
{sites.map((s) => (
<option key={s} value={s}>{s}</option>
))}
</select>
<select
aria-label="Filtrer par statut"
value={statut}
onChange={(e) => setStatut(e.target.value as '' | AssetStatus)}
>
<option value="">Statut : tous</option>
{ASSET_STATUSES.map((s) => (
<option key={s} value={s}>{ASSET_STATUS_LABELS[s]}</option>
))}
</select>
</div>
<div className="carte" style={{ padding: 0 }}>
<div className="table">
<table>
<thead>
<tr>
<th>Appareil</th>
<th>Marque · modèle</th>
<th>Site / emplacement</th>
<th>Catégorie</th>
<th>Statut</th>
<th>Organes</th>
</tr>
</thead>
<tbody>
{visibles.map((a) => (
<tr
key={a.id}
className={a.status === 'OUT_OF_SERVICE' ? 'ligne-bloque' : undefined}
onClick={() => navigate(`/ascenseurs/${a.id}`)}
style={{ cursor: 'pointer' }}
>
<td className="ref num">{a.reference}</td>
<td>{a.brand} {a.model ?? ''}</td>
<td>
{a.siteName}
{a.locationName !== a.siteName ? (
<span className="sous">{a.locationName}</span>
) : null}
</td>
<td>{a.categoryName}</td>
<td><StatutEquipement statut={a.status} /></td>
<td className="num">{a.componentCount}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</>
);
}