import { useQuery } from '@tanstack/react-query'; import { useState, type FormEvent } from 'react'; import { useParams } from 'react-router-dom'; import type { PortalRequestStatus } from '@siop/shared'; import { api } from '@/api/client'; import { Button } from '@/components/ui/button'; import { IcoAscenseurs } from '@/components/nav-icons'; /** * Écran 6 validé R0 : Karim (gardien, aucune formation, aucun compte) arrive * ici en scannant le QR. Équipement prérempli, UN choix critique, suivi sans * jargon. Ses signalements vivent sur SON téléphone (référence + jeton). */ const CLE_SUIVIS = 'siop.portail.suivis'; interface Suivi { reference: string; token: string; } const lireSuivis = (): Suivi[] => { try { return JSON.parse(localStorage.getItem(CLE_SUIVIS) ?? '[]') as Suivi[]; } catch { return []; } }; const ajouterSuivi = (s: Suivi) => localStorage.setItem(CLE_SUIVIS, JSON.stringify([s, ...lireSuivis()].slice(0, 10))); export default function PagePortail() { const { reference } = useParams<{ reference: string }>(); const [suivis, setSuivis] = useState(lireSuivis); const [personneBloquee, setPersonneBloquee] = useState(false); const [envoye, setEnvoye] = useState(false); const [erreur, setErreur] = useState(null); const { data: asset, isError: refInconnue } = useQuery({ queryKey: ['portal-asset', reference], retry: false, queryFn: async () => { const { data, response } = await api.GET('/portal/assets/{reference}', { params: { path: { reference: reference! } }, }); if (!data) throw new Error(String(response.status)); return data; }, }); const surEnvoi = async (e: FormEvent) => { e.preventDefault(); setErreur(null); const form = e.currentTarget; const description = String(new FormData(form).get('description') ?? '').trim(); const { data, response } = await api.POST('/portal/requests', { body: { assetReference: reference!, description, isPersonTrapped: personneBloquee, }, }); if (!data) { setErreur( response.status === 429 ? 'Trop de signalements envoyés — réessayez dans une minute.' : 'Envoi impossible — réessayez ou appelez le numéro affiché en cabine.', ); return; } ajouterSuivi({ reference: data.reference, token: data.publicToken }); setSuivis(lireSuivis()); setPersonneBloquee(false); setEnvoye(true); form.reset(); }; return (
SIOP Signalement{asset ? ` — ${asset.siteName}` : ''}
{refInconnue ? (

Ce QR ne correspond à aucun appareil connu. Vérifiez l'étiquette ou appelez le numéro affiché en cabine.

) : (
Ascenseur {reference}{asset ? ` — ${asset.locationName}` : ''}{' '} (détecté par le QR)
Une personne est-elle bloquée dans la cabine ?
📷 Ajouter une photo (bientôt disponible)
{erreur ?

{erreur}

: null} {envoye ? (

✓ Signalement envoyé — l'équipe SPELEV est prévenue.

) : null}
)} {suivis.length > 0 ? (

Mes signalements

{suivis.map((s) => ( ))}
) : null}
); } function LigneSuivi({ suivi }: { suivi: Suivi }) { const { data } = useQuery({ queryKey: ['portal-status', suivi.reference], refetchInterval: 30_000, retry: false, queryFn: async () => { const { data } = await api.GET('/portal/requests/{reference}/{token}', { params: { path: { reference: suivi.reference, token: suivi.token } }, }); return data ?? null; }, }); if (!data) return null; return (
{data.description} {data.reference} ·{' '} {new Intl.DateTimeFormat('fr-FR', { dateStyle: 'medium' }).format(new Date(data.createdAt))} {data.status === 'REJECTED' ? (
Sans suite : {data.rejectionReason ?? 'voir avec le gestionnaire'}
) : ( )}
); } function Etapes({ status }: { status: PortalRequestStatus }) { const franchi = (etape: 'RECEIVED' | 'IN_PROGRESS' | 'RESOLVED') => { const ordre = ['RECEIVED', 'IN_PROGRESS', 'RESOLVED']; return ordre.indexOf(status.step) >= ordre.indexOf(etape); }; return (
✓ Reçu {franchi('IN_PROGRESS') ? '✓ ' : ''}Intervention {franchi('RESOLVED') ? '✓ ' : ''}Résolu
); }