-> REFONTE TOTALE DE TOUS LES AFFICHEURS CLASSIQUES + QUAIS FONCTIONNALITÉS EN COURS DE DÉVELOPPEMENT : -> ADAPTABILITÉ EN FONCTION DES SITUATIONS
527 lines
22 KiB
JavaScript
527 lines
22 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
||
import WcsButton from '../components/WcsButton';
|
||
import { getSourceFromPath, getPlatformsForStation } from '../services/railwayService';
|
||
|
||
export default function SelectAfficheur({ onBack, navigate }) {
|
||
const source = getSourceFromPath(window.location.pathname);
|
||
const prefix = source === 'supabase' ? '/ferrovia' : '';
|
||
|
||
const params = new URLSearchParams(window.location.search || '');
|
||
const gareParam = params.get('gare') || '';
|
||
const modeParam = params.get('mode') || 'departs';
|
||
const typeParam = params.get('type') || 'afl';
|
||
|
||
const [platforms, setPlatforms] = useState([]);
|
||
const [selectedPlatform, setSelectedPlatform] = useState('');
|
||
const [manualPlatform, setManualPlatform] = useState('');
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
// État des pages sélectionnées pour les afficheurs AFL
|
||
const [aflPages, setAflPages] = useState({
|
||
horaires: true,
|
||
infos: false,
|
||
securite1: false,
|
||
securite2: false,
|
||
corresp: false
|
||
});
|
||
|
||
// Options spécifiques pour les afficheurs Classiques
|
||
const [extendedLines, setExtendedLines] = useState(0); // 0, 1, 2, 3
|
||
const [showTime, setShowTime] = useState(false);
|
||
|
||
const toggleAflPage = (key) => {
|
||
setAflPages(prev => ({ ...prev, [key]: !prev[key] }));
|
||
};
|
||
|
||
const selectAllAflPages = (all) => {
|
||
setAflPages({
|
||
horaires: true,
|
||
infos: all,
|
||
securite1: all,
|
||
securite2: all,
|
||
corresp: all
|
||
});
|
||
};
|
||
|
||
// Decode and format station name for display
|
||
const decodedGare = gareParam
|
||
? decodeURIComponent(String(gareParam).replace(/\+/g, ' ')).replace(/-/g, ' ')
|
||
: '';
|
||
|
||
useEffect(() => {
|
||
let mounted = true;
|
||
async function loadPlatforms() {
|
||
try {
|
||
setLoading(true);
|
||
const sortedPlatforms = await getPlatformsForStation(decodedGare || gareParam, source);
|
||
if (!mounted) return;
|
||
|
||
setPlatforms(sortedPlatforms || []);
|
||
if (sortedPlatforms && sortedPlatforms.length > 0) {
|
||
setSelectedPlatform(sortedPlatforms[0]);
|
||
}
|
||
} catch (err) {
|
||
console.error('Error fetching platforms:', err);
|
||
} finally {
|
||
if (mounted) setLoading(false);
|
||
}
|
||
}
|
||
|
||
loadPlatforms();
|
||
return () => {
|
||
mounted = false;
|
||
};
|
||
}, [gareParam, decodedGare, source]);
|
||
|
||
const handleGeneral = () => {
|
||
if (typeof navigate === 'function') {
|
||
const kebab = gareParam ? String(gareParam).trim() : '';
|
||
let url = `${prefix}/afficheurs/${modeParam}/${typeParam}?gare=${encodeURIComponent(kebab)}`;
|
||
|
||
if (typeParam === 'afl') {
|
||
const activeKeys = Object.entries(aflPages)
|
||
.filter(([_, active]) => active)
|
||
.map(([key]) => key);
|
||
|
||
if (activeKeys.length > 0) {
|
||
url += `&pages=${activeKeys.join(',')}`;
|
||
}
|
||
} else if (typeParam === 'classiques') {
|
||
if (extendedLines > 0) {
|
||
url += `&extended=${extendedLines}`;
|
||
}
|
||
if (showTime) {
|
||
url += `&time=true`;
|
||
}
|
||
}
|
||
|
||
navigate(url);
|
||
}
|
||
};
|
||
|
||
const handlePlatform = () => {
|
||
const quai = manualPlatform.trim() || selectedPlatform;
|
||
if (!quai) {
|
||
alert("Veuillez sélectionner ou saisir une voie.");
|
||
return;
|
||
}
|
||
if (typeof navigate === 'function') {
|
||
const kebab = gareParam ? String(gareParam).trim() : '';
|
||
let url = `${prefix}/afficheurs/${modeParam}/${typeParam}/quais?gare=${encodeURIComponent(kebab)}&quai=${encodeURIComponent(quai)}`;
|
||
|
||
if (typeParam === 'classiques') {
|
||
if (extendedLines > 0) {
|
||
url += `&extended=${extendedLines}`;
|
||
}
|
||
if (showTime) {
|
||
url += `&time=true`;
|
||
}
|
||
}
|
||
|
||
navigate(url);
|
||
}
|
||
};
|
||
|
||
const handleSinglePage = (pageKey) => {
|
||
if (typeof navigate === 'function') {
|
||
const kebab = gareParam ? String(gareParam).trim() : '';
|
||
navigate(`${prefix}/afficheurs/${modeParam}/${typeParam}?gare=${encodeURIComponent(kebab)}&page=${pageKey}`);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="launcher-container">
|
||
{/* Stepper */}
|
||
<div className="launcher-stepper">
|
||
<div className="launcher-step launcher-step--completed">
|
||
<span className="launcher-step-num">✓</span>
|
||
<span>{decodedGare || 'Gare'}</span>
|
||
</div>
|
||
<span className="launcher-step-separator">›</span>
|
||
<div className="launcher-step launcher-step--completed">
|
||
<span className="launcher-step-num">✓</span>
|
||
<span>{typeParam.toUpperCase()}</span>
|
||
</div>
|
||
<span className="launcher-step-separator">›</span>
|
||
<div className="launcher-step launcher-step--active">
|
||
<span className="launcher-step-num">3</span>
|
||
<span>Options & Affichage</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="launcher-card">
|
||
{/* Header */}
|
||
<div className="launcher-header">
|
||
<div>
|
||
<h1 className="launcher-title">Configuration de l'afficheur</h1>
|
||
<p className="launcher-subtitle">
|
||
Gare : <strong style={{ color: '#002257' }}>{decodedGare}</strong> • Mode : <strong style={{ color: '#00439e' }}>{modeParam === 'arrivees' ? '🏁 Arrivées' : '🚆 Départs'}</strong> • Style : <strong style={{ color: '#008b47' }}>{typeParam.toUpperCase()}</strong>
|
||
</p>
|
||
</div>
|
||
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
|
||
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS'}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Grille responsive 2 colonnes (1 col sur mobile) */}
|
||
<div className="launcher-grid launcher-grid--2col">
|
||
{/* Option 1 : Afficheur Général */}
|
||
<div className="launcher-select-card" style={{ cursor: 'default' }}>
|
||
<div>
|
||
<span className="launcher-card-icon">🖥️</span>
|
||
<span className="launcher-card-tag launcher-card-tag--modern">Écran Général</span>
|
||
<h2 className="launcher-card-title">Afficheur Général (Hall)</h2>
|
||
<p className="launcher-card-desc">
|
||
Affiche la liste complète des circulations avec basculement automatique des pages.
|
||
</p>
|
||
|
||
{typeParam === 'afl' && (
|
||
<div className="launcher-checklist-box">
|
||
<div className="launcher-checklist-header">
|
||
<span className="launcher-checklist-title">
|
||
Pages incluses dans la boucle :
|
||
</span>
|
||
<div className="launcher-checklist-actions">
|
||
<button
|
||
type="button"
|
||
onClick={() => selectAllAflPages(true)}
|
||
className="launcher-link-btn"
|
||
>
|
||
Tout cocher
|
||
</button>
|
||
<span style={{ color: '#ccc' }}>|</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => selectAllAflPages(false)}
|
||
className="launcher-link-btn"
|
||
style={{ color: '#666' }}
|
||
>
|
||
Horaires seuls
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||
<label className="launcher-checkbox-label">
|
||
<input
|
||
type="checkbox"
|
||
checked={aflPages.horaires}
|
||
onChange={() => toggleAflPage('horaires')}
|
||
className="launcher-checkbox"
|
||
/>
|
||
<span><strong>Page 1 & 2</strong> (Horaires des trains)</span>
|
||
</label>
|
||
|
||
<label className="launcher-checkbox-label">
|
||
<input
|
||
type="checkbox"
|
||
checked={aflPages.infos}
|
||
onChange={() => toggleAflPage('infos')}
|
||
className="launcher-checkbox"
|
||
/>
|
||
<span><strong>Informations</strong> (Trafic & Travaux)</span>
|
||
</label>
|
||
|
||
<label className="launcher-checkbox-label">
|
||
<input
|
||
type="checkbox"
|
||
checked={aflPages.securite1}
|
||
onChange={() => toggleAflPage('securite1')}
|
||
className="launcher-checkbox"
|
||
/>
|
||
<span><strong>Sécurité 1</strong> (Bagages délaissés)</span>
|
||
</label>
|
||
|
||
<label className="launcher-checkbox-label">
|
||
<input
|
||
type="checkbox"
|
||
checked={aflPages.securite2}
|
||
onChange={() => toggleAflPage('securite2')}
|
||
className="launcher-checkbox"
|
||
/>
|
||
<span><strong>Sécurité 2</strong> (Vigipirate / 3117)</span>
|
||
</label>
|
||
|
||
<label className="launcher-checkbox-label">
|
||
<input
|
||
type="checkbox"
|
||
checked={aflPages.corresp}
|
||
onChange={() => toggleAflPage('corresp')}
|
||
className="launcher-checkbox"
|
||
/>
|
||
<span><strong>Correspondances</strong> (Bus & Tram)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{typeParam === 'classiques' && (
|
||
<div className="launcher-checklist-box">
|
||
<div className="launcher-checklist-header">
|
||
<span className="launcher-checklist-title">
|
||
Options d'affichage Classiques :
|
||
</span>
|
||
</div>
|
||
|
||
{/* Choix du nombre de lignes étendues */}
|
||
<div style={{ marginBottom: '14px' }}>
|
||
<label style={{ display: 'block', fontSize: '13px', fontWeight: 700, color: '#002257', marginBottom: '8px' }}>
|
||
Lignes étendues (Dessertes défilantes) :
|
||
</label>
|
||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '6px' }}>
|
||
{[0, 1, 2, 3].map((num) => (
|
||
<button
|
||
key={num}
|
||
type="button"
|
||
onClick={() => setExtendedLines(num)}
|
||
style={{
|
||
padding: '8px 4px',
|
||
borderRadius: '8px',
|
||
border: extendedLines === num ? '2px solid #00439e' : '1px solid #c2dbfe',
|
||
background: extendedLines === num ? '#00439e' : '#ffffff',
|
||
color: extendedLines === num ? '#ffffff' : '#002257',
|
||
fontWeight: 700,
|
||
fontSize: '13px',
|
||
cursor: 'pointer',
|
||
transition: 'all 0.15s ease'
|
||
}}
|
||
>
|
||
{num === 0 ? '0 (Standard)' : `${num} étendue${num > 1 ? 's' : ''}`}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<div style={{ fontSize: '12px', color: '#555', marginTop: '6px' }}>
|
||
{extendedLines > 0
|
||
? `Les ${extendedLines} premier${extendedLines > 1 ? 's' : ''} train${extendedLines > 1 ? 's' : ''} s'afficheront sur 2 lignes réduites avec le défilement de toutes les gares.`
|
||
: 'Affichage classique compact sans défilement de gares.'}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Option Afficher l'heure */}
|
||
<div style={{ borderTop: '1px solid #e2ebf6', paddingTop: '10px' }}>
|
||
<label className="launcher-checkbox-label" style={{ cursor: 'pointer' }}>
|
||
<input
|
||
type="checkbox"
|
||
checked={showTime}
|
||
onChange={(e) => setShowTime(e.target.checked)}
|
||
className="launcher-checkbox"
|
||
/>
|
||
<span>
|
||
<strong>Afficher l'heure & informations</strong>
|
||
</span>
|
||
</label>
|
||
<div style={{ fontSize: '12px', color: '#555', marginLeft: '28px', marginTop: '2px' }}>
|
||
Réserve la 10e ligne pour l'horloge numérique [ HH MM SS ] et un bandeau d'information défilant (horaires sur 9 lignes max).
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={handleGeneral}
|
||
className="launcher-btn launcher-btn--primary"
|
||
>
|
||
🖥️ Lancer l'Afficheur Général
|
||
</button>
|
||
</div>
|
||
|
||
{/* Option 2 : Afficheur par Quai */}
|
||
<div className="launcher-select-card" style={{ cursor: 'default' }}>
|
||
<div>
|
||
<span className="launcher-card-icon">🚉</span>
|
||
<span className="launcher-card-tag launcher-card-tag--retro">Écran de Voie</span>
|
||
<h2 className="launcher-card-title">Afficheur par Quai</h2>
|
||
<p className="launcher-card-desc">
|
||
Affiche le prochain train au départ/arrivée avec la liste déroulante des gares desservies sur la voie.
|
||
</p>
|
||
|
||
<div style={{ background: '#f8fafc', border: '1px solid #e2ebf6', borderRadius: '10px', padding: '16px', marginBottom: '20px' }}>
|
||
{loading ? (
|
||
<div style={{ fontSize: '14px', color: '#00439e', fontWeight: 600 }}>⏳ Détection des voies réelles...</div>
|
||
) : (
|
||
<div>
|
||
{platforms.length > 0 ? (
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<label style={{ display: 'block', fontSize: '13px', fontWeight: 700, color: '#002257', marginBottom: '6px' }}>
|
||
Voies actives détectées :
|
||
</label>
|
||
<select
|
||
value={selectedPlatform}
|
||
onChange={(e) => {
|
||
setSelectedPlatform(e.target.value);
|
||
setManualPlatform('');
|
||
}}
|
||
style={{
|
||
width: '100%',
|
||
padding: '12px',
|
||
borderRadius: '8px',
|
||
border: '2px solid #c2dbfe',
|
||
fontSize: '15px',
|
||
fontWeight: 600,
|
||
background: '#ffffff',
|
||
color: '#002257'
|
||
}}
|
||
>
|
||
{platforms.map(p => (
|
||
<option key={p} value={p}>Voie {p}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
) : (
|
||
<div style={{ fontSize: '13px', color: '#666', marginBottom: '16px', background: '#fff', padding: '8px 12px', borderRadius: '6px' }}>
|
||
ℹ️ Saisie manuelle de la voie ci-dessous.
|
||
</div>
|
||
)}
|
||
|
||
<div>
|
||
<label style={{ display: 'block', fontSize: '13px', fontWeight: 700, color: '#002257', marginBottom: '6px' }}>
|
||
Ou saisie libre d'une voie :
|
||
</label>
|
||
<input
|
||
type="text"
|
||
placeholder="Ex: A, B, 1, 2 bis..."
|
||
value={manualPlatform}
|
||
onChange={(e) => setManualPlatform(e.target.value)}
|
||
style={{
|
||
width: '100%',
|
||
padding: '12px',
|
||
borderRadius: '8px',
|
||
border: '2px solid #e2ebf6',
|
||
fontSize: '15px',
|
||
boxSizing: 'border-box',
|
||
background: '#ffffff'
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={handlePlatform}
|
||
className="launcher-btn launcher-btn--success"
|
||
>
|
||
🚉 Lancer l'Afficheur de Quai
|
||
</button>
|
||
</div>
|
||
|
||
{/* Options 3 & 4 pour le mode Classiques : Flash Info & Informations */}
|
||
{typeParam === 'classiques' && (
|
||
<>
|
||
{/* Option 3 : Flash Information */}
|
||
<div className="launcher-select-card" style={{ cursor: 'default' }}>
|
||
<div>
|
||
<span className="launcher-card-icon">⚡</span>
|
||
<span className="launcher-card-tag launcher-card-tag--retro" style={{ background: '#fab23e', color: '#00337f' }}>
|
||
Flash Info Trafic
|
||
</span>
|
||
<h2 className="launcher-card-title">Flash Information</h2>
|
||
<p className="launcher-card-desc">
|
||
Écran dédié aux perturbations de circulation en temps réel et travaux de maintenance sur le réseau.
|
||
</p>
|
||
<div style={{ background: '#f8fafc', border: '1px solid #e2ebf6', borderRadius: '10px', padding: '16px', marginBottom: '20px' }}>
|
||
<div style={{ fontSize: '13px', color: '#00337f', fontWeight: 600 }}>
|
||
⚡ Conforme au design historique <em>Flash Circulation</em> avec fond ambre et bandeau bleu nuit.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const kebab = gareParam ? String(gareParam).trim() : '';
|
||
navigate(`${prefix}/afficheurs/classiques/flash-info?gare=${encodeURIComponent(kebab)}`);
|
||
}}
|
||
className="launcher-btn"
|
||
style={{ background: '#00337f', color: '#fab23e', border: 'none', fontWeight: 700 }}
|
||
>
|
||
⚡ Lancer le Flash Information
|
||
</button>
|
||
</div>
|
||
|
||
{/* Option 4 : Informations (Messages Dynamiques) */}
|
||
<div className="launcher-select-card" style={{ cursor: 'default' }}>
|
||
<div>
|
||
<span className="launcher-card-icon">ℹ️</span>
|
||
<span className="launcher-card-tag launcher-card-tag--retro" style={{ background: '#00337f', color: '#ffffff' }}>
|
||
Messages Gare
|
||
</span>
|
||
<h2 className="launcher-card-title">Informations Voyageurs</h2>
|
||
<p className="launcher-card-desc">
|
||
Écran d'annonces et consignes dynamiques de la gare (Vigipirate, étiquetage des bagages, sécurité en gare).
|
||
</p>
|
||
<div style={{ background: '#f8fafc', border: '1px solid #e2ebf6', borderRadius: '10px', padding: '16px', marginBottom: '20px' }}>
|
||
<div style={{ fontSize: '13px', color: '#00337f', fontWeight: 600 }}>
|
||
ℹ️ Conforme au design historique <em>Information</em> avec messages voyageurs de la gare de {decodedGare}.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const kebab = gareParam ? String(gareParam).trim() : '';
|
||
navigate(`${prefix}/afficheurs/classiques/informations?gare=${encodeURIComponent(kebab)}`);
|
||
}}
|
||
className="launcher-btn"
|
||
style={{ background: '#00337f', color: '#ffffff', border: 'none', fontWeight: 700 }}
|
||
>
|
||
ℹ️ Lancer l'Afficheur Informations
|
||
</button>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
{/* Accès direct aux pages fixes AFL */}
|
||
{typeParam === 'afl' && (
|
||
<div style={{ borderTop: '1px solid #e2ebf6', paddingTop: '20px', marginBottom: '20px' }}>
|
||
<span className="launcher-chips-label">Accès direct à une page fixe AFL :</span>
|
||
<div className="launcher-chips-list">
|
||
<button type="button" onClick={() => handleSinglePage('1')} className="launcher-chip">
|
||
Page 1 (Horaires)
|
||
</button>
|
||
<button type="button" onClick={() => handleSinglePage('2')} className="launcher-chip">
|
||
Page 2 (Horaires)
|
||
</button>
|
||
<button type="button" onClick={() => handleSinglePage('infos')} className="launcher-chip">
|
||
Informations Trafic
|
||
</button>
|
||
<button type="button" onClick={() => handleSinglePage('securite1')} className="launcher-chip">
|
||
Sécurité (Bagages)
|
||
</button>
|
||
<button type="button" onClick={() => handleSinglePage('securite2')} className="launcher-chip">
|
||
Sécurité (Vigipirate / 3117)
|
||
</button>
|
||
<button type="button" onClick={() => handleSinglePage('corresp')} className="launcher-chip">
|
||
Correspondances
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Navigation retour */}
|
||
<div className="launcher-footer-nav">
|
||
<WcsButton
|
||
mode="plain"
|
||
onClick={() => {
|
||
if (typeof onBack === 'function') {
|
||
onBack();
|
||
} else if (typeof navigate === 'function') {
|
||
navigate(`${prefix}/afficheurs?gare=${encodeURIComponent(gareParam)}&mode=${modeParam}`);
|
||
}
|
||
}}
|
||
label="← Retour au choix du style"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
|