-> Refonte du design de sélection d'afficheurs -> Ajout du Mode PiP -> Patch du bug du logo de l'horaire pas affiché. Corrigé pour les Horaires TER à 6 numéros.
202 lines
7.9 KiB
JavaScript
202 lines
7.9 KiB
JavaScript
import React, { useEffect, useState, useMemo } from 'react';
|
||
import { WcsCard, WcsCardBody, WcsButton, WcsInput, WcsBadge, WcsHorizontalStepper } from 'wcs-react';
|
||
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' : '';
|
||
|
||
const [allGares, setAllGares] = useState([]);
|
||
const [sncfResults, setSncfResults] = useState([]);
|
||
const [loading, setLoading] = useState(source === 'supabase');
|
||
const [error, setError] = useState(null);
|
||
const [search, setSearch] = useState('');
|
||
|
||
// Pour Supabase : charger les gares une seule fois au montage
|
||
useEffect(() => {
|
||
let mounted = true;
|
||
if (source === 'supabase') {
|
||
async function loadSupabaseGares() {
|
||
setLoading(true);
|
||
setError(null);
|
||
try {
|
||
const data = await searchGares('', 'supabase');
|
||
if (mounted) setAllGares(Array.isArray(data) ? data.map(r => r.nom || r) : []);
|
||
} catch (err) {
|
||
console.error('Supabase error:', err);
|
||
if (mounted) setError(err.message || String(err));
|
||
} finally {
|
||
if (mounted) setLoading(false);
|
||
}
|
||
}
|
||
loadSupabaseGares();
|
||
}
|
||
return () => { mounted = false; };
|
||
}, [source]);
|
||
|
||
// Pour SNCF : recherche dynamique avec debounce dès 2 caractères
|
||
useEffect(() => {
|
||
let active = true;
|
||
if (source === 'sncf') {
|
||
const q = search.trim();
|
||
if (q.length < 2) {
|
||
setSncfResults([]);
|
||
return;
|
||
}
|
||
const timer = setTimeout(async () => {
|
||
try {
|
||
const results = await searchGares(q, 'sncf');
|
||
if (active) setSncfResults(results);
|
||
} catch (e) {
|
||
if (active) setSncfResults([]);
|
||
}
|
||
}, 180);
|
||
|
||
return () => {
|
||
active = false;
|
||
clearTimeout(timer);
|
||
};
|
||
}
|
||
}, [search, source]);
|
||
|
||
// Liste finale affichée
|
||
const displayedGares = useMemo(() => {
|
||
if (source === 'supabase') {
|
||
const q = search.toLowerCase().trim();
|
||
return allGares.filter(nom => String(nom).toLowerCase().includes(q)).map(nom => ({ nom }));
|
||
}
|
||
return sncfResults;
|
||
}, [source, allGares, sncfResults, search]);
|
||
|
||
const handleSelectGare = (nom) => {
|
||
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||
if (navigate) {
|
||
const cleanNom = String(nom).trim().replace(/\s+/g, '-');
|
||
navigate(`${prefix}/afficheurs/${encodeURIComponent(cleanNom)}?mode=departs`);
|
||
}
|
||
};
|
||
|
||
const steps = [
|
||
{ text: 'Choix de la gare', button: { kind: 'Text', text: '1' }, complete: false },
|
||
{ text: "Type d'afficheur", button: { kind: 'Text', text: '2' }, disable: true },
|
||
{ text: 'Options & Affichage', button: { kind: 'Text', text: '3' }, disable: true }
|
||
];
|
||
|
||
return (
|
||
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '24px', overflowY: 'auto', maxHeight: 'calc(100vh - 120px)' }}>
|
||
<WcsHorizontalStepper steps={steps} currentStep={0} style={{ marginBottom: '24px' }} />
|
||
|
||
<WcsCard mode="raised">
|
||
<WcsCardBody>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||
<div>
|
||
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>🚆 Prochains Départs</h1>
|
||
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>Recherchez une gare pour afficher le tableau des départs en temps réel.</p>
|
||
</div>
|
||
<WcsBadge className={source === 'supabase' ? 'wcs-secondary' : 'wcs-primary'} shape="rounded">
|
||
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS Live'}
|
||
</WcsBadge>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '24px' }}>
|
||
<WcsInput
|
||
type="text"
|
||
icon="search"
|
||
placeholder="Rechercher une gare (ex: Dijon, Paris, Lyon, Besançon...)"
|
||
value={search}
|
||
onWcsInput={(e) => setSearch(e.target.value)}
|
||
style={{ width: '100%' }}
|
||
clearable={true}
|
||
/>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '24px' }}>
|
||
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Gares fréquentes :</p>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||
{POPULAR_GARES.map((gName) => (
|
||
<WcsButton
|
||
key={gName}
|
||
mode="stroked"
|
||
shape="round"
|
||
size="s"
|
||
onClick={() => handleSelectGare(gName)}
|
||
>
|
||
{gName.replace(/-/g, ' ')}
|
||
</WcsButton>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{loading && <p style={{ color: 'var(--wcs-primary)', fontWeight: 600 }}>⏳ Chargement des gares...</p>}
|
||
{error && <p style={{ color: 'var(--wcs-danger)', fontWeight: 600 }}>⚠️ Erreur : {error}</p>}
|
||
|
||
{source === 'sncf' && search.trim().length < 2 && (
|
||
<div style={{ textAlign: 'center', padding: '24px 16px', color: 'var(--wcs-text-light)', background: 'var(--wcs-light)', borderRadius: '8px', border: '1px dashed var(--wcs-gray)' }}>
|
||
<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>
|
||
)}
|
||
|
||
{source === 'sncf' && search.trim().length >= 2 && displayedGares.length === 0 && (
|
||
<div style={{ textAlign: 'center', padding: '24px', color: 'var(--wcs-text-light)', background: 'var(--wcs-white)', borderRadius: '8px' }}>
|
||
Aucune gare trouvée pour « <strong>{search}</strong> ».
|
||
</div>
|
||
)}
|
||
|
||
{displayedGares.length > 0 && (
|
||
<div role="list" style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '24px' }}>
|
||
{displayedGares.map((g) => {
|
||
const nom = g.nom;
|
||
return (
|
||
<WcsCard
|
||
key={g.uic || nom}
|
||
mode="flat"
|
||
style={{ cursor: 'pointer', border: '1px solid var(--wcs-light)' }}
|
||
onClick={() => handleSelectGare(nom)}
|
||
>
|
||
<WcsCardBody style={{ padding: '12px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||
<div>
|
||
<div style={{ fontWeight: 'bold', fontSize: '16px', color: 'var(--wcs-primary)' }}>{nom}</div>
|
||
{g.commune && (
|
||
<div style={{ fontSize: '13px', color: 'var(--wcs-text-light)' }}>
|
||
{g.commune} {g.departement ? `(${g.departement})` : ''}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||
{g.uic && <WcsBadge>{g.uic}</WcsBadge>}
|
||
<span style={{ color: 'var(--wcs-primary)', fontSize: '20px', fontWeight: 'bold' }}>›</span>
|
||
</div>
|
||
</WcsCardBody>
|
||
</WcsCard>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
|
||
<div style={{ borderTop: '1px solid var(--wcs-light)', paddingTop: '16px' }}>
|
||
<WcsButton mode="clear" onClick={onBack}>
|
||
← Retour à l'accueil
|
||
</WcsButton>
|
||
</div>
|
||
</WcsCardBody>
|
||
</WcsCard>
|
||
</div>
|
||
);
|
||
}
|