Files
ferrovia-infogare/src/pages/SelectAfficheur.js
T
mathis 9c6ac798f1 RELEASE V5.5.0 : Voici les modifications apportées :
-> REFONTE TOTALE DE TOUS LES AFFICHEURS CLASSIQUES + QUAIS

FONCTIONNALITÉS EN COURS DE DÉVELOPPEMENT :

-> ADAPTABILITÉ EN FONCTION DES SITUATIONS
2026-08-31 02:02:46 +02:00

527 lines
22 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 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>
);
}