diff --git a/.env b/.env
new file mode 100644
index 0000000..7e44882
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+REACT_APP_MOTIS_SERVER=https://motis.luemy.eu
diff --git a/src/App.js b/src/App.js
index b7a77f4..d6b92aa 100644
--- a/src/App.js
+++ b/src/App.js
@@ -6,10 +6,9 @@ import {frFR, LocalizationProvider} from "@mui/x-date-pickers"
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
import 'dayjs/locale/fr'
import './App.css'
-import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
-import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister";
-import {persistQueryClient} from "@tanstack/react-query-persist-client";
-import Home from "./Home";
+import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
+import Home from "./Home"
+import dayjs from "dayjs"
function App() {
const router = createBrowserRouter([
@@ -18,7 +17,7 @@ function App() {
element: ,
},
{
- path: "/station/:theme/:stationSlug",
+ path: "/station/:theme/:stationId",
element:
}
])
@@ -54,14 +53,7 @@ function App() {
},
})
- const localStoragePersister = createSyncStoragePersister({
- storage: window.localStorage,
- })
-
- persistQueryClient({
- queryClient,
- persister: localStoragePersister,
- })
+ dayjs.locale('fr')
return <>
diff --git a/src/AutocompleteStation.jsx b/src/AutocompleteStation.jsx
index 84b9ef3..1beeed4 100644
--- a/src/AutocompleteStation.jsx
+++ b/src/AutocompleteStation.jsx
@@ -1,9 +1,8 @@
import {Autocomplete, TextField} from "@mui/material";
-import {useRef, useState} from "react";
+import {useState} from "react";
function AutocompleteStation(params) {
const [options, setOptions] = useState([])
- const previousController = useRef()
function onInputChange(event, value) {
if (!value) {
@@ -11,17 +10,9 @@ function AutocompleteStation(params) {
return
}
- if (previousController.current)
- previousController.current.abort()
-
- const controller = new AbortController()
- const signal = controller.signal
- previousController.current = controller
- fetch("/api/core/station/?search=" + value, {signal})
+ fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`)
.then(response => response.json())
- .then(data => data.results)
.then(setOptions)
- .catch()
}
return <>
@@ -29,7 +20,7 @@ function AutocompleteStation(params) {
id="stop"
options={options}
onInputChange={onInputChange}
- filterOptions={(x) => x}
+ filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))}
getOptionKey={option => option.id}
getOptionLabel={option => option.name}
groupBy={option => getOptionGroup(option)}
@@ -40,7 +31,7 @@ function AutocompleteStation(params) {
}
function getOptionGroup(option) {
- return option.country
+ return option.id.split('_')[0]
}
export default AutocompleteStation;
diff --git a/src/Home.js b/src/Home.js
index af19209..4da3f46 100644
--- a/src/Home.js
+++ b/src/Home.js
@@ -5,7 +5,7 @@ function Home() {
const navigate = useNavigate()
function onStationSelected(event, station) {
- navigate(`/station/sncf/${station.slug}/`)
+ navigate(`/station/sncf/${station.id}/`)
}
return <>
diff --git a/src/Station.js b/src/Station.js
index 3810ad7..0098194 100644
--- a/src/Station.js
+++ b/src/Station.js
@@ -1,19 +1,19 @@
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
import TrainsTable from "./TrainsTable"
import TripsFilter from "./TripsFilter"
-import {useState} from "react";
-import {Box, Button, FormLabel} from "@mui/material";
-import {DatePicker, TimePicker} from "@mui/x-date-pickers";
-import dayjs from "dayjs";
-import {useQuery, useQueryClient} from "@tanstack/react-query";
-import AutocompleteStation from "./AutocompleteStation";
+import {useState} from "react"
+import {Box, Button, FormLabel} from "@mui/material"
+import {DateTimePicker} from "@mui/x-date-pickers"
+import dayjs from "dayjs"
+import {useQuery, useQueryClient} from "@tanstack/react-query"
+import AutocompleteStation from "./AutocompleteStation"
-function DateTimeSelector({station, date, time}) {
+function DateTimeSelector({datetime, setDatetime}) {
const navigate = useNavigate()
function onStationSelected(event, station) {
if (station !== null)
- navigate(`/station/sncf/${station.slug}/`)
+ navigate(`/station/sncf/${station.id}/`)
}
return <>
@@ -25,38 +25,31 @@ function DateTimeSelector({station, date, time}) {
Modifier la date et l'heure de recherche :
-
-
+
>
}
function Station() {
- let {theme, stationSlug} = useParams()
- let [searchParams, _setSearchParams] = useSearchParams()
- const now = new Date()
- let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
- let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
- let [date, setDate] = useState(searchParams.get('date') || dateNow)
- let [time, setTime] = useState(searchParams.get('time') || timeNow)
+ // eslint-disable-next-line no-unused-vars
+ let {theme, stationId} = useParams()
+ // eslint-disable-next-line no-unused-vars
+ let [searchParams, setSearchParams] = useSearchParams()
+ const [datetime, setDatetime] = useState(dayjs())
useQueryClient()
const stationQuery = useQuery({
- queryKey: ['station', stationSlug],
- queryFn: () => fetch(`/api/core/station/${stationSlug}/`)
+ queryKey: ['station', stationId],
+ queryFn: () => fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?stopId=${stationId}&n=1`)
.then(response => response.json()),
- enabled: !!stationSlug,
+ enabled: !!stationId,
})
- const station = stationQuery.data ?? {name: "Chargement…"}
+ const station = stationQuery.data?.stopTimes[0].place ?? {name: "Chargement…"}
- if (time === timeNow) {
+ if (searchParams.get("time") === undefined) {
setInterval(() => {
- const now = new Date()
- let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
- let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
- setDate(dateNow)
- setTime(timeNow)
+ setDatetime(dayjs())
}, 5000)
}
@@ -67,10 +60,10 @@ function Station() {
-
+
-
-
+
+
)
diff --git a/src/TrainsTable.js b/src/TrainsTable.js
index aabbe6d..1e103f1 100644
--- a/src/TrainsTable.js
+++ b/src/TrainsTable.js
@@ -10,8 +10,9 @@ import {
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";
+import {useQuery} from "@tanstack/react-query"
+import {useCallback, useEffect, useMemo, useRef} from "react"
+import dayjs from "dayjs"
const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
'tbody &:nth-of-type(odd)': {
@@ -26,12 +27,12 @@ const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
},
}));
-function TrainsTable({station, date, time, tableType}) {
+function TrainsTable({station, datetime, tableType}) {
return <>
>
@@ -49,25 +50,31 @@ function TrainsTableHeader({tableType}) {
>
}
-function TrainsTableBody({station, date, time, tableType}) {
+function TrainsTableBody({station, datetime, tableType}) {
const filterTime = useCallback((train) => {
if (tableType === "departures")
- return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}`
+ return dayjs(train.place.departure) >= datetime
else
- return `${train.arrival_date}T${train.arrival_time_24h}` >= `${date}T${time}`
- }, [date, time, tableType])
+ return dayjs(train.place.arrival) >= datetime
+ }, [datetime, tableType])
const updateTrains = useCallback(() => {
- return fetch(`/api/station/next_${tableType}/?station_slug=${station.slug}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
+ const query_params = new URLSearchParams({
+ stopId: station.stopId,
+ arriveBy: tableType === "arrivals",
+ time: datetime.format(),
+ n: 20,
+ }).toString()
+ return fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?${query_params}`)
.then(response => response.json())
- .then(data => data.results)
+ .then(data => data.stopTimes)
.then(data => [...data])
- }, [station.id, date, time, tableType])
+ }, [station.stopId, datetime, tableType])
const trainsQuery = useQuery({
- queryKey: ['trains', station.id, tableType],
+ queryKey: ['trains', station.stopId, tableType],
queryFn: updateTrains,
- enabled: !!station.id,
+ enabled: !!station.stopId,
})
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
@@ -79,7 +86,7 @@ function TrainsTableBody({station, date, time, tableType}) {
const nullRef = useRef(null)
let table_rows = trains.map((train) =>
-
+
)
return <>
@@ -91,90 +98,42 @@ function TrainsTableBody({station, date, time, tableType}) {
>
}
-function TrainRow({train, tableType, date, time}) {
+function TrainRow({train, tableType}) {
const tripQuery = useQuery({
- queryKey: ['trip', train.trip],
- queryFn: () => fetch(`/api/gtfs/trip/${train.trip}/`)
+ queryKey: ['tripId', train.tripId],
+ queryFn: () => fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/trip?${new URLSearchParams({tripId: train.tripId})}`)
.then(response => response.json()),
- enabled: !!train.trip,
+ enabled: !!train.tripId,
})
const trip = tripQuery.data ?? {}
+ const leg = trip.legs ? trip.legs[0] : null
- 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 trainType = getTrainType(train)
+ const backgroundColor = getBackgroundColor(train)
+ const textColor = getTextColor(train)
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 stops = useMemo(() => leg ? [leg.from, ...leg.intermediateStops, leg.to] : [], [leg])
+ const stopIndex = useMemo(() => {
+ if (stops.length === 0 || train.place.stopId === undefined)
+ return -1
+ for (let i = 0; i < stops.length; i++) {
+ const index = tableType === "departures" ? i : stops.length - 1 - i
+ const stop = stops[index]
+ let timeCond = tableType === "departures" ? stop.scheduledDeparture === train.place.scheduledDeparture
+ : stop.scheduledArrival === train.place.scheduledArrival
+ if (stop.stopId === train.place.stopId && timeCond)
+ return index
+ }
+ }, [stops, train, tableType])
+ const nextStops = tableType === "departures" ? stops.slice(stopIndex + 1) : stops.slice(0, stopIndex)
- 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 = nextStops[tableType === "departures" ? nextStops.length - 1 : 0] ?? {name: "Chargement…"}
- let headline = stops[tableType === "departures" ? stops.length - 1 : 0]?.stop ?? {name: "Chargement…"}
+ const canceled = false // TODO Implémenter l'annulation
+ const [delayed, prettyDelay] = getPrettyDelay(train, tableType)
- 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(" > ") ?? ""
+ let stopsNames = nextStops.map(stopTime => stopTime?.name ?? "").join(" > ") ?? ""
return <>
@@ -197,8 +156,8 @@ function TrainRow({train, tableType, date, time}) {
-
{trip.short_name}
-
{trip.headsign}
+
{train.routeShortName}
+
{train.headsign}
@@ -208,19 +167,16 @@ function TrainRow({train, tableType, date, time}) {
{getDisplayTime(train, tableType)}
-
+
{prettyDelay}
-
- {prettyScheduleRelationship}
-
- {headline.name}
+ {headline.name}
{stopsNames}
@@ -228,12 +184,14 @@ function TrainRow({train, tableType, date, time}) {
>
}
-function getTrainType(train, trip, route) {
- switch (route.gtfs_feed) {
+function getTrainType(train) {
+ if (train.place.stopId === undefined)
+ return ""
+ switch (train.place.stopId.split('_')[0]) {
case "FR-SNCF-TGV":
case "FR-SNCF-IC":
case "FR-SNCF-TER":
- let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0]
+ let trainType = train.place.stopId.split("StopPoint:OCE")[1].split("-")[0]
switch (trainType) {
case "Train TER":
return "TER"
@@ -246,7 +204,7 @@ function getTrainType(train, trip, route) {
}
case "FR-IDF-IDFM":
case "FR-GES-CTS":
- return route.short_name
+ return "A"
case "FR-EUROSTAR":
return "Eurostar"
case "IT-FRA-TI":
@@ -254,13 +212,13 @@ function getTrainType(train, trip, route) {
case "ES-RENFE":
return "RENFE"
case "AT-OBB":
- if (trip.short_name?.startsWith("NJ"))
+ if (train.routeShortName?.startsWith("NJ"))
return "NJ"
return "ÖBB"
case "CH-ALL":
- return route.desc
+ return "A"
default:
- return trip.short_name?.split(" ")[0]
+ return train.routeShortName?.split(" ")[0]
}
}
@@ -294,8 +252,8 @@ function getTrainTypeDisplay(trainType) {
}
}
-function getBackgroundColor(train, trip, route) {
- let trainType = getTrainType(train, trip, route)
+function getBackgroundColor(train) {
+ let trainType = getTrainType(train)
switch (trainType) {
case "OUIGO":
return "#0096CA"
@@ -304,17 +262,17 @@ function getBackgroundColor(train, trip, route) {
case "NJ":
return "#272759"
default:
- if (route.color)
- return `#${route.color}`
+ if (train.routeColor)
+ return `#${train.routeColor}`
return "#FFFFFF"
}
}
-function getTextColor(train, trip, route) {
- if (route.text_color)
- return `#${route.text_color}`
+function getTextColor(train) {
+ if (train.routeTextColor)
+ return `#${train.routeTextColor}`
else {
- let trainType = getTrainType(train, trip, route)
+ let trainType = getTrainType(train)
switch (trainType) {
case "OUIGO":
return "#FFFFFF"
@@ -332,33 +290,21 @@ function getTextColor(train, trip, route) {
}
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)
+ dayjs.locale('fr')
+ let time = tableType === "departures" ? train.place.scheduledDeparture : train.place.scheduledArrival
+ return dayjs(time).format('LT')
}
-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 ["", ""]
- }
+function getPrettyDelay(train, tableType) {
+ if (train === undefined || !train.realTime) {
+ return [false, ""]
}
+ const [scheduled, projected] = tableType === "departures" ? [train.place.scheduledDeparture, train.place.departure]
+ : [train.place.scheduledArrival, train.place.arrival]
+ const delay_minutes = dayjs(projected).diff(dayjs(scheduled), "minute")
+ if (delay_minutes === 0)
+ return [false, "À l'heure"]
+ return [true, `+${delay_minutes} min`]
}
export default TrainsTable;
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 07250f0..37af84a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-import React, {useMemo} from 'react';
+import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import reportWebVitals from './reportWebVitals';