import { Box, styled, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material" import {CSSTransition, TransitionGroup} from 'react-transition-group' import {useQueries, useQuery} from "@tanstack/react-query"; import {useCallback, useEffect, useMemo, useRef, useState} from "react"; const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({ 'tbody &:nth-of-type(odd)': { backgroundColor: theme.palette.sncf[tabletype].light, }, 'th, &:nth-of-type(even)': { backgroundColor: theme.palette.sncf[tabletype].dark, }, // hide last border '&:last-child td, &:last-child th': { border: 0, }, })); function TrainsTable({station, date, time, tableType}) { return <>
} function TrainsTableHeader({tableType}) { return <> Train Heure Destination } function TrainsTableBody({station, date, time, tableType}) { const filterTime = useCallback((train) => { if (tableType === "departures") return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}` else return `${train.arrival_date}T${train.arrival_time_24h}` >= `${date}T${time}` }, [date, time, tableType]) const updateTrains = useCallback(() => { return fetch(`/api/station/next_${tableType}/?station_slug=${station.slug}&date=${date}&time=${time}&offset=${0}&limit=${20}`) .then(response => response.json()) .then(data => data.results) .then(data => [...data]) }, [station.id, date, time, tableType]) const trainsQuery = useQuery({ queryKey: ['trains', station.id, tableType], queryFn: updateTrains, enabled: !!station.id, }) const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data]) useEffect(() => { let validTrains = trains?.filter(filterTime) ?? [] if (trains?.length > 0 && validTrains.length < trains?.length) trainsQuery.refetch().then() }, [trains, filterTime, trainsQuery]) const nullRef = useRef(null) let table_rows = trains.map((train) => ) return <> {table_rows} } function TrainRow({train, tableType, date, time}) { const tripQuery = useQuery({ queryKey: ['trip', train.trip], queryFn: () => fetch(`/api/gtfs/trip/${train.trip}/`) .then(response => response.json()), enabled: !!train.trip, }) const trip = tripQuery.data ?? {} const routeQuery = useQuery({ queryKey: ['route', trip.route], queryFn: () => fetch(`/api/gtfs/route/${trip.route}/`) .then(response => response.json()), enabled: !!trip.route, }) const route = routeQuery.data ?? {} const trainType = getTrainType(train, trip, route) const backgroundColor = getBackgroundColor(train, trip, route) const textColor = getTextColor(train, trip, route) const trainTypeDisplay = getTrainTypeDisplay(trainType) const stopTimesQuery = useQuery({ queryKey: ['stop_times', trip.id], queryFn: () => fetch(`/api/gtfs/stop_time/?${new URLSearchParams({trip: trip.id, order: 'stop_sequence', limit: 1000})}`) .then(response => response.json()) .then(data => data.results), enabled: !!trip.id, }) const stopTimes = stopTimesQuery.data ?? [] const stopIds = stopTimes.map(stop_time => stop_time.stop) const stopQueries = useQueries({ queries: stopIds.map(stopId => ({ queryKey: ['stop', stopId], queryFn: () => fetch(`/api/gtfs/stop/${stopId}/`) .then(response => response.json()), enabled: !!stopId, })), }) const stops = stopTimes.map(((stopTime, i) => ({...stopTime, stop: stopQueries[i]?.data ?? {"name": "…"}}))) ?? [] let headline = stops[tableType === "departures" ? stops.length - 1 : 0]?.stop ?? {name: "Chargement…"} const realtimeTripQuery = useQuery({ queryKey: ['realtimeTrip', trip.id, date, time], queryFn: () => fetch(`/api/gtfs-rt/trip_update/${trip.id}/`) .then(response => response.json()), enabled: !!trip.id, }) const [realtimeTripData, setRealtimeTripData] = useState({}) useEffect(() => { if (realtimeTripQuery.data) setRealtimeTripData(realtimeTripQuery.data) }, [realtimeTripQuery.data]) const tripScheduleRelationship = realtimeTripData.schedule_relationship ?? 0 const realtimeQuery = useQuery({ queryKey: ['realtime', train.id, date, time], queryFn: () => fetch(`/api/gtfs-rt/stop_time_update/${train.id}/`) .then(response => response.json()), enabled: !!train.id, }) const [realtimeData, setRealtimeData] = useState({}) useEffect(() => { if (realtimeQuery.data) setRealtimeData(realtimeQuery.data) }, [realtimeQuery.data]) const stopScheduleRelationship = realtimeData.schedule_relationship ?? 0 const canceled = tripScheduleRelationship === 3 || stopScheduleRelationship === 1 const delay = tableType === "departures" ? realtimeData.departure_delay : realtimeData.arrival_delay const prettyDelay = delay && !canceled ? getPrettyDelay(delay) : "" const [prettyScheduleRelationship, scheduleRelationshipColor] = getPrettyScheduleRelationship(tripScheduleRelationship, stopScheduleRelationship) let stopsFilter if (canceled) stopsFilter = (stop_time) => true else if (tableType === "departures") stopsFilter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0 else stopsFilter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0 let stopsNames = stops.filter(stopsFilter).map(stopTime => stopTime?.stop.name ?? "").join(" > ") ?? "" return <>
{trainTypeDisplay}
{trip.short_name}
{trip.headsign}
{getDisplayTime(train, tableType)} {prettyDelay} {prettyScheduleRelationship} {headline.name} {stopsNames}
} function getTrainType(train, trip, route) { switch (route.gtfs_feed) { case "FR-SNCF-TGV": case "FR-SNCF-IC": case "FR-SNCF-TER": let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0] switch (trainType) { case "Train TER": return "TER" case "INTERCITES": return "INTER-CITÉS" case "INTERCITES de nuit": return "INTER-CITÉS de nuit" default: return trainType } case "FR-IDF-IDFM": case "FR-GES-CTS": return route.short_name case "FR-EUROSTAR": return "Eurostar" case "IT-FRA-TI": return "Trenitalia France" case "ES-RENFE": return "RENFE" case "AT-OBB": if (trip.short_name?.startsWith("NJ")) return "NJ" return "ÖBB" default: return trip.short_name?.split(" ")[0] } } function getTrainTypeDisplay(trainType) { switch (trainType) { case "TGV INOUI": return TGV INOUI case "OUIGO": return OUIGO case "ICE": return ICE case "Lyria": return Lyria case "TER": return TER case "Car TER": return
Car
TER
case "Eurostar": return Eurostar case "Trenitalia": case "Trenitalia France": return Frecciarossa case "RENFE": return RENFE case "NJ": return NightJet default: return trainType } } function getBackgroundColor(train, trip, route) { let trainType = getTrainType(train, trip, route) switch (trainType) { case "OUIGO": return "#0096CA" case "Eurostar": return "#00286A" case "NJ": return "#272759" default: if (route.color) return `#${route.color}` return "#FFFFFF" } } function getTextColor(train, trip, route) { if (route.text_color) return `#${route.text_color}` else { let trainType = getTrainType(train, trip, route) switch (trainType) { case "OUIGO": return "#FFFFFF" case "TGV INOUI": return "#9B2743" case "ICE": return "#B4B4B4" case "INTER-CITÉS": case "INTER-CITÉS de nuit": return "#404042" default: return "#000000" } } } function getDisplayTime(train, tableType) { let time = tableType === "departures" ? train.departure_time : train.arrival_time let day_split = time.split(' ') return day_split[day_split.length - 1].substring(0, 5) } function getPrettyDelay(delay) { let delay_split = delay.split(':') let hours = parseInt(delay_split[0]) let minutes = parseInt(delay_split[1]) let full_minutes = hours * 60 + minutes return full_minutes ? `+${full_minutes} min` : "À l'heure" } function getPrettyScheduleRelationship(tripScheduledRelationship, stopScheduledRelationship) { switch (tripScheduledRelationship) { case 1: return ["Ajouté", "#3ebb18"] case 3: return ["Supprimé", "#ff8701"] default: switch (stopScheduledRelationship) { case 1: return ["Supprimé", "#ff8701"] default: return ["", ""] } } } export default TrainsTable;