Désactivation des annonces sonores et refonte responsive mobile/tablette des pages de guidage WCS

This commit is contained in:
mathis committed 2026-08-30 22:07:34 +02:00
1 parent 4f3a34b101
commit 983f4f0160
9 files changed
+1315 -842

No files matched your search

+141 -91
View File
@@ -1,7 +1,21 @@
import React, { useEffect, useState, useMemo } from 'react';
import WcsButton from '../components/WcsButton';
import sessionMemory from '../lib/sessionMemory';
import { getSourceFromPath, searchGares } from '../services/railwayService';
const POPULAR_GARES = [
'Paris-Gare-de-Lyon',
'Besançon-Viotte',
'Dijon-Ville',
'Lyon-Part-Dieu',
'Marseille-Saint-Charles',
'Lille-Flandres',
'Strasbourg',
'Bordeaux-Saint-Jean',
'Nantes',
'Rennes'
];
export default function Departs({ onBack, navigate }) {
const source = getSourceFromPath(window.location.pathname);
const prefix = source === 'supabase' ? '/ferrovia' : '';
@@ -10,7 +24,6 @@ export default function Departs({ onBack, navigate }) {
const [sncfResults, setSncfResults] = useState([]);
const [loading, setLoading] = useState(source === 'supabase');
const [error, setError] = useState(null);
const [selected, setSelected] = useState('');
const [search, setSearch] = useState('');
// Pour Supabase : charger les gares une seule fois au montage
@@ -51,7 +64,7 @@ export default function Departs({ onBack, navigate }) {
} catch (e) {
if (active) setSncfResults([]);
}
}, 200);
}, 180);
return () => {
active = false;
@@ -70,109 +83,146 @@ export default function Departs({ onBack, navigate }) {
}, [source, allGares, sncfResults, search]);
const handleSelectGare = (nom) => {
setSelected(nom);
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
if (navigate) {
navigate(`${prefix}/afficheurs/${encodeURIComponent(nom)}?mode=departs`);
const cleanNom = String(nom).trim().replace(/\s+/g, '-');
navigate(`${prefix}/afficheurs/${encodeURIComponent(cleanNom)}?mode=departs`);
}
};
return (
<div style={{ padding: 20 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
<h1 style={{ margin: 0 }}>Départs</h1>
<span style={{
fontSize: '12px',
fontWeight: '600',
padding: '4px 10px',
borderRadius: '12px',
background: source === 'supabase' ? '#e6f4ea' : '#e8f0fe',
color: source === 'supabase' ? '#137333' : '#1a73e8'
}}>
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF (GTFS)'}
</span>
<div className="launcher-container">
{/* Stepper */}
<div className="launcher-stepper">
<div className="launcher-step launcher-step--active">
<span className="launcher-step-num">1</span>
<span>Choix de la gare</span>
</div>
<span className="launcher-step-separator">›</span>
<div className="launcher-step">
<span className="launcher-step-num">2</span>
<span>Type d'afficheur</span>
</div>
<span className="launcher-step-separator">›</span>
<div className="launcher-step">
<span className="launcher-step-num">3</span>
<span>Options & Affichage</span>
</div>
</div>
{loading && <p>Chargement des gares...</p>}
{error && <p style={{ color: 'red' }}>Erreur : {error}</p>}
{!loading && !error && (
<div>
{/* Barre de recherche */}
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 16 }}>
<input
type="search"
placeholder={source === 'sncf' ? "Rechercher une gare SNCF (ex: Dijon, Paris, Lyon...)" : "Rechercher une gare..."}
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{ width: '60%', padding: '10px 12px', borderRadius: 6, border: '1px solid #ccc' }}
aria-label="Rechercher une gare"
autoFocus
/>
<div className="launcher-card">
{/* Header */}
<div className="launcher-header">
<div>
<h1 className="launcher-title">🚆 Prochains Départs</h1>
<p className="launcher-subtitle">Recherchez une gare pour afficher le tableau des départs en temps réel.</p>
</div>
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS Live'}
</span>
</div>
{/* Invitation à la recherche en mode SNCF */}
{source === 'sncf' && search.trim().length < 2 && (
<div style={{ textAlign: 'center', padding: '30px 16px', color: '#666', background: '#fff', borderRadius: '8px', border: '1px dashed #ccc' }}>
<p style={{ margin: 0, fontSize: '15px' }}>
🔍 <strong>Saisissez au moins 2 caractères</strong> pour rechercher parmi les gares du référentiel GTFS SNCF.
</p>
</div>
)}
{/* Aucun résultat */}
{source === 'sncf' && search.trim().length >= 2 && displayedGares.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px', color: '#777' }}>
Aucune gare SNCF trouvée pour "{search}".
</div>
)}
{/* Résultats */}
{displayedGares.length > 0 && (
<div role="list" aria-label="Résultats des gares" style={{ background: '#fff', borderRadius: '8px', padding: '8px 16px', boxShadow: '0 1px 4px rgba(0,0,0,0.06)' }}>
{displayedGares.map((g) => {
const nom = g.nom;
return (
<div key={g.uic || nom} role="listitem">
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', cursor: 'pointer' }}
onClick={() => handleSelectGare(nom)}
onKeyDown={(e) => (e.key === 'Enter' ? handleSelectGare(nom) : null)}
tabIndex={0}
>
<div>
<strong style={{ fontSize: '15px' }}>{nom}</strong>
{g.commune && <span style={{ marginLeft: '8px', color: '#666', fontSize: '13px' }}>({g.commune} - {g.departement})</span>}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{g.uic && <span style={{ fontSize: '11px', color: '#888', background: '#f1f3f4', padding: '2px 6px', borderRadius: '4px' }}>UIC {g.uic}</span>}
{selected === nom && <div style={{ color: '#0070f3' }}>Sélectionnée</div>}
</div>
</div>
<div style={{ height: 1, background: '#eef1f6', margin: '2px 0' }} />
</div>
);
})}
</div>
)}
{selected && (
<div style={{ marginTop: 16 }}>
<p>Gare sélectionnée : <strong>{selected}</strong></p>
<button onClick={() => {
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
if (navigate) {
navigate(`${prefix}/afficheurs/${encodeURIComponent(selected)}?mode=departs`);
}
}}>Voir les départs</button>
</div>
{/* Barre de recherche */}
<div className="launcher-search-box">
<input
type="search"
placeholder="Rechercher une gare (ex: Dijon, Paris, Lyon, Besançon...)"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="launcher-search-input"
aria-label="Rechercher une gare"
autoFocus
/>
{search && (
<button
type="button"
className="launcher-search-clear"
onClick={() => setSearch('')}
aria-label="Effacer la recherche"
>
✕
</button>
)}
</div>
)}
<div style={{ marginTop: 20 }}>
<button onClick={onBack}>Retour</button>
{/* Suggestions rapides / Gares populaires */}
<div className="launcher-chips-container">
<span className="launcher-chips-label">Gares fréquentes :</span>
<div className="launcher-chips-list">
{POPULAR_GARES.map((gName) => (
<button
key={gName}
type="button"
className="launcher-chip"
onClick={() => handleSelectGare(gName)}
>
{gName.replace(/-/g, ' ')}
</button>
))}
</div>
</div>
{loading && <p style={{ color: '#00439e', fontWeight: 600 }}>⏳ Chargement des gares...</p>}
{error && <p style={{ color: '#d70022', fontWeight: 600 }}>⚠️ Erreur : {error}</p>}
{/* Invitation à la recherche en mode SNCF */}
{source === 'sncf' && search.trim().length < 2 && (
<div style={{ textAlign: 'center', padding: '24px 16px', color: '#666', background: '#f8fafc', borderRadius: '8px', border: '1px dashed #d0d7de' }}>
<p style={{ margin: 0, fontSize: '14px' }}>
💡 Saisissez au moins <strong>2 lettres</strong> pour chercher parmi plus de 3 500 gares du réseau français.
</p>
</div>
)}
{/* Aucun résultat */}
{source === 'sncf' && search.trim().length >= 2 && displayedGares.length === 0 && (
<div style={{ textAlign: 'center', padding: '24px', color: '#777', background: '#fff', borderRadius: '8px' }}>
Aucune gare trouvée pour « <strong>{search}</strong> ».
</div>
)}
{/* Liste des résultats */}
{displayedGares.length > 0 && (
<div className="launcher-results-list" role="list">
{displayedGares.map((g) => {
const nom = g.nom;
return (
<div
key={g.uic || nom}
className="launcher-result-item"
onClick={() => handleSelectGare(nom)}
onKeyDown={(e) => (e.key === 'Enter' ? handleSelectGare(nom) : null)}
tabIndex={0}
role="listitem"
>
<div>
<div className="launcher-result-name">{nom}</div>
{g.commune && (
<div className="launcher-result-sub">
{g.commune} {g.departement ? `(${g.departement})` : ''}
</div>
)}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{g.uic && <span className="launcher-result-tag">UIC {g.uic}</span>}
<span style={{ color: '#00439e', fontSize: '18px', fontWeight: 'bold' }}>›</span>
</div>
</div>
);
})}
</div>
)}
{/* Navigation retour */}
<div className="launcher-footer-nav">
<WcsButton
mode="plain"
onClick={onBack}
label="← Retour à l'accueil"
/>
</div>
</div>
</div>
);
}