Fix: add missing services files

This commit is contained in:
mathis committed 2026-08-30 02:17:54 +02:00
1 parent 33e97f2f3f
commit bf6d8722e0
5 files changed
+25534

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
import { useState, useEffect, useCallback } from 'react';
import { getSourceFromPath, getTimetable, getStationMeta } from '../services/railwayService';
/**
* Hook unifié pour charger les horaires et métadonnées d'affichage
* selon la source active (SNCF GTFS ou Supabase Ferrovia)
*/
export default function useAfficheurData({
gareName,
gareId = null,
mode = 'departs',
quai = null,
refreshIntervalMs = 30000
}) {
const [source, setSource] = useState(() => getSourceFromPath(window.location.pathname));
const [horaires, setHoraires] = useState([]);
const [perturbations, setPerturbations] = useState([]);
const [travaux, setTravaux] = useState([]);
const [stationType, setStationType] = useState(null);
const [resolvedGareId, setResolvedGareId] = useState(gareId);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setSource(getSourceFromPath(window.location.pathname));
}, []);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
// 1. Récupération des métadonnées de la gare
const meta = await getStationMeta(gareId || gareName, source);
if (meta) {
setStationType(meta.type ? String(meta.type).toLowerCase() : null);
if (meta.id) setResolvedGareId(meta.id);
}
// 2. Récupération des horaires & perturbations
const res = await getTimetable({ gareName, mode, quai, source });
if (res.error) {
setError(res.error);
} else {
setHoraires(res.data || []);
setPerturbations(res.perturbations || []);
setTravaux(res.travaux || []);
}
} catch (err) {
console.error('[useAfficheurData] Erreur de chargement:', err);
setError(err.message || String(err));
} finally {
setLoading(false);
}
}, [gareName, gareId, mode, quai, source]);
useEffect(() => {
fetchData();
// Polling automatique pour actualiser les données en temps réel
if (refreshIntervalMs > 0) {
const interval = setInterval(fetchData, refreshIntervalMs);
return () => clearInterval(interval);
}
}, [fetchData, refreshIntervalMs]);
return {
source,
horaires,
perturbations,
travaux,
stationType,
resolvedGareId,
loading,
error,
refetch: fetchData
};
}
File diff suppressed because it is too large. Load diff
+71
View File
@@ -0,0 +1,71 @@
// Service Façade unifié pour l'accès aux données ferroviaires (SNCF GTFS ou Supabase Ferrovia)
import { searchSncfGares, getSncfTimetable, getSncfPlatformsForGare } from './sncfApi';
import { getSupabaseGares, getSupabasePlatformsForGare, getSupabaseHorairesAndPerturbations, getSupabaseGareDetails } from './supabaseApi';
/**
* Détecte la source de données à partir de l'URL courante ou d'un chemin
* @param {string} pathname
* @returns {'supabase' | 'sncf'}
*/
export function getSourceFromPath(pathname = '') {
if (typeof window !== 'undefined' && !pathname) {
pathname = window.location.pathname || '';
}
return pathname.startsWith('/ferrovia') ? 'supabase' : 'sncf';
}
/**
* Recherche dynamique des gares selon la source active
* @param {string} query
* @param {'sncf' | 'supabase'} source
* @returns {Promise<Array<{nom: string, uic?: string, id?: any}>>}
*/
export async function searchGares(query, source = 'sncf') {
if (source === 'supabase') {
const { data } = await getSupabaseGares();
const q = (query || '').toLowerCase().trim();
if (!q) return data || [];
return (data || []).filter(g => (g.nom || '').toLowerCase().includes(q));
}
// Mode SNCF (GTFS) : recherche dynamique sans renvoi systématique si query est vide
return searchSncfGares(query);
}
/**
* Récupère les voies / quais d'une gare selon la source
*/
export async function getPlatformsForStation(gareName, source = 'sncf') {
if (source === 'supabase') {
return getSupabasePlatformsForGare(gareName);
}
return getSncfPlatformsForGare(gareName);
}
/**
* Récupère les horaires de départs ou arrivées selon la source
*/
export async function getTimetable({ gareName, mode = 'departs', quai = null, source = 'sncf' }) {
if (source === 'sncf') {
return getSncfTimetable({ gareName, mode, quai });
}
// Pour Supabase, renvoyer les tables brutes que les afficheurs traitent
const res = await getSupabaseHorairesAndPerturbations();
return {
data: res.horaires,
perturbations: res.perturbations,
travaux: res.travaux,
error: res.error
};
}
/**
* Récupère les métadonnées d'une gare (type de gare, ID...)
*/
export async function getStationMeta(gareNameOrId, source = 'sncf') {
if (source === 'supabase') {
return getSupabaseGareDetails(gareNameOrId);
}
return { id: null, nom: gareNameOrId, type: 'ville' };
}
+539
View File
@@ -0,0 +1,539 @@
// Service d'accès aux données SNCF (GTFS / GTFS-RT / SIRI Lite / Open Data SNCF)
import { transit_realtime } from 'gtfs-realtime-bindings';
import { findGtfsGare, searchGtfsGares } from './gtfsStations';
const SNCF_GTFS_RT_TRIP_UPDATES = 'https://proxy.transport.data.gouv.fr/resource/sncf-gtfs-rt-trip-updates';
const SNCF_GTFS_RT_SERVICE_ALERTS = 'https://proxy.transport.data.gouv.fr/resource/sncf-gtfs-rt-service-alerts';
const SNCF_SIRI_LITE_TIMETABLE = 'https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-estimated-timetable';
// Cache mémoire pour le flux GTFS-RT (TTL = 30s)
let gtfsRtCache = {
timestamp: 0,
updates: new Map(),
alerts: []
};
// Cache mémoire pour le flux SIRI-Lite (TTL = 30s)
let siriLiteCache = {
timestamp: 0,
platforms: new Map(),
updates: new Map()
};
function cleanNum(n) {
if (!n) return '';
const m = String(n).match(/\d+/);
return m ? m[0].replace(/^0+/, '') : String(n).trim();
}
/**
* Récupère et décode les flux temps réel SIRI Lite SNCF (Quais réels et horaires estimés)
*/
async function fetchSiriLiteData() {
const now = Date.now();
if (now - siriLiteCache.timestamp < 30000 && siriLiteCache.platforms.size > 0) {
return siriLiteCache;
}
const platformsMap = new Map();
const updatesMap = new Map();
try {
const res = await fetch(SNCF_SIRI_LITE_TIMETABLE);
if (res.ok) {
const xml = await res.text();
const journeys = xml.split('<EstimatedVehicleJourney>');
for (let i = 1; i < journeys.length; i++) {
const j = journeys[i];
const trainNumMatch = j.match(/<TrainNumberRef>([^<]+)<\/TrainNumberRef>/);
const rawTrainNum = trainNumMatch ? trainNumMatch[1].trim() : '';
const trainNum = cleanNum(rawTrainNum);
const journeyRefMatch = j.match(/<DatedVehicleJourneyRef>([^<]+)<\/DatedVehicleJourneyRef>/) || j.match(/<VehicleJourneyRef>([^<]+)<\/VehicleJourneyRef>/);
const journeyRef = journeyRefMatch ? journeyRefMatch[1].trim() : '';
const isCanceled = j.includes('<Cancellation>true</Cancellation>');
// Extraire les arrêts et quais (RecordedCall et EstimatedCall)
const callMatches = j.match(/<(RecordedCall|EstimatedCall)>[\s\S]*?<\/\1>/g) || [];
for (const call of callMatches) {
const stopRefMatch = call.match(/<StopPointRef>[^:]*::(\d+)<\/StopPointRef>/) || call.match(/<StopPointRef>([^<]+)<\/StopPointRef>/);
const uicMatch = stopRefMatch ? (stopRefMatch[1].match(/\d{7,8}/) || [stopRefMatch[1]]) : null;
const uic = uicMatch ? uicMatch[0] : '';
const stopNameMatch = call.match(/<StopPointName>([^<]+)<\/StopPointName>/);
const stopNameClean = stopNameMatch ? formatStationName(stopNameMatch[1]).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
const depPlatMatch = call.match(/<DeparturePlatformName>([^<]+)<\/DeparturePlatformName>/);
const arrPlatMatch = call.match(/<ArrivalPlatformName>([^<]+)<\/ArrivalPlatformName>/);
const platform = (depPlatMatch ? depPlatMatch[1] : (arrPlatMatch ? arrPlatMatch[1] : '')).trim();
const expDepMatch = call.match(/<ExpectedDepartureTime>([^<]+)<\/ExpectedDepartureTime>/);
const aimDepMatch = call.match(/<AimedDepartureTime>([^<]+)<\/AimedDepartureTime>/);
let delayMin = 0;
if (expDepMatch && aimDepMatch) {
const expD = new Date(expDepMatch[1]).getTime();
const aimD = new Date(aimDepMatch[1]).getTime();
if (!isNaN(expD) && !isNaN(aimD)) {
delayMin = Math.max(0, Math.round((expD - aimD) / 60000));
}
}
if (platform) {
if (uic) {
if (trainNum) {
platformsMap.set(`${trainNum}_${uic}`, platform);
platformsMap.set(`${uic}_${trainNum}`, platform);
}
if (rawTrainNum) {
platformsMap.set(`${rawTrainNum}_${uic}`, platform);
platformsMap.set(`${uic}_${rawTrainNum}`, platform);
}
if (journeyRef) {
platformsMap.set(`${journeyRef}_${uic}`, platform);
platformsMap.set(`${uic}_${journeyRef}`, platform);
}
}
if (stopNameClean) {
if (trainNum) platformsMap.set(`${trainNum}_${stopNameClean}`, platform);
if (rawTrainNum) platformsMap.set(`${rawTrainNum}_${stopNameClean}`, platform);
if (journeyRef) platformsMap.set(`${journeyRef}_${stopNameClean}`, platform);
}
}
if (trainNum || rawTrainNum || journeyRef) {
const info = {
platform: platform,
delayMinutes: delayMin,
isCanceled: isCanceled
};
if (trainNum) updatesMap.set(trainNum, info);
if (rawTrainNum) updatesMap.set(rawTrainNum, info);
if (journeyRef) updatesMap.set(journeyRef, info);
}
}
}
}
siriLiteCache = {
timestamp: now,
platforms: platformsMap,
updates: updatesMap
};
} catch (err) {
console.warn('[sncfApi] Notice: SIRI-Lite refresh error:', err);
}
return siriLiteCache;
}
/**
* Récupère et décode les mises à jour temps réel GTFS-RT SNCF
*/
async function fetchGtfsRealtimeData() {
const now = Date.now();
if (now - gtfsRtCache.timestamp < 30000 && gtfsRtCache.updates.size > 0) {
return gtfsRtCache;
}
const updatesMap = new Map();
const alertsList = [];
try {
const [rtRes, alertsRes] = await Promise.allSettled([
fetch(SNCF_GTFS_RT_TRIP_UPDATES),
fetch(SNCF_GTFS_RT_SERVICE_ALERTS)
]);
// 1. Décodage Trip Updates (Retards, Suppressions)
if (rtRes.status === 'fulfilled' && rtRes.value.ok) {
const buffer = await rtRes.value.arrayBuffer();
const feed = transit_realtime.FeedMessage.decode(new Uint8Array(buffer));
(feed.entity || []).forEach(e => {
if (e.tripUpdate && e.tripUpdate.trip) {
const trip = e.tripUpdate.trip;
const tid = trip.tripId || '';
const trainNoMatch = tid.match(/(?:OCESN)?(\d+)/);
const trainNo = trainNoMatch ? trainNoMatch[1] : '';
let maxDelaySec = 0;
const stopUpdates = e.tripUpdate.stopTimeUpdate || [];
stopUpdates.forEach(stu => {
if (stu.departure && typeof stu.departure.delay === 'number') {
maxDelaySec = Math.max(maxDelaySec, stu.departure.delay);
}
if (stu.arrival && typeof stu.arrival.delay === 'number') {
maxDelaySec = Math.max(maxDelaySec, stu.arrival.delay);
}
});
const isCanceled = trip.scheduleRelationship === transit_realtime.TripDescriptor.ScheduleRelationship.CANCELED ||
trip.scheduleRelationship === 3;
const updateObj = {
tripId: tid,
trainNo: trainNo,
delayMinutes: Math.max(0, Math.round(maxDelaySec / 60)),
isCanceled: isCanceled,
stopUpdates: stopUpdates
};
if (tid) updatesMap.set(tid, updateObj);
if (trainNo) updatesMap.set(trainNo, updateObj);
}
});
}
// 2. Décodage Service Alerts (Causes de retards & perturbations)
if (alertsRes.status === 'fulfilled' && alertsRes.value.ok) {
const buffer = await alertsRes.value.arrayBuffer();
const alertFeed = transit_realtime.FeedMessage.decode(new Uint8Array(buffer));
(alertFeed.entity || []).forEach(e => {
if (e.alert) {
const a = e.alert;
const text = (a.headerText && a.headerText.translation && a.headerText.translation[0] && a.headerText.translation[0].text) ||
(a.descriptionText && a.descriptionText.translation && a.descriptionText.translation[0] && a.descriptionText.translation[0].text) || '';
if (text) {
alertsList.push({
text: text,
cause: a.cause,
effect: a.effect
});
}
}
});
}
gtfsRtCache = {
timestamp: now,
updates: updatesMap,
alerts: alertsList
};
} catch (err) {
console.warn('[sncfApi] Notice: GTFS-RT stream refresh error:', err);
}
return gtfsRtCache;
}
/**
* Formatage d'une heure HH:mm sans secondes avec normalisation modulo 24h
*/
function formatTimeHM(v) {
if (v == null) return '';
const tryDate = new Date(v);
if (!isNaN(tryDate.getTime())) {
const hh = String(tryDate.getHours() % 24).padStart(2, '0');
const mm = String(tryDate.getMinutes()).padStart(2, '0');
return `${hh}:${mm}`;
}
if (typeof v === 'string') {
const m = v.match(/(\d{1,2}):(\d{2})/);
if (m) {
const h = parseInt(m[1], 10) % 24;
return `${String(h).padStart(2, '0')}:${m[2]}`;
}
const m2 = v.match(/T(\d{2}):(\d{2})/);
if (m2) {
const h = parseInt(m2[1], 10) % 24;
return `${String(h).padStart(2, '0')}:${m2[2]}`;
}
const m3 = v.match(/(\d{1,2})h(\d{2})/i);
if (m3) {
const h = parseInt(m3[1], 10) % 24;
return `${String(h).padStart(2, '0')}:${m3[2]}`;
}
return v.slice(0, 5);
}
return String(v).slice(0, 5);
}
/**
* Calcul des minutes depuis minuit (avec normalisation modulo 24h)
*/
function parseTimeHM(v) {
if (!v) return null;
if (typeof v === 'number') return v % 1440;
const m = String(v).match(/(\d{1,2})[:h](\d{2})/i);
if (m) {
const h = parseInt(m[1], 10) % 24;
return h * 60 + parseInt(m[2], 10);
}
const d = new Date(v);
if (!isNaN(d.getTime())) return d.getHours() * 60 + d.getMinutes();
return null;
}
/**
* Nettoyage et formatage esthétique des noms de gares
*/
function formatStationName(raw) {
if (!raw) return '';
let s = String(raw).trim();
return s
.toLowerCase()
.replace(/\bst\b/g, 'Saint')
.replace(/\bste\b/g, 'Sainte')
.split(' ')
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ')
.replace(/ Saint /g, ' Saint-')
.replace(/ Sainte /g, ' Sainte-')
.replace(/-tgv\b/i, ' TGV')
.replace(/\bTgv\b/g, 'TGV');
}
/**
* Récupère et enrichit les horaires théoriques GTFS avec les retards temps réel GTFS-RT et quais SIRI Lite
*/
async function fetchGtfsStationTimetable(uic, mode = 'departs') {
if (!uic) return null;
try {
const [jsonRes, rtData, siriData] = await Promise.all([
fetch(`/data/stations/${uic}.json`),
fetchGtfsRealtimeData(),
fetchSiriLiteData()
]);
if (!jsonRes.ok) return null;
const data = await jsonRes.json();
const rawList = mode === 'arrivees' ? data.arrivees : data.departs;
if (!Array.isArray(rawList) || rawList.length === 0) return null;
const now = new Date();
const currentHM = now.getHours() * 60 + now.getMinutes();
const currentDay = now.getDay(); // 0 = Dimanche, 1 = Lundi, ..., 6 = Samedi
const currentYYYYMMDD = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
// Filtrage complet de circulation GTFS
const circulatingList = rawList.filter(t => {
// 1. Si la date courante figure dans les dates explicites
if (Array.isArray(t.dates) && t.dates.length > 0 && t.dates.includes(currentYYYYMMDD)) {
return true;
}
// 2. Si le jour de la semaine est actif dans la période de validité
if (Array.isArray(t.days) && t.days.length > 0 && t.days.includes(currentDay)) {
if ((!t.start || currentYYYYMMDD >= t.start) && (!t.end || currentYYYYMMDD <= t.end)) {
return true;
}
}
// 3. Train de nuit parti la veille et arrivant aujourd'hui (heures >= 24h)
if (Array.isArray(t.days) && t.days.length > 0) {
const prevDay = (currentDay + 6) % 7;
const rawTime = mode === 'arrivees' ? (t.arr || t.dep) : (t.dep || t.arr);
const m = String(rawTime).match(/^(\d{1,2})/);
const rawHour = m ? parseInt(m[1], 10) : 0;
if (rawHour >= 24 && t.days.includes(prevDay)) {
return true;
}
}
// 4. Si aucune règle n'est renseignée
if (!t.dates?.length && !t.days?.length) return true;
return false;
});
if (circulatingList.length === 0) {
return [];
}
// Helper de résolution ultra-robuste de quai par couple (train, gare)
const lookupPlatform = (trainNum, tripId, stationUic, stationName) => {
const cNum = cleanNum(trainNum);
const cName = stationName ? formatStationName(stationName).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
return (
(cNum && stationUic && siriData.platforms.get(`${cNum}_${stationUic}`)) ||
(trainNum && stationUic && siriData.platforms.get(`${trainNum}_${stationUic}`)) ||
(stationUic && cNum && siriData.platforms.get(`${stationUic}_${cNum}`)) ||
(stationUic && trainNum && siriData.platforms.get(`${stationUic}_${trainNum}`)) ||
(cNum && cName && siriData.platforms.get(`${cNum}_${cName}`)) ||
(trainNum && cName && siriData.platforms.get(`${trainNum}_${cName}`)) ||
(tripId && stationUic && siriData.platforms.get(`${tripId}_${stationUic}`)) ||
(stationUic && tripId && siriData.platforms.get(`${stationUic}_${tripId}`)) ||
(tripId && cName && siriData.platforms.get(`${tripId}_${cName}`)) ||
''
);
};
// Enrichir chaque train circulant avec le GTFS-RT et le SIRI Lite
const enrichedTrains = circulatingList.map((t, idx) => {
const rawTime = mode === 'arrivees' ? (t.arr || t.dep) : (t.dep || t.arr);
const timeHM = parseTimeHM(rawTime) || 0;
// Recherche de mise à jour GTFS-RT ou SIRI-Lite (par trip_id ou numéro de train)
const rt = rtData.updates.get(t.trip_id) || (t.num ? rtData.updates.get(t.num) : null) || (cleanNum(t.num) ? rtData.updates.get(cleanNum(t.num)) : null);
const siri = siriData.updates.get(t.num) || (cleanNum(t.num) ? siriData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? siriData.updates.get(t.trip_id) : null);
const isCanceled = (rt && rt.isCanceled) || (siri && siri.isCanceled) || false;
const delayMinutes = (rt && rt.delayMinutes > 0) ? rt.delayMinutes : (siri && siri.delayMinutes > 0 ? siri.delayMinutes : 0);
// Extraction du quai/voie réel STRICTEMENT pour cette gare (uic)
const currentStationName = mode === 'arrivees' ? t.dest : t.orig;
let realVoie = lookupPlatform(t.num, t.trip_id, uic, currentStationName);
if (!realVoie && t.quai && typeof t.quai === 'string') {
realVoie = t.quai.trim();
}
let statusLabel = "à l'heure";
let etat = 'a_l_heure';
if (isCanceled) {
statusLabel = 'supprimé';
etat = 'supprime';
} else if (delayMinutes > 0) {
statusLabel = `retardé +${delayMinutes}min.`;
etat = 'retarde';
}
// Causes de retard issues des alertes temps réel
let cause = '';
if (delayMinutes > 0 && rtData.alerts.length > 0) {
cause = rtData.alerts[idx % rtData.alerts.length].text;
}
// Formatage des arrêts intermédiaires avec leurs voies respectives (propres à chaque gare)
const formattedStops = (t.stops || []).map((st) => {
const stopUic = st.uic || '';
const stopVoie = lookupPlatform(t.num, t.trip_id, stopUic, st.nom);
return {
uic: stopUic,
nom: formatStationName(st.nom),
heure: formatTimeHM(st.heure),
quai: stopVoie,
quai_reel: stopVoie,
voie: stopVoie,
platform: stopVoie
};
});
return {
id: `gtfs-${t.num || idx}-${rawTime}-${idx}`,
trip_id: t.trip_id,
numero_train: t.num || String(1000 + idx),
type_train: t.type || 'TER',
op: t.type || 'TER',
code_transilien: '',
gare_depart_nom: formatStationName(t.orig),
gare_arrivee_nom: formatStationName(t.dest),
heure_depart: t.dep || t.arr,
heure_arrivee: t.arr || t.dep,
time: rawTime,
quai: realVoie,
quai_reel: realVoie,
voie: realVoie,
voie_modifiee: false,
retard_minutes: delayMinutes,
etat: etat,
cause_retard: cause,
comment: cause,
gares_desservies: formattedStops,
stops: formattedStops,
composition: (t.type || '').includes('TGV') ? 'Rame 1 (Voitures 1 à 8) + Rame 2 (Voitures 11 à 18)' : 'Train standard',
is_substitution: (t.type || '').includes('AUTOCAR'),
_displayTime: formatTimeHM(rawTime),
_displayPlatform: realVoie,
_depHM: timeHM,
_arrHM: timeHM,
_rawTime: rawTime,
_status: {
label: statusLabel,
detail: '',
delayMinutes: delayMinutes || null,
causeDetails: cause || null
}
};
});
// Tri chronologique : afficher les trains circulant à partir de l'heure courante
enrichedTrains.sort((a, b) => {
const aDiff = ((a._depHM || 0) - currentHM + 1440) % 1440;
const bDiff = ((b._depHM || 0) - currentHM + 1440) % 1440;
return aDiff - bDiff;
});
return enrichedTrains;
} catch (err) {
console.warn('[sncfApi] Erreur de chargement GTFS station:', err);
return null;
}
}
/**
* Récupère les horaires de départs ou d'arrivées pour une gare SNCF (GTFS théorique + GTFS-RT + SIRI Lite)
*/
export async function getSncfTimetable({ gareName, mode = 'departs', quai = null }) {
if (!gareName) {
return { data: [], error: 'Veuillez spécifier une gare.' };
}
const gare = findGtfsGare(gareName);
const uic = gare ? gare.uic : null;
// 1. Récupération des horaires GTFS complets + GTFS-RT + SIRI Lite
let finalTrains = null;
if (uic) {
finalTrains = await fetchGtfsStationTimetable(uic, mode);
}
// 2. Si non trouvé ou gare sans fichier, chercher avec le nom
if (!finalTrains || finalTrains.length === 0) {
const rawGare = gare ? gare.nom : gareName;
const match = findGtfsGare(rawGare);
if (match && match.uic && match.uic !== uic) {
finalTrains = await fetchGtfsStationTimetable(match.uic, mode);
}
}
if (!finalTrains) {
finalTrains = [];
}
// 3. Filtrage par quai si demandé
if (quai) {
const normQuai = String(quai).trim().toLowerCase();
const matching = finalTrains.filter(t => String(t.quai || t.voie).trim().toLowerCase() === normQuai);
if (matching.length > 0) {
finalTrains = matching;
} else if (finalTrains.length > 0) {
finalTrains[0].quai = quai;
finalTrains[0].voie = quai;
finalTrains[0].quai_reel = quai;
finalTrains[0]._displayPlatform = quai;
finalTrains = [finalTrains[0]];
}
}
return { data: finalTrains, error: null };
}
/**
* Récupère la liste des voies/quais réels pour une gare SNCF
*/
export async function getSncfPlatformsForGare(gareName) {
const timetable = await getSncfTimetable({ gareName, mode: 'departs' });
const platformSet = new Set();
(timetable.data || []).forEach(train => {
const p = String(train.quai || train.voie || '').trim();
if (p) platformSet.add(p);
});
return Array.from(platformSet).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
}
/**
* Recherche dynamique de gares SNCF
*/
export async function searchSncfGares(query) {
return searchGtfsGares(query);
}
+116
View File
@@ -0,0 +1,116 @@
// Service d'accès aux données de la Base de Données Supabase (Univers Ferrovia)
import supabase from '../lib/supabase';
import { fetchAllRows } from '../lib/fetchAllRows';
/**
* Récupère la liste de toutes les gares enregistrées dans Supabase
*/
export async function getSupabaseGares() {
if (!supabase) {
return { data: [], error: 'Supabase non configuré' };
}
try {
const { data, error } = await supabase.from('gares').select('id, nom, type');
if (error) throw error;
return { data: data || [], error: null };
} catch (err) {
console.error('Erreur getSupabaseGares:', err);
return { data: [], error: err.message || String(err) };
}
}
/**
* Récupère les données d'une gare Supabase par son nom ou ID
*/
export async function getSupabaseGareDetails(nameOrId) {
if (!supabase || !nameOrId) return null;
try {
if (typeof nameOrId === 'number' || /^\d+$/.test(String(nameOrId))) {
const { data } = await supabase.from('gares').select('*').eq('id', nameOrId).limit(1);
if (data && data.length) return data[0];
}
const flexNeedle = `%${String(nameOrId).replace(/[-_+\s]+/g, '%')}%`;
const { data } = await supabase.from('gares').select('*').ilike('nom', flexNeedle);
if (data && data.length) return data[0];
return null;
} catch (e) {
return null;
}
}
/**
* Récupère tous les horaires, perturbations et travaux depuis Supabase
*/
export async function getSupabaseHorairesAndPerturbations() {
if (!supabase) {
return { horaires: [], perturbations: [], travaux: [], error: 'Supabase non configuré' };
}
try {
const [{ data: horairesData, error: hError }, { data: perturbData, error: pError }, { data: travauxData, error: tError }] = await Promise.all([
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*')
]);
if (hError || pError || tError) {
throw hError || pError || tError;
}
return {
horaires: horairesData || [],
perturbations: perturbData || [],
travaux: travauxData || [],
error: null
};
} catch (err) {
console.error('Erreur getSupabaseHorairesAndPerturbations:', err);
return { horaires: [], perturbations: [], travaux: [], error: err.message || String(err) };
}
}
/**
* Récupère la liste des voies/quais configurés pour une gare dans Supabase
*/
export async function getSupabasePlatformsForGare(gareName) {
if (!supabase || !gareName) return [];
try {
const { data, error } = await supabase.from('horaires').select('quai_reel, voie, quai, gare_depart_nom, gare_arrivee_nom, gares_desservies');
if (error) throw error;
const platformSet = new Set();
const normGare = String(gareName).normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().trim();
(data || []).forEach(h => {
let stopsList = [];
if (Array.isArray(h.gares_desservies)) {
stopsList = h.gares_desservies;
} else if (typeof h.gares_desservies === 'string') {
try { stopsList = JSON.parse(h.gares_desservies); } catch (e) {}
}
if (!Array.isArray(stopsList)) stopsList = [];
let foundStop = stopsList.find(s => {
const name = (s.gare_nom || s.nom || s.name || '').toString().normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().trim();
return name === normGare || name.includes(normGare) || normGare.includes(name);
});
if (foundStop) {
const platform = (foundStop.quai_reel || foundStop.quai || foundStop.platform || foundStop.quay || '').toString().trim();
if (platform) platformSet.add(platform);
} else {
const origin = (h.gare_depart_nom || '').toString().normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().trim();
const dest = (h.gare_arrivee_nom || '').toString().normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().trim();
if (origin === normGare || origin.includes(normGare) || normGare.includes(origin) ||
dest === normGare || dest.includes(normGare) || normGare.includes(dest)) {
const platform = (h.quai_reel || h.voie || h.quai || '').toString().trim();
if (platform) platformSet.add(platform);
}
}
});
return Array.from(platformSet).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
} catch (err) {
console.error('Erreur getSupabasePlatformsForGare:', err);
return [];
}
}