V1.1.0 //
-> 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.
This commit is contained in:
1 parent
c30a0e9002
commit
6cc1e1820d
14 files changed
+1143
-849
No files matched your search
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "afficeheurs-ferrovia.",
|
"name": "afficheurs-ferrovia",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "afficeheurs-ferrovia.",
|
"name": "afficheurs-ferrovia",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dependencies": "^0.0.1",
|
"dependencies": "^0.0.1",
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "PORT=4010 react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"serve": "serve -s build -l 4010",
|
"serve": "serve -s build -l 4010",
|
||||||
"start:prod": "serve -s build -l 4010",
|
"start:prod": "serve -s build -l 4010",
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
const CACHE_NAME = 'ferrovia-pwa-v1';
|
||||||
|
|
||||||
|
self.addEventListener('install', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
return cache.addAll([
|
||||||
|
'./',
|
||||||
|
'./index.html',
|
||||||
|
'./manifest.json',
|
||||||
|
'./favicon.ico',
|
||||||
|
'./logo192.png',
|
||||||
|
'./logo512.png'
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((cacheNames) => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.filter((name) => name !== CACHE_NAME).map((name) => caches.delete(name))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.clients.claim();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
// Ignorer les requêtes non-GET et les requêtes vers d'autres origines ou APIs
|
||||||
|
if (event.request.method !== 'GET' || !event.request.url.startsWith(self.location.origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request).then((cachedResponse) => {
|
||||||
|
if (cachedResponse) {
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
return fetch(event.request).then((networkResponse) => {
|
||||||
|
return caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(event.request, networkResponse.clone());
|
||||||
|
return networkResponse;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// En cas d'échec réseau total, on pourrait retourner un fallback hors-ligne
|
||||||
|
return caches.match('./index.html');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generatedAt": "2025-10-24T21:56:22.827Z",
|
"generatedAt": "2026-10-24T21:56:22.827Z",
|
||||||
"logos": [
|
"logos": [
|
||||||
{
|
{
|
||||||
"file": "logo-aleop.svg",
|
"file": "logo-aleop.svg",
|
||||||
@@ -8,11 +8,12 @@
|
|||||||
"name": "Aleop"
|
"name": "Aleop"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "logo-auvergerhonealpes.svg",
|
"file": "sncf-voyageurs-logo.png",
|
||||||
"path": "/logo-auvergerhonealpes.svg",
|
"path": "/sncf-voyageurs-logo.png",
|
||||||
"slug": "auvergerhonealpes",
|
"slug": "auvergerhonealpes",
|
||||||
"name": "Auvergerhonealpes"
|
"name": "TER AURA"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"file": "logo-breizhgo.svg",
|
"file": "logo-breizhgo.svg",
|
||||||
"path": "/logo-breizhgo.svg",,
|
"path": "/logo-breizhgo.svg",,
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const filePath = 'c:\\Users\\mathi\\Documents\\Développement WEB\\ferrovia-infogare\\src\\App.js';
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
"import React, { useEffect, useState } from 'react';",
|
||||||
|
"import React, { useEffect, useState } from 'react';\nimport { WcsCard, WcsCardBody, WcsButton, WcsBadge } from 'wcs-react';"
|
||||||
|
);
|
||||||
|
|
||||||
|
const home_component_old = ` return (
|
||||||
|
<div className="launcher-container">
|
||||||
|
{/* Carte de bienvenue */}
|
||||||
|
<div className="launcher-card">
|
||||||
|
<div className="launcher-header">
|
||||||
|
<div>
|
||||||
|
<h1 className="launcher-title">🚅 Téléaffichage Ferroviaire SNCF</h1>
|
||||||
|
<p className="launcher-subtitle">
|
||||||
|
{source === 'supabase'
|
||||||
|
? "Consultez les afficheurs basés sur les données de votre BDD Supabase."
|
||||||
|
: "Simulateur d'écrans de gare officiels connectés en direct aux flux GTFS & Open Data SNCF."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className={\`launcher-badge \${source === 'supabase' ? 'launcher-badge--supabase' : ''}\`}>
|
||||||
|
{source === 'supabase' ? '🗄️ Mode Supabase' : '🌐 Mode SNCF Live'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cartes principales : Départs / Arrivées */}
|
||||||
|
<div className="launcher-grid launcher-grid--2col">
|
||||||
|
<div
|
||||||
|
className="launcher-select-card"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/departs\`);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/departs\`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="launcher-card-icon">🚆</span>
|
||||||
|
<span className="launcher-card-tag launcher-card-tag--modern">Départs</span>
|
||||||
|
<h2 className="launcher-card-title">Prochains Départs</h2>
|
||||||
|
<p className="launcher-card-desc">
|
||||||
|
Consultez les trains au départ, leurs voies, retards et dessertes complètes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<span className="launcher-btn launcher-btn--primary">
|
||||||
|
Consulter les Départs ›
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="launcher-select-card"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/arrivees\`);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/arrivees\`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="launcher-card-icon">🏁</span>
|
||||||
|
<span className="launcher-card-tag launcher-card-tag--modern">Arrivées</span>
|
||||||
|
<h2 className="launcher-card-title">Prochaines Arrivées</h2>
|
||||||
|
<p className="launcher-card-desc">
|
||||||
|
Suivez en direct les trains attendus en gare, provenances et voies assignées.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<span className="launcher-btn launcher-btn--secondary">
|
||||||
|
Consulter les Arrivées ›
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Raccourcis rapides vers les gares */}
|
||||||
|
<div className="launcher-chips-container" style={{ marginTop: '8px' }}>
|
||||||
|
<span className="launcher-chips-label">Accès direct par gare :</span>
|
||||||
|
<div className="launcher-chips-list">
|
||||||
|
{POPULAR_HOME_GARES.map((gName) => (
|
||||||
|
<button
|
||||||
|
key={gName}
|
||||||
|
type="button"
|
||||||
|
className="launcher-chip"
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/afficheurs/\${encodeURIComponent(gName)}?mode=departs\`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📍 {gName.replace(/-/g, ' ')}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Présentation des types d'afficheurs */}
|
||||||
|
<div style={{ borderTop: '1px solid #e2ebf6', paddingTop: '20px', marginTop: '20px' }}>
|
||||||
|
<div style={{ fontSize: '13px', color: '#5f6368', lineHeight: 1.6 }}>
|
||||||
|
✨ <strong>Formats disponibles :</strong> Afficheurs Fluides & Lumineux (AFL), Téléaffichage historique (Classiques), Écrans Vidéo Voyageurs (EVA) et Écrans par Quai / Voie.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);`;
|
||||||
|
|
||||||
|
const home_component_new = ` return (
|
||||||
|
<div className="launcher-container" style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
|
||||||
|
<WcsCard mode="raised">
|
||||||
|
<WcsCardBody>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>🚅 Téléaffichage Ferroviaire SNCF</h1>
|
||||||
|
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>
|
||||||
|
{source === 'supabase'
|
||||||
|
? "Consultez les afficheurs basés sur les données de votre BDD Supabase."
|
||||||
|
: "Simulateur d'écrans de gare officiels connectés en direct aux flux GTFS & Open Data SNCF."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<WcsBadge className={source === 'supabase' ? 'wcs-secondary' : 'wcs-primary'} shape="rounded">
|
||||||
|
{source === 'supabase' ? '🗄️ Mode Supabase' : '🌐 Mode SNCF Live'}
|
||||||
|
</WcsBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '24px', flexDirection: 'row', flexWrap: 'wrap', marginBottom: '24px' }}>
|
||||||
|
<WcsCard style={{ flex: '1 1 300px', cursor: 'pointer' }} mode="flat" onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/departs\`);
|
||||||
|
}}>
|
||||||
|
<WcsCardBody>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<span style={{ fontSize: '32px' }}>🚆</span>
|
||||||
|
<WcsBadge className="wcs-success">Départs</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginTop: '16px' }}>Prochains Départs</h2>
|
||||||
|
<p style={{ color: 'var(--wcs-text-light)', minHeight: '60px' }}>Consultez les trains au départ, leurs voies, retards et dessertes complètes.</p>
|
||||||
|
<WcsButton className="wcs-primary" style={{ width: '100%' }}>Consulter les Départs</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
|
||||||
|
<WcsCard style={{ flex: '1 1 300px', cursor: 'pointer' }} mode="flat" onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/arrivees\`);
|
||||||
|
}}>
|
||||||
|
<WcsCardBody>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<span style={{ fontSize: '32px' }}>🏁</span>
|
||||||
|
<WcsBadge className="wcs-info">Arrivées</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginTop: '16px' }}>Prochaines Arrivées</h2>
|
||||||
|
<p style={{ color: 'var(--wcs-text-light)', minHeight: '60px' }}>Suivez en direct les trains attendus en gare, provenances et voies assignées.</p>
|
||||||
|
<WcsButton className="wcs-secondary" style={{ width: '100%' }}>Consulter les Arrivées</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '24px' }}>
|
||||||
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Accès direct par gare :</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
|
{POPULAR_HOME_GARES.map((gName) => (
|
||||||
|
<WcsButton
|
||||||
|
key={gName}
|
||||||
|
mode="stroked"
|
||||||
|
shape="round"
|
||||||
|
size="s"
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(\`\${prefix}/afficheurs/\${encodeURIComponent(gName)}?mode=departs\`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📍 {gName.replace(/-/g, ' ')}
|
||||||
|
</WcsButton>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr style={{ border: 'none', borderTop: '1px solid var(--wcs-light)', margin: '24px 0' }} />
|
||||||
|
<div style={{ fontSize: '14px', color: 'var(--wcs-text-light)', lineHeight: 1.6 }}>
|
||||||
|
✨ <strong>Formats disponibles :</strong> Afficheurs Fluides & Lumineux (AFL), Téléaffichage historique (Classiques), Écrans Vidéo Voyageurs (EVA) et Écrans par Quai / Voie.
|
||||||
|
</div>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
</div>
|
||||||
|
);`;
|
||||||
|
|
||||||
|
content = content.replace(home_component_old, home_component_new);
|
||||||
|
fs.writeFileSync(filePath, content, 'utf-8');
|
||||||
|
console.log('App.js patched successfully');
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
file_path = r"c:\Users\mathi\Documents\Développement WEB\ferrovia-infogare\src\App.js"
|
||||||
|
|
||||||
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Make the changes in App.js for the Home component
|
||||||
|
new_content = content.replace(
|
||||||
|
"import React, { useEffect, useState } from 'react';",
|
||||||
|
"import React, { useEffect, useState } from 'react';\nimport { WcsCard, WcsCardBody, WcsButton, WcsBadge } from 'wcs-react';"
|
||||||
|
)
|
||||||
|
|
||||||
|
home_component_old = """ return (
|
||||||
|
<div className="launcher-container">
|
||||||
|
{/* Carte de bienvenue */}
|
||||||
|
<div className="launcher-card">
|
||||||
|
<div className="launcher-header">
|
||||||
|
<div>
|
||||||
|
<h1 className="launcher-title">🚅 Téléaffichage Ferroviaire SNCF</h1>
|
||||||
|
<p className="launcher-subtitle">
|
||||||
|
{source === 'supabase'
|
||||||
|
? "Consultez les afficheurs basés sur les données de votre BDD Supabase."
|
||||||
|
: "Simulateur d'écrans de gare officiels connectés en direct aux flux GTFS & Open Data SNCF."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
|
||||||
|
{source === 'supabase' ? '🗄️ Mode Supabase' : '🌐 Mode SNCF Live'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cartes principales : Départs / Arrivées */}
|
||||||
|
<div className="launcher-grid launcher-grid--2col">
|
||||||
|
<div
|
||||||
|
className="launcher-select-card"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/departs`);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/departs`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="launcher-card-icon">🚆</span>
|
||||||
|
<span className="launcher-card-tag launcher-card-tag--modern">Départs</span>
|
||||||
|
<h2 className="launcher-card-title">Prochains Départs</h2>
|
||||||
|
<p className="launcher-card-desc">
|
||||||
|
Consultez les trains au départ, leurs voies, retards et dessertes complètes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<span className="launcher-btn launcher-btn--primary">
|
||||||
|
Consulter les Départs ›
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="launcher-select-card"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/arrivees`);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/arrivees`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="launcher-card-icon">🏁</span>
|
||||||
|
<span className="launcher-card-tag launcher-card-tag--modern">Arrivées</span>
|
||||||
|
<h2 className="launcher-card-title">Prochaines Arrivées</h2>
|
||||||
|
<p className="launcher-card-desc">
|
||||||
|
Suivez en direct les trains attendus en gare, provenances et voies assignées.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<span className="launcher-btn launcher-btn--secondary">
|
||||||
|
Consulter les Arrivées ›
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Raccourcis rapides vers les gares */}
|
||||||
|
<div className="launcher-chips-container" style={{ marginTop: '8px' }}>
|
||||||
|
<span className="launcher-chips-label">Accès direct par gare :</span>
|
||||||
|
<div className="launcher-chips-list">
|
||||||
|
{POPULAR_HOME_GARES.map((gName) => (
|
||||||
|
<button
|
||||||
|
key={gName}
|
||||||
|
type="button"
|
||||||
|
className="launcher-chip"
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/afficheurs/${encodeURIComponent(gName)}?mode=departs`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📍 {gName.replace(/-/g, ' ')}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Présentation des types d'afficheurs */}
|
||||||
|
<div style={{ borderTop: '1px solid #e2ebf6', paddingTop: '20px', marginTop: '20px' }}>
|
||||||
|
<div style={{ fontSize: '13px', color: '#5f6368', lineHeight: 1.6 }}>
|
||||||
|
✨ <strong>Formats disponibles :</strong> Afficheurs Fluides & Lumineux (AFL), Téléaffichage historique (Classiques), Écrans Vidéo Voyageurs (EVA) et Écrans par Quai / Voie.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);"""
|
||||||
|
|
||||||
|
home_component_new = """ return (
|
||||||
|
<div className="launcher-container" style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
|
||||||
|
<WcsCard mode="raised">
|
||||||
|
<WcsCardBody>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>🚅 Téléaffichage Ferroviaire SNCF</h1>
|
||||||
|
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>
|
||||||
|
{source === 'supabase'
|
||||||
|
? "Consultez les afficheurs basés sur les données de votre BDD Supabase."
|
||||||
|
: "Simulateur d'écrans de gare officiels connectés en direct aux flux GTFS & Open Data SNCF."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<WcsBadge className={source === 'supabase' ? 'wcs-secondary' : 'wcs-primary'} shape="rounded">
|
||||||
|
{source === 'supabase' ? '🗄️ Mode Supabase' : '🌐 Mode SNCF Live'}
|
||||||
|
</WcsBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '24px', flexDirection: 'row', flexWrap: 'wrap', marginBottom: '24px' }}>
|
||||||
|
<WcsCard style={{ flex: '1 1 300px', cursor: 'pointer' }} mode="flat" onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/departs`);
|
||||||
|
}}>
|
||||||
|
<WcsCardBody>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<span style={{ fontSize: '32px' }}>🚆</span>
|
||||||
|
<WcsBadge className="wcs-success">Départs</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginTop: '16px' }}>Prochains Départs</h2>
|
||||||
|
<p style={{ color: 'var(--wcs-text-light)', minHeight: '60px' }}>Consultez les trains au départ, leurs voies, retards et dessertes complètes.</p>
|
||||||
|
<WcsButton className="wcs-primary" style={{ width: '100%' }}>Consulter les Départs</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
|
||||||
|
<WcsCard style={{ flex: '1 1 300px', cursor: 'pointer' }} mode="flat" onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/arrivees`);
|
||||||
|
}}>
|
||||||
|
<WcsCardBody>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<span style={{ fontSize: '32px' }}>🏁</span>
|
||||||
|
<WcsBadge className="wcs-info">Arrivées</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginTop: '16px' }}>Prochaines Arrivées</h2>
|
||||||
|
<p style={{ color: 'var(--wcs-text-light)', minHeight: '60px' }}>Suivez en direct les trains attendus en gare, provenances et voies assignées.</p>
|
||||||
|
<WcsButton className="wcs-secondary" style={{ width: '100%' }}>Consulter les Arrivées</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '24px' }}>
|
||||||
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Accès direct par gare :</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
|
{POPULAR_HOME_GARES.map((gName) => (
|
||||||
|
<WcsButton
|
||||||
|
key={gName}
|
||||||
|
mode="stroked"
|
||||||
|
shape="round"
|
||||||
|
size="s"
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/afficheurs/${encodeURIComponent(gName)}?mode=departs`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📍 {gName.replace(/-/g, ' ')}
|
||||||
|
</WcsButton>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr style={{ border: 'none', borderTop: '1px solid var(--wcs-light)', margin: '24px 0' }} />
|
||||||
|
<div style={{ fontSize: '14px', color: 'var(--wcs-text-light)', lineHeight: 1.6 }}>
|
||||||
|
✨ <strong>Formats disponibles :</strong> Afficheurs Fluides & Lumineux (AFL), Téléaffichage historique (Classiques), Écrans Vidéo Voyageurs (EVA) et Écrans par Quai / Voie.
|
||||||
|
</div>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
</div>
|
||||||
|
);"""
|
||||||
|
|
||||||
|
new_content = new_content.replace(home_component_old, home_component_new)
|
||||||
|
|
||||||
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(new_content)
|
||||||
+64
-99
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { WcsCard, WcsCardBody, WcsButton, WcsBadge } from 'wcs-react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
import { applyPolyfills, defineCustomElements } from 'wcs-core/loader';
|
import { applyPolyfills, defineCustomElements } from 'wcs-core/loader';
|
||||||
@@ -24,11 +25,9 @@ import { getSourceFromPath } from './services/railwayService';
|
|||||||
|
|
||||||
applyPolyfills().then(() => defineCustomElements(window));
|
applyPolyfills().then(() => defineCustomElements(window));
|
||||||
|
|
||||||
// Petit routeur client sans dépendances externes
|
|
||||||
function useSimpleRouter() {
|
function useSimpleRouter() {
|
||||||
function normalizePath(p) {
|
function normalizePath(p) {
|
||||||
if (!p) return '/';
|
if (!p) return '/';
|
||||||
// retirer query string et hash
|
|
||||||
p = String(p).split(/[?#]/)[0];
|
p = String(p).split(/[?#]/)[0];
|
||||||
if (p.indexOf('index.html') !== -1) return '/';
|
if (p.indexOf('index.html') !== -1) return '/';
|
||||||
p = p.trim();
|
p = p.trim();
|
||||||
@@ -74,7 +73,6 @@ function App() {
|
|||||||
const prefix = source === 'supabase' ? '/ferrovia' : '';
|
const prefix = source === 'supabase' ? '/ferrovia' : '';
|
||||||
const cleanRoute = (route.replace(/^\/ferrovia/, '') || '/');
|
const cleanRoute = (route.replace(/^\/ferrovia/, '') || '/');
|
||||||
|
|
||||||
// Composant Home moderne & responsive pour Mobile, Tablette et Desktop
|
|
||||||
function Home({ navigate, prefix, source }) {
|
function Home({ navigate, prefix, source }) {
|
||||||
const POPULAR_HOME_GARES = [
|
const POPULAR_HOME_GARES = [
|
||||||
'Paris-Gare-de-Lyon',
|
'Paris-Gare-de-Lyon',
|
||||||
@@ -88,118 +86,85 @@ function App() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="launcher-container">
|
<div className="launcher-container" style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
|
||||||
{/* Carte de bienvenue */}
|
<WcsCard mode="raised">
|
||||||
<div className="launcher-card">
|
<WcsCardBody>
|
||||||
<div className="launcher-header">
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="launcher-title">🚅 Téléaffichage Ferroviaire SNCF</h1>
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>🚅 Téléaffichage Ferroviaire SNCF</h1>
|
||||||
<p className="launcher-subtitle">
|
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>
|
||||||
{source === 'supabase'
|
{source === 'supabase'
|
||||||
? "Consultez les afficheurs basés sur les données de votre BDD Supabase."
|
? "Consultez les afficheurs basés sur les données de votre BDD Supabase."
|
||||||
: "Simulateur d'écrans de gare officiels connectés en direct aux flux GTFS & Open Data SNCF."}
|
: "Simulateur d'écrans de gare officiels connectés en direct aux flux GTFS & Open Data SNCF."}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
<WcsBadge className={source === 'supabase' ? 'wcs-secondary' : 'wcs-primary'} shape="rounded">
|
||||||
|
{source === 'supabase' ? '🗄️ Mode Supabase' : '🌐 Mode SNCF Live'}
|
||||||
|
</WcsBadge>
|
||||||
</div>
|
</div>
|
||||||
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
|
|
||||||
{source === 'supabase' ? '🗄️ Mode Supabase' : '🌐 Mode SNCF Live'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Cartes principales : Départs / Arrivées */}
|
<div style={{ display: 'flex', gap: '24px', flexDirection: 'row', flexWrap: 'wrap', marginBottom: '24px' }}>
|
||||||
<div className="launcher-grid launcher-grid--2col">
|
<WcsCard style={{ flex: '1 1 300px', cursor: 'pointer', border: '1px solid var(--wcs-light)' }} mode="flat" onClick={() => {
|
||||||
<div
|
|
||||||
className="launcher-select-card"
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={() => {
|
|
||||||
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
|
||||||
navigate(`${prefix}/departs`);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
navigate(`${prefix}/departs`);
|
navigate(`${prefix}/departs`);
|
||||||
}
|
}}>
|
||||||
}}
|
<WcsCardBody>
|
||||||
>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div>
|
<span style={{ fontSize: '32px' }}>🚆</span>
|
||||||
<span className="launcher-card-icon">🚆</span>
|
<WcsBadge className="wcs-success">Départs</WcsBadge>
|
||||||
<span className="launcher-card-tag launcher-card-tag--modern">Départs</span>
|
</div>
|
||||||
<h2 className="launcher-card-title">Prochains Départs</h2>
|
<h2 style={{ fontSize: '18px', marginTop: '16px' }}>Prochains Départs</h2>
|
||||||
<p className="launcher-card-desc">
|
<p style={{ color: 'var(--wcs-text-light)', minHeight: '60px' }}>Consultez les trains au départ, leurs voies, retards et dessertes complètes.</p>
|
||||||
Consultez les trains au départ, leurs voies, retards et dessertes complètes.
|
<WcsButton className="wcs-primary" style={{ width: '100%' }}>Consulter les Départs</WcsButton>
|
||||||
</p>
|
</WcsCardBody>
|
||||||
</div>
|
</WcsCard>
|
||||||
<div style={{ marginTop: 12 }}>
|
|
||||||
<span className="launcher-btn launcher-btn--primary">
|
|
||||||
Consulter les Départs ›
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<WcsCard style={{ flex: '1 1 300px', cursor: 'pointer', border: '1px solid var(--wcs-light)' }} mode="flat" onClick={() => {
|
||||||
className="launcher-select-card"
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={() => {
|
|
||||||
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
|
||||||
navigate(`${prefix}/arrivees`);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
try { sessionMemory.set('afficheurMode', 'arrivees'); } catch (e) {}
|
||||||
navigate(`${prefix}/arrivees`);
|
navigate(`${prefix}/arrivees`);
|
||||||
}
|
}}>
|
||||||
}}
|
<WcsCardBody>
|
||||||
>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div>
|
<span style={{ fontSize: '32px' }}>🏁</span>
|
||||||
<span className="launcher-card-icon">🏁</span>
|
<WcsBadge className="wcs-info">Arrivées</WcsBadge>
|
||||||
<span className="launcher-card-tag launcher-card-tag--modern">Arrivées</span>
|
</div>
|
||||||
<h2 className="launcher-card-title">Prochaines Arrivées</h2>
|
<h2 style={{ fontSize: '18px', marginTop: '16px' }}>Prochaines Arrivées</h2>
|
||||||
<p className="launcher-card-desc">
|
<p style={{ color: 'var(--wcs-text-light)', minHeight: '60px' }}>Suivez en direct les trains attendus en gare, provenances et voies assignées.</p>
|
||||||
Suivez en direct les trains attendus en gare, provenances et voies assignées.
|
<WcsButton className="wcs-secondary" style={{ width: '100%' }}>Consulter les Arrivées</WcsButton>
|
||||||
</p>
|
</WcsCardBody>
|
||||||
</div>
|
</WcsCard>
|
||||||
<div style={{ marginTop: 12 }}>
|
</div>
|
||||||
<span className="launcher-btn launcher-btn--secondary">
|
|
||||||
Consulter les Arrivées ›
|
<div style={{ marginBottom: '24px' }}>
|
||||||
</span>
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Accès direct par gare :</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
|
{POPULAR_HOME_GARES.map((gName) => (
|
||||||
|
<WcsButton
|
||||||
|
key={gName}
|
||||||
|
mode="stroked"
|
||||||
|
shape="round"
|
||||||
|
size="s"
|
||||||
|
onClick={() => {
|
||||||
|
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
||||||
|
navigate(`${prefix}/afficheurs/${encodeURIComponent(gName)}?mode=departs`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📍 {gName.replace(/-/g, ' ')}
|
||||||
|
</WcsButton>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Raccourcis rapides vers les gares */}
|
<hr style={{ border: 'none', borderTop: '1px solid var(--wcs-light)', margin: '24px 0' }} />
|
||||||
<div className="launcher-chips-container" style={{ marginTop: '8px' }}>
|
<div style={{ fontSize: '14px', color: 'var(--wcs-text-light)', lineHeight: 1.6 }}>
|
||||||
<span className="launcher-chips-label">Accès direct par gare :</span>
|
|
||||||
<div className="launcher-chips-list">
|
|
||||||
{POPULAR_HOME_GARES.map((gName) => (
|
|
||||||
<button
|
|
||||||
key={gName}
|
|
||||||
type="button"
|
|
||||||
className="launcher-chip"
|
|
||||||
onClick={() => {
|
|
||||||
try { sessionMemory.set('afficheurMode', 'departs'); } catch (e) {}
|
|
||||||
navigate(`${prefix}/afficheurs/${encodeURIComponent(gName)}?mode=departs`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
📍 {gName.replace(/-/g, ' ')}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Présentation des types d'afficheurs */}
|
|
||||||
<div style={{ borderTop: '1px solid #e2ebf6', paddingTop: '20px', marginTop: '20px' }}>
|
|
||||||
<div style={{ fontSize: '13px', color: '#5f6368', lineHeight: 1.6 }}>
|
|
||||||
✨ <strong>Formats disponibles :</strong> Afficheurs Fluides & Lumineux (AFL), Téléaffichage historique (Classiques), Écrans Vidéo Voyageurs (EVA) et Écrans par Quai / Voie.
|
✨ <strong>Formats disponibles :</strong> Afficheurs Fluides & Lumineux (AFL), Téléaffichage historique (Classiques), Écrans Vidéo Voyageurs (EVA) et Écrans par Quai / Voie.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</WcsCardBody>
|
||||||
</div>
|
</WcsCard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routes pour afficheurs : supporte le mode racine ET le mode /ferrovia
|
|
||||||
if (
|
if (
|
||||||
cleanRoute.startsWith('/afficheurs/classiques/flash-info') ||
|
cleanRoute.startsWith('/afficheurs/classiques/flash-info') ||
|
||||||
cleanRoute.startsWith('/afficheurs/classiques/flash-circulation') ||
|
cleanRoute.startsWith('/afficheurs/classiques/flash-circulation') ||
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import './AfficheurViewportWrapper.css';
|
import './AfficheurViewportWrapper.css';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
/**
|
|
||||||
* AfficheurViewportWrapper
|
|
||||||
* Assure un affichage 16:9 strict (1920x1080) sur tous les appareils
|
|
||||||
* (Mobile portrait, Mobile paysage, Tablettes, Ordinateurs, TV).
|
|
||||||
* Ajoute automatiquement des bordures noires (letterboxing / pillarboxing)
|
|
||||||
* pour conserver fidèlement les proportions du design original sans distorsion.
|
|
||||||
*/
|
|
||||||
export default function AfficheurViewportWrapper({ children }) {
|
export default function AfficheurViewportWrapper({ children }) {
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
|
const [pipWindow, setPipWindow] = useState(null);
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
const [isPipSupported, setIsPipSupported] = useState(false);
|
||||||
|
const [autoPipTriggered, setAutoPipTriggered] = useState(false);
|
||||||
|
|
||||||
|
const isMobile = typeof window !== 'undefined' && (window.innerWidth <= 768 || /Mobi|Android/i.test(navigator.userAgent));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if ('documentPictureInPicture' in window) {
|
||||||
|
setIsPipSupported(true);
|
||||||
|
}
|
||||||
|
|
||||||
const calculateScale = () => {
|
const calculateScale = () => {
|
||||||
// Dimensions de la fenêtre d'affichage (support viewport iOS / Android safe area)
|
|
||||||
const w = window.innerWidth || (document.documentElement && document.documentElement.clientWidth) || 1920;
|
const w = window.innerWidth || (document.documentElement && document.documentElement.clientWidth) || 1920;
|
||||||
const h = window.innerHeight || (document.documentElement && document.documentElement.clientHeight) || 1080;
|
const h = window.innerHeight || (document.documentElement && document.documentElement.clientHeight) || 1080;
|
||||||
|
|
||||||
// Échelle basée sur la résolution standard 1920x1080 (16:9)
|
|
||||||
const scaleX = w / 1920;
|
const scaleX = w / 1920;
|
||||||
const scaleY = h / 1080;
|
const scaleY = h / 1080;
|
||||||
const s = Math.min(scaleX, scaleY);
|
const s = Math.min(scaleX, scaleY);
|
||||||
@@ -27,7 +28,6 @@ export default function AfficheurViewportWrapper({ children }) {
|
|||||||
calculateScale();
|
calculateScale();
|
||||||
window.addEventListener('resize', calculateScale);
|
window.addEventListener('resize', calculateScale);
|
||||||
window.addEventListener('orientationchange', calculateScale);
|
window.addEventListener('orientationchange', calculateScale);
|
||||||
|
|
||||||
if (window.visualViewport) {
|
if (window.visualViewport) {
|
||||||
window.visualViewport.addEventListener('resize', calculateScale);
|
window.visualViewport.addEventListener('resize', calculateScale);
|
||||||
}
|
}
|
||||||
@@ -41,16 +41,136 @@ export default function AfficheurViewportWrapper({ children }) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const togglePiP = async () => {
|
||||||
|
if (pipWindow) {
|
||||||
|
pipWindow.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('documentPictureInPicture' in window) {
|
||||||
|
try {
|
||||||
|
const pipW = isMobile ? window.innerWidth * 0.8 : 1920 / 3;
|
||||||
|
const pipH = isMobile ? (window.innerWidth * 0.8) * (1080 / 1920) : 1080 / 3;
|
||||||
|
|
||||||
|
const pip = await window.documentPictureInPicture.requestWindow({
|
||||||
|
width: Math.max(pipW, 300),
|
||||||
|
height: Math.max(pipH, 170),
|
||||||
|
});
|
||||||
|
|
||||||
|
[...document.styleSheets].forEach((styleSheet) => {
|
||||||
|
try {
|
||||||
|
const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join('');
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = cssRules;
|
||||||
|
pip.document.head.appendChild(style);
|
||||||
|
} catch(e) {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.type = styleSheet.type;
|
||||||
|
link.media = styleSheet.media;
|
||||||
|
link.href = styleSheet.href;
|
||||||
|
pip.document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pip.document.body.classList.add('pip-mode');
|
||||||
|
pip.document.body.style.margin = '0';
|
||||||
|
pip.document.body.style.padding = '0';
|
||||||
|
pip.document.body.style.overflow = 'hidden';
|
||||||
|
pip.document.body.style.width = '100vw';
|
||||||
|
pip.document.body.style.height = '100vh';
|
||||||
|
pip.document.body.style.backgroundColor = '#000';
|
||||||
|
pip.document.body.style.display = 'flex';
|
||||||
|
pip.document.body.style.justifyContent = 'center';
|
||||||
|
pip.document.body.style.alignItems = 'center';
|
||||||
|
|
||||||
|
const updatePipScale = () => {
|
||||||
|
const w = pip.innerWidth;
|
||||||
|
const h = pip.innerHeight;
|
||||||
|
const pipScale = Math.min(w / 1920, h / 1080);
|
||||||
|
pip.document.body.style.setProperty('--pip-scale', pipScale);
|
||||||
|
};
|
||||||
|
updatePipScale();
|
||||||
|
pip.addEventListener('resize', updatePipScale);
|
||||||
|
|
||||||
|
pip.addEventListener("pagehide", () => {
|
||||||
|
setPipWindow(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
setPipWindow(pip);
|
||||||
|
setAutoPipTriggered(true);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to enter PiP mode', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<div
|
||||||
|
className="afficheur-pwa-canvas"
|
||||||
|
style={{
|
||||||
|
transform: pipWindow ? 'scale(1)' : `scale(${scale})`,
|
||||||
|
width: '1920px',
|
||||||
|
height: '1080px',
|
||||||
|
position: 'relative'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="afficheur-pwa-screen-wrapper">
|
<div className="afficheur-pwa-screen-wrapper" ref={containerRef} style={{ width: '100vw', height: '100vh', overflow: 'hidden' }}>
|
||||||
<div
|
{!pipWindow && content}
|
||||||
className="afficheur-pwa-canvas"
|
|
||||||
style={{
|
{isMobile && isPipSupported && !pipWindow && !autoPipTriggered && (
|
||||||
transform: `scale(${scale})`,
|
<div
|
||||||
}}
|
onClick={togglePiP}
|
||||||
>
|
style={{
|
||||||
{children}
|
position: 'absolute',
|
||||||
</div>
|
top: 0, left: 0, right: 0, bottom: 0,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.85)',
|
||||||
|
zIndex: 10000,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '20px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '48px', marginBottom: '16px' }}>📱</span>
|
||||||
|
<h2 style={{ margin: '0 0 16px 0' }}>Appuyez pour lancer l'afficheur</h2>
|
||||||
|
<p style={{ opacity: 0.8 }}>Le mode Picture-in-Picture s'activera pour une meilleure expérience sur mobile.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pipWindow && (
|
||||||
|
<div style={{ color: 'white', textAlign: 'center', padding: '24px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
|
||||||
|
<h2>L'afficheur est en cours de lecture dans le lecteur réduit (PiP)</h2>
|
||||||
|
<p style={{ opacity: 0.7 }}>Vous pouvez naviguer sur votre téléphone, l'afficheur restera au premier plan.</p>
|
||||||
|
<button onClick={() => pipWindow.close()} style={{ padding: '12px 24px', marginTop: '24px', background: '#00439e', color: 'white', border: 'none', borderRadius: '4px', fontSize: '16px', fontWeight: 'bold' }}>
|
||||||
|
Fermer le mode PiP
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pipWindow && createPortal(
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transform: `scale(var(--pip-scale, 1))`
|
||||||
|
}}>
|
||||||
|
{content}
|
||||||
|
</div>,
|
||||||
|
pipWindow.document.body
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -15,3 +15,14 @@ root.render(
|
|||||||
// to log results (for example: reportWebVitals(console.log))
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
reportWebVitals();
|
reportWebVitals();
|
||||||
|
|
||||||
|
// Register PWA Service Worker
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/sw.js').then(registration => {
|
||||||
|
console.log('SW registered: ', registration);
|
||||||
|
}).catch(registrationError => {
|
||||||
|
console.log('SW registration failed: ', registrationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+98
-125
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState, useMemo } from 'react';
|
import React, { useEffect, useState, useMemo } from 'react';
|
||||||
import WcsButton from '../components/WcsButton';
|
import { WcsCard, WcsCardBody, WcsButton, WcsInput, WcsBadge, WcsHorizontalStepper } from 'wcs-react';
|
||||||
import sessionMemory from '../lib/sessionMemory';
|
import sessionMemory from '../lib/sessionMemory';
|
||||||
import { getSourceFromPath, searchGares } from '../services/railwayService';
|
import { getSourceFromPath, searchGares } from '../services/railwayService';
|
||||||
|
|
||||||
@@ -90,139 +90,112 @@ export default function Arrivees({ onBack, navigate }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="launcher-container">
|
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '24px', overflowY: 'auto', maxHeight: 'calc(100vh - 120px)' }}>
|
||||||
{/* Stepper */}
|
<WcsHorizontalStepper steps={steps} currentStep={0} style={{ marginBottom: '24px' }} />
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="launcher-card">
|
<WcsCard mode="raised">
|
||||||
{/* Header */}
|
<WcsCardBody>
|
||||||
<div className="launcher-header">
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="launcher-title">🏁 Prochaines Arrivées</h1>
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>🏁 Prochaines Arrivées</h1>
|
||||||
<p className="launcher-subtitle">Recherchez une gare pour afficher le tableau des arrivées en temps réel.</p>
|
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>Recherchez une gare pour afficher le tableau des arrivées 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>
|
||||||
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
|
|
||||||
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS Live'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Barre de recherche */}
|
<div style={{ marginBottom: '24px' }}>
|
||||||
<div className="launcher-search-box">
|
<WcsInput
|
||||||
<input
|
type="text"
|
||||||
type="search"
|
icon="search"
|
||||||
placeholder="Rechercher une gare (ex: Dijon, Paris, Lyon, Besançon...)"
|
placeholder="Rechercher une gare (ex: Dijon, Paris, Lyon, Besançon...)"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onWcsInput={(e) => setSearch(e.target.value)}
|
||||||
className="launcher-search-input"
|
style={{ width: '100%' }}
|
||||||
aria-label="Rechercher une gare"
|
clearable={true}
|
||||||
autoFocus
|
/>
|
||||||
/>
|
|
||||||
{search && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="launcher-search-clear"
|
|
||||||
onClick={() => setSearch('')}
|
|
||||||
aria-label="Effacer la recherche"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 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>
|
||||||
</div>
|
|
||||||
|
|
||||||
{loading && <p style={{ color: '#00439e', fontWeight: 600 }}>⏳ Chargement des gares...</p>}
|
<div style={{ marginBottom: '24px' }}>
|
||||||
{error && <p style={{ color: '#d70022', fontWeight: 600 }}>⚠️ Erreur : {error}</p>}
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Gares fréquentes :</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
{/* Invitation à la recherche en mode SNCF */}
|
{POPULAR_GARES.map((gName) => (
|
||||||
{source === 'sncf' && search.trim().length < 2 && (
|
<WcsButton
|
||||||
<div style={{ textAlign: 'center', padding: '24px 16px', color: '#666', background: '#f8fafc', borderRadius: '8px', border: '1px dashed #d0d7de' }}>
|
key={gName}
|
||||||
<p style={{ margin: 0, fontSize: '14px' }}>
|
mode="stroked"
|
||||||
💡 Saisissez au moins <strong>2 lettres</strong> pour chercher parmi plus de 3 500 gares du réseau français.
|
shape="round"
|
||||||
</p>
|
size="s"
|
||||||
</div>
|
onClick={() => handleSelectGare(gName)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 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>
|
{gName.replace(/-/g, ' ')}
|
||||||
<div className="launcher-result-name">{nom}</div>
|
</WcsButton>
|
||||||
{g.commune && (
|
))}
|
||||||
<div className="launcher-result-sub">
|
</div>
|
||||||
{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>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Navigation retour */}
|
{loading && <p style={{ color: 'var(--wcs-primary)', fontWeight: 600 }}>⏳ Chargement des gares...</p>}
|
||||||
<div className="launcher-footer-nav">
|
{error && <p style={{ color: 'var(--wcs-danger)', fontWeight: 600 }}>⚠️ Erreur : {error}</p>}
|
||||||
<WcsButton
|
|
||||||
mode="plain"
|
{source === 'sncf' && search.trim().length < 2 && (
|
||||||
onClick={onBack}
|
<div style={{ textAlign: 'center', padding: '24px 16px', color: 'var(--wcs-text-light)', background: 'var(--wcs-light)', borderRadius: '8px', border: '1px dashed var(--wcs-gray)' }}>
|
||||||
label="← Retour à l'accueil"
|
<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.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
+98
-125
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState, useMemo } from 'react';
|
import React, { useEffect, useState, useMemo } from 'react';
|
||||||
import WcsButton from '../components/WcsButton';
|
import { WcsCard, WcsCardBody, WcsButton, WcsInput, WcsBadge, WcsHorizontalStepper } from 'wcs-react';
|
||||||
import sessionMemory from '../lib/sessionMemory';
|
import sessionMemory from '../lib/sessionMemory';
|
||||||
import { getSourceFromPath, searchGares } from '../services/railwayService';
|
import { getSourceFromPath, searchGares } from '../services/railwayService';
|
||||||
|
|
||||||
@@ -90,139 +90,112 @@ export default function Departs({ onBack, navigate }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="launcher-container">
|
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '24px', overflowY: 'auto', maxHeight: 'calc(100vh - 120px)' }}>
|
||||||
{/* Stepper */}
|
<WcsHorizontalStepper steps={steps} currentStep={0} style={{ marginBottom: '24px' }} />
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="launcher-card">
|
<WcsCard mode="raised">
|
||||||
{/* Header */}
|
<WcsCardBody>
|
||||||
<div className="launcher-header">
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="launcher-title">🚆 Prochains Départs</h1>
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>🚆 Prochains Départs</h1>
|
||||||
<p className="launcher-subtitle">Recherchez une gare pour afficher le tableau des départs en temps réel.</p>
|
<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>
|
||||||
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
|
|
||||||
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS Live'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Barre de recherche */}
|
<div style={{ marginBottom: '24px' }}>
|
||||||
<div className="launcher-search-box">
|
<WcsInput
|
||||||
<input
|
type="text"
|
||||||
type="search"
|
icon="search"
|
||||||
placeholder="Rechercher une gare (ex: Dijon, Paris, Lyon, Besançon...)"
|
placeholder="Rechercher une gare (ex: Dijon, Paris, Lyon, Besançon...)"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onWcsInput={(e) => setSearch(e.target.value)}
|
||||||
className="launcher-search-input"
|
style={{ width: '100%' }}
|
||||||
aria-label="Rechercher une gare"
|
clearable={true}
|
||||||
autoFocus
|
/>
|
||||||
/>
|
|
||||||
{search && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="launcher-search-clear"
|
|
||||||
onClick={() => setSearch('')}
|
|
||||||
aria-label="Effacer la recherche"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 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>
|
||||||
</div>
|
|
||||||
|
|
||||||
{loading && <p style={{ color: '#00439e', fontWeight: 600 }}>⏳ Chargement des gares...</p>}
|
<div style={{ marginBottom: '24px' }}>
|
||||||
{error && <p style={{ color: '#d70022', fontWeight: 600 }}>⚠️ Erreur : {error}</p>}
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Gares fréquentes :</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
{/* Invitation à la recherche en mode SNCF */}
|
{POPULAR_GARES.map((gName) => (
|
||||||
{source === 'sncf' && search.trim().length < 2 && (
|
<WcsButton
|
||||||
<div style={{ textAlign: 'center', padding: '24px 16px', color: '#666', background: '#f8fafc', borderRadius: '8px', border: '1px dashed #d0d7de' }}>
|
key={gName}
|
||||||
<p style={{ margin: 0, fontSize: '14px' }}>
|
mode="stroked"
|
||||||
💡 Saisissez au moins <strong>2 lettres</strong> pour chercher parmi plus de 3 500 gares du réseau français.
|
shape="round"
|
||||||
</p>
|
size="s"
|
||||||
</div>
|
onClick={() => handleSelectGare(gName)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 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>
|
{gName.replace(/-/g, ' ')}
|
||||||
<div className="launcher-result-name">{nom}</div>
|
</WcsButton>
|
||||||
{g.commune && (
|
))}
|
||||||
<div className="launcher-result-sub">
|
</div>
|
||||||
{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>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Navigation retour */}
|
{loading && <p style={{ color: 'var(--wcs-primary)', fontWeight: 600 }}>⏳ Chargement des gares...</p>}
|
||||||
<div className="launcher-footer-nav">
|
{error && <p style={{ color: 'var(--wcs-danger)', fontWeight: 600 }}>⚠️ Erreur : {error}</p>}
|
||||||
<WcsButton
|
|
||||||
mode="plain"
|
{source === 'sncf' && search.trim().length < 2 && (
|
||||||
onClick={onBack}
|
<div style={{ textAlign: 'center', padding: '24px 16px', color: 'var(--wcs-text-light)', background: 'var(--wcs-light)', borderRadius: '8px', border: '1px dashed var(--wcs-gray)' }}>
|
||||||
label="← Retour à l'accueil"
|
<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.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
+63
-87
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import WcsButton from '../components/WcsButton';
|
import { WcsCard, WcsCardBody, WcsButton, WcsBadge, WcsHorizontalStepper } from 'wcs-react';
|
||||||
import sessionMemory from '../lib/sessionMemory';
|
import sessionMemory from '../lib/sessionMemory';
|
||||||
import { getSourceFromPath } from '../services/railwayService';
|
import { getSourceFromPath } from '../services/railwayService';
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ export default function Displays({ onBack, navigate }) {
|
|||||||
id: 'classiques',
|
id: 'classiques',
|
||||||
label: 'Classiques',
|
label: 'Classiques',
|
||||||
sublabel: 'Téléaffichage Historique',
|
sublabel: 'Téléaffichage Historique',
|
||||||
|
sublabel: 'Téléaffichage Historique',
|
||||||
tag: 'Rétro / Vintage',
|
tag: 'Rétro / Vintage',
|
||||||
tagType: 'retro',
|
tagType: 'retro',
|
||||||
icon: '📺',
|
icon: '📺',
|
||||||
@@ -33,7 +34,7 @@ export default function Displays({ onBack, navigate }) {
|
|||||||
tag: 'Gares Connectées',
|
tag: 'Gares Connectées',
|
||||||
tagType: 'modern',
|
tagType: 'modern',
|
||||||
icon: '📱',
|
icon: '📱',
|
||||||
desc: 'Écrans d\'information enrichis avec multimédia, infogare et dessertes complètes.'
|
desc: "Écrans d'information enrichis avec multimédia, infogare et dessertes complètes."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transilien',
|
id: 'transilien',
|
||||||
@@ -46,8 +47,7 @@ export default function Displays({ onBack, navigate }) {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Nettoyage du chemin pour extraire la gare même avec /ferrovia
|
const cleanPath = (typeof window !== 'undefined' ? window.location.pathname : '').replace(/ferrovia/, '');
|
||||||
const cleanPath = (typeof window !== 'undefined' ? window.location.pathname : '').replace(/^\/ferrovia/, '');
|
|
||||||
const parts = cleanPath.split('/').filter(Boolean);
|
const parts = cleanPath.split('/').filter(Boolean);
|
||||||
const typeIds = types.map(t => t.id);
|
const typeIds = types.map(t => t.id);
|
||||||
let gare = null;
|
let gare = null;
|
||||||
@@ -63,14 +63,13 @@ export default function Displays({ onBack, navigate }) {
|
|||||||
const g = paramsForGare.get('gare');
|
const g = paramsForGare.get('gare');
|
||||||
if (g) {
|
if (g) {
|
||||||
try {
|
try {
|
||||||
gare = decodeURIComponent(String(g).replace(/\+/g, ' ')).replace(/-/g, ' ');
|
gare = decodeURIComponent(String(g).replace(/\\+/g, ' ')).replace(/-/g, ' ');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
gare = String(g).replace(/-/g, ' ');
|
gare = String(g).replace(/-/g, ' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Déterminer le mode (departs/arrivees)
|
|
||||||
const params = new URLSearchParams(window.location.search || '');
|
const params = new URLSearchParams(window.location.search || '');
|
||||||
let mode = params.get('mode');
|
let mode = params.get('mode');
|
||||||
if (!mode) {
|
if (!mode) {
|
||||||
@@ -83,96 +82,73 @@ export default function Displays({ onBack, navigate }) {
|
|||||||
|
|
||||||
const formattedGareName = gare ? gare.replace(/-/g, ' ') : 'Non sélectionnée';
|
const formattedGareName = gare ? gare.replace(/-/g, ' ') : 'Non sélectionnée';
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{ text: formattedGareName, button: { kind: 'Icon', iconName: 'check' }, complete: true },
|
||||||
|
{ text: "Type d'afficheur", button: { kind: 'Text', text: '2' }, complete: false },
|
||||||
|
{ text: 'Options & Affichage', button: { kind: 'Text', text: '3' }, disable: true }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="launcher-container">
|
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '24px', overflowY: 'auto', maxHeight: 'calc(100vh - 120px)' }}>
|
||||||
{/* Stepper */}
|
<WcsHorizontalStepper steps={steps} currentStep={1} style={{ marginBottom: '24px' }} />
|
||||||
<div className="launcher-stepper">
|
|
||||||
<div className="launcher-step launcher-step--completed">
|
|
||||||
<span className="launcher-step-num">✓</span>
|
|
||||||
<span>{formattedGareName}</span>
|
|
||||||
</div>
|
|
||||||
<span className="launcher-step-separator">›</span>
|
|
||||||
<div className="launcher-step launcher-step--active">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="launcher-card">
|
<WcsCard mode="raised">
|
||||||
{/* Header */}
|
<WcsCardBody>
|
||||||
<div className="launcher-header">
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="launcher-title">Sélectionnez le style d'afficheur</h1>
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>Sélectionnez le style d'afficheur</h1>
|
||||||
<p className="launcher-subtitle">
|
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>
|
||||||
Gare : <strong style={{ color: '#002257' }}>{formattedGareName}</strong> • Mode : <strong style={{ color: '#00439e' }}>{mode === 'arrivees' ? '🏁 Arrivées' : '🚆 Départs'}</strong>
|
Gare : <strong>{formattedGareName}</strong> • Mode : <strong>{mode === 'arrivees' ? '🏁 Arrivées' : '🚆 Départs'}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
<span className={`launcher-badge ${source === 'supabase' ? 'launcher-badge--supabase' : ''}`}>
|
|
||||||
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Grille de sélection tactile des styles d'afficheurs */}
|
|
||||||
<div className="launcher-grid launcher-grid--4col">
|
|
||||||
{types.map((t) => (
|
|
||||||
<div
|
|
||||||
key={t.id}
|
|
||||||
className="launcher-select-card"
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={() => {
|
|
||||||
if (typeof navigate === 'function') {
|
|
||||||
const kebab = gare ? String(gare).trim().replace(/\s+/g, '-').replace(/\//g, '/') : '';
|
|
||||||
navigate(`${prefix}/select-afficheur?gare=${encodeURIComponent(kebab)}&mode=${mode}&type=${t.id}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
const kebab = gare ? String(gare).trim().replace(/\s+/g, '-').replace(/\//g, '/') : '';
|
|
||||||
navigate(`${prefix}/select-afficheur?gare=${encodeURIComponent(kebab)}&mode=${mode}&type=${t.id}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span className="launcher-card-icon">{t.icon}</span>
|
|
||||||
<span className={`launcher-card-tag ${t.tagType === 'retro' ? 'launcher-card-tag--retro' : 'launcher-card-tag--modern'}`}>
|
|
||||||
{t.tag}
|
|
||||||
</span>
|
|
||||||
<h2 className="launcher-card-title">{t.label}</h2>
|
|
||||||
<div style={{ fontSize: '13px', fontWeight: 600, color: '#00439e', marginBottom: '8px' }}>{t.sublabel}</div>
|
|
||||||
<p className="launcher-card-desc">{t.desc}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ marginTop: 12 }}>
|
|
||||||
<span className="launcher-btn launcher-btn--secondary" style={{ padding: '8px 12px', fontSize: '14px' }}>
|
|
||||||
Choisir ce style ›
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<WcsBadge className={source === 'supabase' ? 'wcs-secondary' : 'wcs-primary'} shape="rounded">
|
||||||
</div>
|
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS'}
|
||||||
|
</WcsBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Navigation retour */}
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '16px', marginBottom: '24px' }}>
|
||||||
<div className="launcher-footer-nav">
|
{types.map((t) => (
|
||||||
<WcsButton
|
<WcsCard
|
||||||
mode="plain"
|
key={t.id}
|
||||||
onClick={() => {
|
mode="flat"
|
||||||
|
style={{ cursor: 'pointer', border: '1px solid var(--wcs-light)' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (typeof navigate === 'function') {
|
||||||
|
const kebab = gare ? String(gare).trim().replace(/\\s+/g, '-').replace(/\\/g, '/') : '';
|
||||||
|
navigate(`${prefix}/select-afficheur?gare=${encodeURIComponent(kebab)}&mode=${mode}&type=${t.id}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WcsCardBody style={{ padding: '16px', display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<span style={{ fontSize: '32px', marginRight: '8px' }}>{t.icon}</span>
|
||||||
|
<WcsBadge className={t.tagType === 'retro' ? 'wcs-secondary' : 'wcs-success'}>{t.tag}</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '18px', margin: '0 0 4px 0', color: 'var(--wcs-primary)' }}>{t.label}</h2>
|
||||||
|
<div style={{ fontSize: '13px', fontWeight: 'bold', color: 'var(--wcs-primary)', marginBottom: '8px' }}>{t.sublabel}</div>
|
||||||
|
<p style={{ fontSize: '14px', color: 'var(--wcs-text-light)', flexGrow: 1, marginBottom: '16px' }}>{t.desc}</p>
|
||||||
|
|
||||||
|
<WcsButton className="wcs-secondary" style={{ width: '100%', marginTop: 'auto' }}>
|
||||||
|
Choisir ce style
|
||||||
|
</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ borderTop: '1px solid var(--wcs-light)', paddingTop: '16px' }}>
|
||||||
|
<WcsButton mode="clear" onClick={() => {
|
||||||
if (typeof onBack === 'function') {
|
if (typeof onBack === 'function') {
|
||||||
onBack();
|
onBack();
|
||||||
} else if (navigate) {
|
} else if (navigate) {
|
||||||
navigate(`${prefix}/${mode}`);
|
navigate(`${prefix}/${mode}`);
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
label="← Changer de gare"
|
← Changer de gare
|
||||||
/>
|
</WcsButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
+186
-375
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import WcsButton from '../components/WcsButton';
|
import { WcsCard, WcsCardBody, WcsButton, WcsBadge, WcsCheckbox, WcsSelect, WcsSelectOption, WcsInput, WcsHorizontalStepper } from 'wcs-react';
|
||||||
import { getSourceFromPath, getPlatformsForStation } from '../services/railwayService';
|
import { getSourceFromPath, getPlatformsForStation } from '../services/railwayService';
|
||||||
|
|
||||||
export default function SelectAfficheur({ onBack, navigate }) {
|
export default function SelectAfficheur({ onBack, navigate }) {
|
||||||
@@ -16,7 +16,6 @@ export default function SelectAfficheur({ onBack, navigate }) {
|
|||||||
const [manualPlatform, setManualPlatform] = useState('');
|
const [manualPlatform, setManualPlatform] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// État des pages sélectionnées pour les afficheurs AFL
|
|
||||||
const [aflPages, setAflPages] = useState({
|
const [aflPages, setAflPages] = useState({
|
||||||
horaires: true,
|
horaires: true,
|
||||||
infos: false,
|
infos: false,
|
||||||
@@ -25,8 +24,7 @@ export default function SelectAfficheur({ onBack, navigate }) {
|
|||||||
corresp: false
|
corresp: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Options spécifiques pour les afficheurs Classiques
|
const [extendedLines, setExtendedLines] = useState(0);
|
||||||
const [extendedLines, setExtendedLines] = useState(0); // 0, 1, 2, 3
|
|
||||||
const [showTime, setShowTime] = useState(false);
|
const [showTime, setShowTime] = useState(false);
|
||||||
|
|
||||||
const toggleAflPage = (key) => {
|
const toggleAflPage = (key) => {
|
||||||
@@ -43,9 +41,8 @@ export default function SelectAfficheur({ onBack, navigate }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decode and format station name for display
|
|
||||||
const decodedGare = gareParam
|
const decodedGare = gareParam
|
||||||
? decodeURIComponent(String(gareParam).replace(/\+/g, ' ')).replace(/-/g, ' ')
|
? decodeURIComponent(String(gareParam).replace(/\\+/g, ' ')).replace(/-/g, ' ')
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -129,398 +126,212 @@ export default function SelectAfficheur({ onBack, navigate }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{ text: decodedGare || 'Gare', button: { kind: 'Icon', iconName: 'check' }, complete: true },
|
||||||
|
{ text: typeParam.toUpperCase(), button: { kind: 'Icon', iconName: 'check' }, complete: true },
|
||||||
|
{ text: 'Options & Affichage', button: { kind: 'Text', text: '3' }, complete: false }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="launcher-container">
|
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '24px' }}>
|
||||||
{/* Stepper */}
|
<WcsHorizontalStepper steps={steps} currentStep={2} style={{ marginBottom: '24px' }} />
|
||||||
<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">
|
<WcsCard mode="raised">
|
||||||
{/* Header */}
|
<WcsCardBody>
|
||||||
<div className="launcher-header">
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
|
||||||
<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>
|
<div>
|
||||||
<span className="launcher-card-icon">🖥️</span>
|
<h1 style={{ margin: '0 0 8px 0', fontSize: '24px', color: 'var(--wcs-primary)' }}>Configuration de l'afficheur</h1>
|
||||||
<span className="launcher-card-tag launcher-card-tag--modern">Écran Général</span>
|
<p style={{ margin: 0, color: 'var(--wcs-text-light)' }}>
|
||||||
<h2 className="launcher-card-title">Afficheur Général (Hall)</h2>
|
Gare : <strong>{decodedGare}</strong> • Mode : <strong>{modeParam === 'arrivees' ? '🏁 Arrivées' : '🚆 Départs'}</strong> • Style : <strong>{typeParam.toUpperCase()}</strong>
|
||||||
<p className="launcher-card-desc">
|
|
||||||
Affiche la liste complète des circulations avec basculement automatique des pages.
|
|
||||||
</p>
|
</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>
|
</div>
|
||||||
|
<WcsBadge className={source === 'supabase' ? 'wcs-secondary' : 'wcs-primary'} shape="rounded">
|
||||||
<button
|
{source === 'supabase' ? '🗄️ BDD Ferrovia' : '🌐 SNCF GTFS'}
|
||||||
type="button"
|
</WcsBadge>
|
||||||
onClick={handleGeneral}
|
|
||||||
className="launcher-btn launcher-btn--primary"
|
|
||||||
>
|
|
||||||
🖥️ Lancer l'Afficheur Général
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Option 2 : Afficheur par Quai */}
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '24px', marginBottom: '24px' }}>
|
||||||
<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' }}>
|
<WcsCard mode="flat" style={{ border: '1px solid var(--wcs-light)' }}>
|
||||||
{loading ? (
|
<WcsCardBody style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<div style={{ fontSize: '14px', color: '#00439e', fontWeight: 600 }}>⏳ Détection des voies réelles...</div>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
) : (
|
<span style={{ fontSize: '24px', marginRight: '8px' }}>🖥️</span>
|
||||||
<div>
|
<WcsBadge className="wcs-info">Écran Général</WcsBadge>
|
||||||
{platforms.length > 0 ? (
|
</div>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<h2 style={{ fontSize: '20px', margin: '0 0 8px 0' }}>Afficheur Général (Hall)</h2>
|
||||||
<label style={{ display: 'block', fontSize: '13px', fontWeight: 700, color: '#002257', marginBottom: '6px' }}>
|
<p style={{ fontSize: '14px', color: 'var(--wcs-text-light)', marginBottom: '16px' }}>
|
||||||
Voies actives détectées :
|
Affiche la liste complète des circulations avec basculement automatique des pages.
|
||||||
</label>
|
</p>
|
||||||
<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>
|
{typeParam === 'afl' && (
|
||||||
<label style={{ display: 'block', fontSize: '13px', fontWeight: 700, color: '#002257', marginBottom: '6px' }}>
|
<div style={{ background: 'var(--wcs-light)', padding: '16px', borderRadius: '4px', marginBottom: '16px', flexGrow: 1 }}>
|
||||||
Ou saisie libre d'une voie :
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '16px' }}>
|
||||||
</label>
|
<span style={{ fontWeight: 'bold' }}>Pages incluses dans la boucle :</span>
|
||||||
<input
|
<div>
|
||||||
type="text"
|
<WcsButton mode="clear" size="s" onClick={() => selectAllAflPages(true)}>Tout cocher</WcsButton>
|
||||||
placeholder="Ex: A, B, 1, 2 bis..."
|
<WcsButton mode="clear" size="s" onClick={() => selectAllAflPages(false)}>Horaires seuls</WcsButton>
|
||||||
value={manualPlatform}
|
</div>
|
||||||
onChange={(e) => setManualPlatform(e.target.value)}
|
</div>
|
||||||
style={{
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
width: '100%',
|
<WcsCheckbox checked={aflPages.horaires} onWcsChange={() => toggleAflPage('horaires')}><strong>Page 1 & 2</strong> (Horaires)</WcsCheckbox>
|
||||||
padding: '12px',
|
<WcsCheckbox checked={aflPages.infos} onWcsChange={() => toggleAflPage('infos')}><strong>Informations</strong> (Trafic)</WcsCheckbox>
|
||||||
borderRadius: '8px',
|
<WcsCheckbox checked={aflPages.securite1} onWcsChange={() => toggleAflPage('securite1')}><strong>Sécurité 1</strong> (Bagages)</WcsCheckbox>
|
||||||
border: '2px solid #e2ebf6',
|
<WcsCheckbox checked={aflPages.securite2} onWcsChange={() => toggleAflPage('securite2')}><strong>Sécurité 2</strong> (Vigipirate)</WcsCheckbox>
|
||||||
fontSize: '15px',
|
<WcsCheckbox checked={aflPages.corresp} onWcsChange={() => toggleAflPage('corresp')}><strong>Correspondances</strong></WcsCheckbox>
|
||||||
boxSizing: 'border-box',
|
|
||||||
background: '#ffffff'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
{typeParam === 'classiques' && (
|
||||||
type="button"
|
<div style={{ background: 'var(--wcs-light)', padding: '16px', borderRadius: '4px', marginBottom: '16px', flexGrow: 1 }}>
|
||||||
onClick={handlePlatform}
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Options d'affichage :</p>
|
||||||
className="launcher-btn launcher-btn--success"
|
<div style={{ marginBottom: '16px' }}>
|
||||||
>
|
<label style={{ display: 'block', fontSize: '14px', marginBottom: '8px' }}>Lignes étendues :</label>
|
||||||
🚉 Lancer l'Afficheur de Quai
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
</button>
|
{[0, 1, 2, 3].map(num => (
|
||||||
|
<WcsButton
|
||||||
|
key={num}
|
||||||
|
mode={extendedLines === num ? 'solid' : 'stroked'}
|
||||||
|
size="s"
|
||||||
|
onClick={() => setExtendedLines(num)}
|
||||||
|
>
|
||||||
|
{num === 0 ? '0' : num}
|
||||||
|
</WcsButton>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<WcsCheckbox checked={showTime} onWcsChange={(e) => setShowTime(e.detail.checked)}><strong>Afficher l'heure & infos</strong></WcsCheckbox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<WcsButton className="wcs-primary" style={{ width: '100%', marginTop: 'auto' }} onClick={handleGeneral}>
|
||||||
|
Lancer l'Afficheur Général
|
||||||
|
</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
|
||||||
|
<WcsCard mode="flat" style={{ border: '1px solid var(--wcs-light)' }}>
|
||||||
|
<WcsCardBody style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<span style={{ fontSize: '24px', marginRight: '8px' }}>🚉</span>
|
||||||
|
<WcsBadge className="wcs-warning">Écran de Voie</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '20px', margin: '0 0 8px 0' }}>Afficheur par Quai</h2>
|
||||||
|
<p style={{ fontSize: '14px', color: 'var(--wcs-text-light)', marginBottom: '16px' }}>
|
||||||
|
Affiche le prochain train au départ/arrivée avec la liste déroulante des gares desservies sur la voie.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style={{ background: 'var(--wcs-light)', padding: '16px', borderRadius: '4px', marginBottom: '16px', flexGrow: 1 }}>
|
||||||
|
{loading ? (
|
||||||
|
<p style={{ fontWeight: 'bold', color: 'var(--wcs-primary)' }}>⏳ Détection des voies...</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{platforms.length > 0 && (
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<label style={{ display: 'block', fontWeight: 'bold', marginBottom: '8px', fontSize: '14px' }}>Voies actives détectées :</label>
|
||||||
|
<WcsSelect
|
||||||
|
value={selectedPlatform}
|
||||||
|
onWcsChange={(e) => {
|
||||||
|
setSelectedPlatform(e.detail.value);
|
||||||
|
setManualPlatform('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{platforms.map(p => (
|
||||||
|
<WcsSelectOption key={p} value={p}>Voie {p}</WcsSelectOption>
|
||||||
|
))}
|
||||||
|
</WcsSelect>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{ display: 'block', fontWeight: 'bold', marginBottom: '8px', fontSize: '14px' }}>Saisie libre d'une voie :</label>
|
||||||
|
<WcsInput
|
||||||
|
placeholder="Ex: A, B, 1, 2 bis..."
|
||||||
|
value={manualPlatform}
|
||||||
|
onWcsInput={(e) => setManualPlatform(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<WcsButton className="wcs-success" style={{ width: '100%', marginTop: 'auto' }} onClick={handlePlatform}>
|
||||||
|
Lancer l'Afficheur de Quai
|
||||||
|
</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
|
||||||
|
{typeParam === 'classiques' && (
|
||||||
|
<>
|
||||||
|
<WcsCard mode="flat" style={{ border: '1px solid var(--wcs-light)' }}>
|
||||||
|
<WcsCardBody style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<span style={{ fontSize: '24px', marginRight: '8px' }}>⚡</span>
|
||||||
|
<WcsBadge className="wcs-warning">Flash Info Trafic</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '20px', margin: '0 0 8px 0' }}>Flash Information</h2>
|
||||||
|
<p style={{ fontSize: '14px', color: 'var(--wcs-text-light)', marginBottom: '16px', flexGrow: 1 }}>
|
||||||
|
Écran dédié aux perturbations de circulation en temps réel et travaux de maintenance sur le réseau.
|
||||||
|
</p>
|
||||||
|
<WcsButton className="wcs-warning" style={{ width: '100%', marginTop: 'auto' }} onClick={() => {
|
||||||
|
const kebab = gareParam ? String(gareParam).trim() : '';
|
||||||
|
navigate(`${prefix}/afficheurs/classiques/flash-info?gare=${encodeURIComponent(kebab)}`);
|
||||||
|
}}>
|
||||||
|
Lancer le Flash Information
|
||||||
|
</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
|
||||||
|
<WcsCard mode="flat" style={{ border: '1px solid var(--wcs-light)' }}>
|
||||||
|
<WcsCardBody style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<span style={{ fontSize: '24px', marginRight: '8px' }}>ℹ️</span>
|
||||||
|
<WcsBadge className="wcs-info">Messages Gare</WcsBadge>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: '20px', margin: '0 0 8px 0' }}>Informations Voyageurs</h2>
|
||||||
|
<p style={{ fontSize: '14px', color: 'var(--wcs-text-light)', marginBottom: '16px', flexGrow: 1 }}>
|
||||||
|
Écran d'annonces et consignes dynamiques de la gare (Vigipirate, étiquetage des bagages, sécurité).
|
||||||
|
</p>
|
||||||
|
<WcsButton className="wcs-info" style={{ width: '100%', marginTop: 'auto' }} onClick={() => {
|
||||||
|
const kebab = gareParam ? String(gareParam).trim() : '';
|
||||||
|
navigate(`${prefix}/afficheurs/classiques/informations?gare=${encodeURIComponent(kebab)}`);
|
||||||
|
}}>
|
||||||
|
Lancer l'Afficheur Informations
|
||||||
|
</WcsButton>
|
||||||
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Options 3 & 4 pour le mode Classiques : Flash Info & Informations */}
|
{typeParam === 'afl' && (
|
||||||
{typeParam === 'classiques' && (
|
<div style={{ borderTop: '1px solid var(--wcs-light)', paddingTop: '24px', marginBottom: '24px' }}>
|
||||||
<>
|
<p style={{ fontWeight: 'bold', marginBottom: '12px' }}>Accès direct à une page fixe AFL :</p>
|
||||||
{/* Option 3 : Flash Information */}
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
<div className="launcher-select-card" style={{ cursor: 'default' }}>
|
<WcsButton mode="stroked" size="s" onClick={() => handleSinglePage('1')}>Page 1</WcsButton>
|
||||||
<div>
|
<WcsButton mode="stroked" size="s" onClick={() => handleSinglePage('2')}>Page 2</WcsButton>
|
||||||
<span className="launcher-card-icon">⚡</span>
|
<WcsButton mode="stroked" size="s" onClick={() => handleSinglePage('infos')}>Informations</WcsButton>
|
||||||
<span className="launcher-card-tag launcher-card-tag--retro" style={{ background: '#fab23e', color: '#00337f' }}>
|
<WcsButton mode="stroked" size="s" onClick={() => handleSinglePage('securite1')}>Sécurité (Bagages)</WcsButton>
|
||||||
Flash Info Trafic
|
<WcsButton mode="stroked" size="s" onClick={() => handleSinglePage('securite2')}>Sécurité (Vigipirate)</WcsButton>
|
||||||
</span>
|
<WcsButton mode="stroked" size="s" onClick={() => handleSinglePage('corresp')}>Correspondances</WcsButton>
|
||||||
<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>
|
</div>
|
||||||
|
</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 */}
|
<div style={{ borderTop: '1px solid var(--wcs-light)', paddingTop: '16px' }}>
|
||||||
{typeParam === 'afl' && (
|
<WcsButton mode="clear" onClick={() => {
|
||||||
<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') {
|
if (typeof onBack === 'function') {
|
||||||
onBack();
|
onBack();
|
||||||
} else if (typeof navigate === 'function') {
|
} else if (typeof navigate === 'function') {
|
||||||
navigate(`${prefix}/afficheurs?gare=${encodeURIComponent(gareParam)}&mode=${modeParam}`);
|
navigate(`${prefix}/afficheurs?gare=${encodeURIComponent(gareParam)}&mode=${modeParam}`);
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
label="← Retour au choix du style"
|
← Retour au choix du style
|
||||||
/>
|
</WcsButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</WcsCardBody>
|
||||||
|
</WcsCard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -11,14 +11,14 @@ const normalizeStr = (s) => {
|
|||||||
// Table des logos issue de public/type/data.json
|
// Table des logos issue de public/type/data.json
|
||||||
const LOGOS_DATA = [
|
const LOGOS_DATA = [
|
||||||
{ file: "logo-aleop.svg", slug: "aleop", name: "Aleop" },
|
{ file: "logo-aleop.svg", slug: "aleop", name: "Aleop" },
|
||||||
{ file: "logo-auvergerhonealpes.svg", slug: "auvergerhonealpes", name: "Auvergerhonealpes" },
|
{ file: "sncf-voyageurs-logo.png", slug: "auvergerhonealpes", name: "TER AURA" },
|
||||||
{ file: "logo-breizhgo.svg", slug: "breizhgo", name: "Breizhgo" },
|
{ file: "logo-breizhgo.svg", slug: "breizhgo", name: "Breizhgo" },
|
||||||
{ file: "logo-cfl.svg", slug: "cfl", name: "CFL" },
|
{ file: "logo-cfl.svg", slug: "cfl", name: "CFL" },
|
||||||
{ file: "logo-db.svg", slug: "db", name: "DB" },
|
{ file: "logo-db.svg", slug: "db", name: "DB" },
|
||||||
{ file: "logo-eurostar.svg", slug: "eurostar", name: "Eurostar" },
|
{ file: "logo-eurostar.svg", slug: "eurostar", name: "Eurostar" },
|
||||||
{ file: "logo-fluo.svg", slug: "fluo", name: "Fluo" },
|
{ file: "logo-fluo.svg", slug: "fluo", name: "Fluo" },
|
||||||
{ file: "logo-frecciarossa.svg", slug: "frecciarossa", name: "Frecciarossa" },
|
{ file: "logo-frecciarossa.svg", slug: "frecciarossa", name: "Frecciarossa" },
|
||||||
{ file: "logo-hautsdefrance.svg", slug: "hautsdefrance", name: "Hautsdefrance" },
|
{ file: "logo-hautsdefrance.svg", slug: "hautsdefrance", name: "TER HDF" },
|
||||||
{ file: "logo-ice.svg", slug: "ice", name: "ICE" },
|
{ file: "logo-ice.svg", slug: "ice", name: "ICE" },
|
||||||
{ file: "logo-intercites.svg", slug: "intercites", name: "Intercités" },
|
{ file: "logo-intercites.svg", slug: "intercites", name: "Intercités" },
|
||||||
{ file: "logo-ler.svg", slug: "ler", name: "Ler" },
|
{ file: "logo-ler.svg", slug: "ler", name: "Ler" },
|
||||||
@@ -43,7 +43,7 @@ const LOGOS_DATA = [
|
|||||||
{ file: "logo-sncf1985.svg", slug: "sncf1985", name: "Sncf1985" },
|
{ file: "logo-sncf1985.svg", slug: "sncf1985", name: "Sncf1985" },
|
||||||
{ file: "logo-sncf1992.svg", slug: "sncf1992", name: "Sncf1992" },
|
{ file: "logo-sncf1992.svg", slug: "sncf1992", name: "Sncf1992" },
|
||||||
{ file: "logo-teoz.svg", slug: "teoz", name: "Teoz" },
|
{ file: "logo-teoz.svg", slug: "teoz", name: "Teoz" },
|
||||||
{ file: "logo-ter.svg", slug: "ter", name: "TER" },
|
|
||||||
{ file: "logo-ter-rhone-alpes.svg", slug: "ter-rhone-alpes", name: "TER Rhone-Alpes" },
|
{ file: "logo-ter-rhone-alpes.svg", slug: "ter-rhone-alpes", name: "TER Rhone-Alpes" },
|
||||||
{ file: "logo-teralsace.svg", slug: "teralsace", name: "Ter Alsace" },
|
{ file: "logo-teralsace.svg", slug: "teralsace", name: "Ter Alsace" },
|
||||||
{ file: "logo-teraquitaine.svg", slug: "teraquitaine", name: "Ter Aquitaine" },
|
{ file: "logo-teraquitaine.svg", slug: "teraquitaine", name: "Ter Aquitaine" },
|
||||||
@@ -64,6 +64,7 @@ const LOGOS_DATA = [
|
|||||||
{ file: "logo-tramtrain.svg", slug: "tramtrain", name: "Tramtrain" },
|
{ file: "logo-tramtrain.svg", slug: "tramtrain", name: "Tramtrain" },
|
||||||
{ file: "logo-trenitalia.svg", slug: "trenitalia", name: "Trenitalia" },
|
{ file: "logo-trenitalia.svg", slug: "trenitalia", name: "Trenitalia" },
|
||||||
{ file: "logo-zou.svg", slug: "zou", name: "Zou" }
|
{ file: "logo-zou.svg", slug: "zou", name: "Zou" }
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const logoLookup = new Map();
|
const logoLookup = new Map();
|
||||||
@@ -85,7 +86,7 @@ LOGOS_DATA.forEach((item) => {
|
|||||||
*/
|
*/
|
||||||
export function getTrainLogoAndMeta(horaire) {
|
export function getTrainLogoAndMeta(horaire) {
|
||||||
if (!horaire) {
|
if (!horaire) {
|
||||||
return { logoSrc: '/type/logo-sncf.svg', typeLabel: 'TER', numTrain: '' };
|
return { logoSrc: '/type/sncf-voyageurs-logo.png', typeLabel: 'TER', numTrain: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const opRaw = String(
|
const opRaw = String(
|
||||||
@@ -152,7 +153,7 @@ export function getTrainLogoAndMeta(horaire) {
|
|||||||
} else if (opNorm.includes('renfe') || opNorm.includes('ave')) {
|
} else if (opNorm.includes('renfe') || opNorm.includes('ave')) {
|
||||||
matchedLogo = logoLookup.get('renfeave');
|
matchedLogo = logoLookup.get('renfeave');
|
||||||
} else if (opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus')) {
|
} else if (opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus')) {
|
||||||
matchedLogo = logoLookup.get('trackcar') || { file: 'track-car.svg', name: 'Car TER' };
|
matchedLogo = logoLookup.get('trackcar') || { file: 'sncf-voyageurs-logo.png', name: 'Car TER' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ export function getTrainLogoAndMeta(horaire) {
|
|||||||
} else if (opNorm.includes('lyria')) {
|
} else if (opNorm.includes('lyria')) {
|
||||||
typeLabel = 'TGV Lyria';
|
typeLabel = 'TGV Lyria';
|
||||||
} else if (opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus')) {
|
} else if (opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus')) {
|
||||||
typeLabel = 'AUTOCAR';
|
typeLabel = 'Car TER';
|
||||||
} else if (opNorm.includes('ter')) {
|
} else if (opNorm.includes('ter')) {
|
||||||
typeLabel = 'TER';
|
typeLabel = 'TER';
|
||||||
}
|
}
|
||||||
@@ -222,11 +223,11 @@ export function getTrainLogoAndMeta(horaire) {
|
|||||||
} else if ((p3 >= 860 && p3 <= 862) || (p4 >= 1675 && p4 <= 1684)) {
|
} else if ((p3 >= 860 && p3 <= 862) || (p4 >= 1675 && p4 <= 1684)) {
|
||||||
matchedBrandSlug = 'remi'; matchedBrandLabel = 'RÉMI';
|
matchedBrandSlug = 'remi'; matchedBrandLabel = 'RÉMI';
|
||||||
} else if ((p3 >= 863 && p3 <= 869) || (p4 >= 1685 && p4 <= 1709)) {
|
} else if ((p3 >= 863 && p3 <= 869) || (p4 >= 1685 && p4 <= 1709)) {
|
||||||
matchedBrandSlug = 'teraquitaine'; matchedBrandLabel = 'TER NOUVELLE-AQUITAINE';
|
matchedBrandSlug = 'teraquitaine'; matchedBrandLabel = 'TER NA';
|
||||||
} else if ((p3 >= 870 && p3 <= 872) || (p3 >= 876 && p3 <= 878) || (p4 >= 1710 && p4 <= 1719) || (p4 >= 1730 && p4 <= 1734)) {
|
} else if ((p3 >= 870 && p3 <= 872) || (p3 >= 876 && p3 <= 878) || (p4 >= 1710 && p4 <= 1719) || (p4 >= 1730 && p4 <= 1734)) {
|
||||||
matchedBrandSlug = 'lio'; matchedBrandLabel = 'liO';
|
matchedBrandSlug = 'lio'; matchedBrandLabel = 'liO';
|
||||||
} else if ((p3 >= 873 && p3 <= 875) || (p3 >= 882 && p3 <= 890) || (p4 >= 1720 && p4 <= 1729) || (p4 >= 1750 && p4 <= 1774) || (p4 >= 1790 && p4 <= 1799)) {
|
} else if ((p3 >= 873 && p3 <= 875) || (p3 >= 882 && p3 <= 890) || (p4 >= 1720 && p4 <= 1729) || (p4 >= 1750 && p4 <= 1774) || (p4 >= 1790 && p4 <= 1799)) {
|
||||||
matchedBrandSlug = 'ter-rhone-alpes'; matchedBrandLabel = 'TER AUVERGNE-RHÔNE-ALPES';
|
matchedBrandSlug = 'ter-rhone-alpes'; matchedBrandLabel = 'TER AURA';
|
||||||
} else if ((p3 >= 879 && p3 <= 881) || (p4 >= 1735 && p4 <= 1749)) {
|
} else if ((p3 >= 879 && p3 <= 881) || (p4 >= 1735 && p4 <= 1749)) {
|
||||||
matchedBrandSlug = 'zou'; matchedBrandLabel = 'ZOU !';
|
matchedBrandSlug = 'zou'; matchedBrandLabel = 'ZOU !';
|
||||||
} else if ((p3 >= 891 && p3 <= 895) || (p4 >= 1775 && p4 <= 1789)) {
|
} else if ((p3 >= 891 && p3 <= 895) || (p4 >= 1775 && p4 <= 1789)) {
|
||||||
@@ -235,13 +236,13 @@ export function getTrainLogoAndMeta(horaire) {
|
|||||||
|
|
||||||
if (matchedBrandSlug && matchedBrandLabel) {
|
if (matchedBrandSlug && matchedBrandLabel) {
|
||||||
typeLabel = matchedBrandLabel;
|
typeLabel = matchedBrandLabel;
|
||||||
if (!matchedLogo || matchedLogo.file === 'logo-ter.svg' || matchedLogo.file === 'logo-sncf.svg') {
|
if (!matchedLogo || matchedLogo.file === 'logo-sncf.svg' || matchedLogo.file === 'logo-sncf.svg') {
|
||||||
matchedLogo = logoLookup.get(matchedBrandSlug) || matchedLogo;
|
matchedLogo = logoLookup.get(matchedBrandSlug) || matchedLogo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const logoFile = (matchedLogo && matchedLogo.file) || 'logo-sncf.svg';
|
const logoFile = (matchedLogo && matchedLogo.file) || 'sncf-voyageurs-logo.png';
|
||||||
const logoSrc = `/type/${logoFile}`;
|
const logoSrc = `/type/${logoFile}`;
|
||||||
|
|
||||||
return { logoSrc, typeLabel, numTrain };
|
return { logoSrc, typeLabel, numTrain };
|
||||||
|
|||||||
Reference in new issue
Block a user