jQuery(document).ready(function($) { const RESULTS_CONTAINER_SELECTOR = '.sncf-board-container'; // Rechargement toutes les 90 secondes (1 min 30 s) const AUTO_REFRESH_SECONDS = 90; let allJourneys = []; // =============================================== // FONCTIONS D'EXTRACTION DE DONNÉES // =============================================== /** Extrait le mode (TER/Transilien N/Train) */ function getTrainMode(journey) { if (!journey.sections || journey.sections.length === 0) return 'Train'; const firstPublicSection = journey.sections.find(s => s.type === 'public_transport'); if (!firstPublicSection || !firstPublicSection.display_informations) return 'Train'; const di = firstPublicSection.display_informations; const lineName = di.label ? di.label.toUpperCase() : (di.code ? di.code.toUpperCase() : ''); const network = di.network ? di.network.toLowerCase() : ''; const commercialMode = di.commercial_mode ? di.commercial_mode.toLowerCase() : ''; // VÉRIFICATION 1 : TER if (network.includes('ter') || commercialMode.includes('ter')) { return 'TER'; } // VÉRIFICATION 2 : Transilien (Ligne N) if (network.includes('transilien') || lineName === 'N' || di.code === 'N') { return 'Transilien N'; } // VÉRIFICATION 3 : Autres trains if (commercialMode.includes('train')) { return 'Train'; } return 'Train'; } /** Extrait l'heure réelle, le retard et le statut. */ function getTimeAndStatus(journey) { if (!journey.departure_date_time) return { time: 'N/A', delayMin: 0, leftStatus: 'N/A' }; const dtReal = journey.departure_date_time; const firstPublicSection = journey.sections.find(s => s.type === 'public_transport'); const firstStop = firstPublicSection?.stop_date_times?.[0]; const dtBase = firstStop?.base_departure_date_time || dtReal; const formatTime = (dt) => Date.parse(dt.replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/, '$1-$2-$3T$4:$5:$6')); const realTime = formatTime(dtReal); const baseTime = formatTime(dtBase); const timeStr = dtReal.substring(9, 15); const departureTime = timeStr.substring(0, 2) + ':' + timeStr.substring(2, 4); let delayMin = 0; if (realTime && baseTime) { delayMin = Math.round((realTime - baseTime) / 60000); } let leftStatus = "à l'heure"; if (delayMin > 0) { leftStatus = `retard ${delayMin} min`; } else if (delayMin < 0) { leftStatus = `avance ${Math.abs(delayMin)} min`; } return { time: departureTime, delayMin: delayMin, leftStatus: leftStatus }; } /** Extrait la voie (quai) et sa source. */ function getPlatform(journey) { if (!journey.sections || journey.sections.length === 0) return { platform: 'NC', source: 'none' }; const firstPublicSection = journey.sections.find(s => s.type === 'public_transport'); if (!firstPublicSection) return { platform: 'NC', source: 'none' }; let platform = firstPublicSection.from && firstPublicSection.from.platform ? firstPublicSection.from.platform.trim() : ''; if (!platform && firstPublicSection.stop_date_times) { const firstStop = firstPublicSection.stop_date_times[0]; if (firstStop && firstStop.platform) { platform = firstStop.platform.trim(); } } if (!platform && firstPublicSection.stop_date_times) { const firstStop = firstPublicSection.stop_date_times[0]; const pubSection = firstStop.stop_point ? firstStop.stop_point.administrative_regions : null; if (pubSection && pubSection.length > 0 && pubSection[0].platform) { platform = pubSection[0].platform.trim(); } } if (platform && platform !== '-' && platform !== ' ') { return { platform: platform, source: 'confirmed' }; } return { platform: 'NC', source: 'none' }; } /** Extrait la taille du train (Long/Court). */ function getTrainBadge(journey) { if (!journey.sections || journey.sections.length === 0) return 'Direct'; const firstPublicSection = journey.sections.find(s => s.type === 'public_transport'); if (!firstPublicSection || !firstPublicSection.display_informations) return 'Direct'; const di = firstPublicSection.display_informations; if (di.description) { const desc = di.description.toLowerCase(); if (desc.includes('court') && !desc.includes('long')) return 'Court'; if (desc.includes('long') && !desc.includes('court')) return 'Long'; } if (di.label) { const label = di.label.toLowerCase(); if (label.includes('court')) return 'Court'; if (label.includes('long')) return 'Long'; } return 'Direct'; } /** Formate la durée en heures et minutes */ function formatDuration(durationSeconds) { const h = Math.floor(durationSeconds / 3600); const m = Math.round((durationSeconds % 3600) / 60); if (h > 0) return `${h}h ${m}min`; return `${m} min`; } /** Construit l'objet de données de la ligne à afficher */ function mapJourneyToRow(journey) { const timeStatus = getTimeAndStatus(journey); const platformInfo = getPlatform(journey); if (!journey.sections || journey.sections.length === 0) { return { time: timeStatus.time, dest: 'Destination inconnue', trainNo: '', line: '', mode: 'Train', leftStatus: timeStatus.leftStatus, delayMin: timeStatus.delayMin, platform: 'NC', platformSource: 'none', badge: 'Direct', duration: '' }; } const firstPublicSection = journey.sections.find(s => s.type === 'public_transport'); const di = firstPublicSection ? firstPublicSection.display_informations : null; return { time: timeStatus.time, dest: di && di.headsign ? di.headsign : 'Destination inconnue', trainNo: di && di.headsign ? di.headsign : '', line: di && di.label ? di.label : '', mode: getTrainMode(journey), leftStatus: timeStatus.leftStatus, delayMin: timeStatus.delayMin, platform: platformInfo.platform, platformSource: platformInfo.source, badge: getTrainBadge(journey), duration: formatDuration(journey.duration) }; } // Fonction de filtrage neutre (accepte tous les trajets trouvés) function isAllowedDestination(journey) { return true; } // =============================================== // FILTRAGE ET RENDU // =============================================== /** Filtre les trajets selon le bouton sélectionné */ function applyClientFilter(filterValue) { const filtered = allJourneys.filter(row => { if (filterValue === 'all') return true; if (filterValue === 'transilien' && row.mode === 'Transilien N') return true; if (filterValue === 'ter' && row.mode === 'TER') return true; return false; }); renderBoard(filtered); } /** Génère et affiche la structure du panneau HTML */ function renderBoard(rows) { const container = $(RESULTS_CONTAINER_SELECTOR); let html = ''; if (rows.length === 0) { html += '