Compare commits
	
		
			8 Commits
		
	
	
		
			1c99c5ca47
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						0a5bec6c4b
	
				 | 
					
					
						|||
| 
						
						
							
						
						6389406744
	
				 | 
					
					
						|||
| 
						
						
							
						
						41441a7803
	
				 | 
					
					
						|||
| 
						
						
							
						
						f0964d8fb7
	
				 | 
					
					
						|||
| 
						
						
							
						
						e58ad34e43
	
				 | 
					
					
						|||
| 
						
						
							
						
						af61173e9d
	
				 | 
					
					
						|||
| 
						
						
							
						
						2e5b5970a9
	
				 | 
					
					
						|||
| 
						
						
							
						
						ec9ac8d7ab
	
				 | 
					
					
						
							
								
								
									
										11129
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11129
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										40
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								package.json
									
									
									
									
									
								
							@@ -3,25 +3,29 @@
 | 
				
			|||||||
  "version": "0.1.0",
 | 
					  "version": "0.1.0",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@emotion/react": "^11.11.3",
 | 
					    "@emotion/react": "^11.13.3",
 | 
				
			||||||
    "@emotion/styled": "^11.11.0",
 | 
					    "@emotion/styled": "^11.13.0",
 | 
				
			||||||
    "@mui/icons-material": "^5.15.17",
 | 
					    "@mapbox/polyline": "^1.2.1",
 | 
				
			||||||
    "@mui/material": "^5.15.6",
 | 
					    "@mui/icons-material": "^6.1.6",
 | 
				
			||||||
    "@mui/x-date-pickers": "^6.19.2",
 | 
					    "@mui/material": "^6.1.6",
 | 
				
			||||||
    "@tanstack/query-sync-storage-persister": "^5.18.0",
 | 
					    "@mui/x-date-pickers": "^7.22.2",
 | 
				
			||||||
    "@tanstack/react-query": "^5.18.0",
 | 
					    "@tanstack/react-query": "^5.59.20",
 | 
				
			||||||
    "@tanstack/react-query-persist-client": "^5.18.0",
 | 
					    "@testing-library/jest-dom": "^6.6.3",
 | 
				
			||||||
    "@testing-library/jest-dom": "^5.17.0",
 | 
					    "@testing-library/react": "^16.0.1",
 | 
				
			||||||
    "@testing-library/react": "^13.4.0",
 | 
					    "@testing-library/user-event": "^14.5.2",
 | 
				
			||||||
    "@testing-library/user-event": "^13.5.0",
 | 
					    "@turf/rhumb-bearing": "^7.1.0",
 | 
				
			||||||
    "dayjs": "^1.11.10",
 | 
					    "@turf/rhumb-distance": "^7.1.0",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "@types/leaflet": "^1.9.14",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "dayjs": "^1.11.13",
 | 
				
			||||||
    "react-router-dom": "^6.21.3",
 | 
					    "leaflet": "^1.9.4",
 | 
				
			||||||
    "react-scripts": "5.0.1",
 | 
					    "react": "^18.3.1",
 | 
				
			||||||
 | 
					    "react-dom": "^18.3.1",
 | 
				
			||||||
 | 
					    "react-leaflet": "^4.2.1",
 | 
				
			||||||
 | 
					    "react-router-dom": "^6.28.0",
 | 
				
			||||||
 | 
					    "react-scripts": "^5.0.1",
 | 
				
			||||||
    "react-transition-group": "^4.4.5",
 | 
					    "react-transition-group": "^4.4.5",
 | 
				
			||||||
    "sass": "^1.70.0",
 | 
					    "sass": "^1.80.6",
 | 
				
			||||||
    "web-vitals": "^2.1.4"
 | 
					    "web-vitals": "^4.2.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "react-scripts start",
 | 
					    "start": "react-scripts start",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,13 @@ import {createBrowserRouter, RouterProvider} from "react-router-dom"
 | 
				
			|||||||
import Station from "./Station"
 | 
					import Station from "./Station"
 | 
				
			||||||
import {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@mui/material"
 | 
					import {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@mui/material"
 | 
				
			||||||
import React, {useMemo} from "react"
 | 
					import React, {useMemo} from "react"
 | 
				
			||||||
import {frFR, LocalizationProvider} from "@mui/x-date-pickers"
 | 
					import {LocalizationProvider} from "@mui/x-date-pickers"
 | 
				
			||||||
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
 | 
					import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
 | 
				
			||||||
import 'dayjs/locale/fr'
 | 
					import 'dayjs/locale/fr'
 | 
				
			||||||
import './App.css'
 | 
					import './App.css'
 | 
				
			||||||
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
 | 
					import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
 | 
				
			||||||
import Home from "./Home"
 | 
					import Home from "./Home"
 | 
				
			||||||
 | 
					import TrainMap from "./Map"
 | 
				
			||||||
import dayjs from "dayjs"
 | 
					import dayjs from "dayjs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App() {
 | 
					function App() {
 | 
				
			||||||
@@ -19,6 +20,10 @@ function App() {
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
      path: "/station/:theme/:stationId",
 | 
					      path: "/station/:theme/:stationId",
 | 
				
			||||||
      element: <Station />
 | 
					      element: <Station />
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      path: "/map",
 | 
				
			||||||
 | 
					      element: <TrainMap />
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ])
 | 
					  ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,7 +63,7 @@ function App() {
 | 
				
			|||||||
  return <>
 | 
					  return <>
 | 
				
			||||||
    <ThemeProvider theme={theme}>
 | 
					    <ThemeProvider theme={theme}>
 | 
				
			||||||
      <CssBaseline />
 | 
					      <CssBaseline />
 | 
				
			||||||
      <LocalizationProvider dateAdapter={AdapterDayjs} localeText={frFR.components.MuiLocalizationProvider.defaultProps.localeText} adapterLocale="fr">
 | 
					      <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
 | 
				
			||||||
        <QueryClientProvider client={queryClient}>
 | 
					        <QueryClientProvider client={queryClient}>
 | 
				
			||||||
          <RouterProvider router={router} />
 | 
					          <RouterProvider router={router} />
 | 
				
			||||||
        </QueryClientProvider>
 | 
					        </QueryClientProvider>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										194
									
								
								src/Map.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								src/Map.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					import "leaflet/dist/leaflet.css"
 | 
				
			||||||
 | 
					import L from 'leaflet'
 | 
				
			||||||
 | 
					import {MapContainer, Marker, TileLayer, useMapEvents} from 'react-leaflet'
 | 
				
			||||||
 | 
					import {useEffect, useMemo, useState} from "react"
 | 
				
			||||||
 | 
					import dayjs from "dayjs"
 | 
				
			||||||
 | 
					import polyline from "@mapbox/polyline"
 | 
				
			||||||
 | 
					import getDistance from '@turf/rhumb-distance'
 | 
				
			||||||
 | 
					import getBearing from '@turf/rhumb-bearing'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function TrainMap () {
 | 
				
			||||||
 | 
					  return <>
 | 
				
			||||||
 | 
					    <MapContainer center={[46.47, 2.37]} zoom={6} style={{height: "100vh"}}>
 | 
				
			||||||
 | 
					      <TileLayer
 | 
				
			||||||
 | 
					        attribution='Données cartographiques : © Les contributeurices <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
 | 
				
			||||||
 | 
					        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <TileLayer
 | 
				
			||||||
 | 
					        attribution="Rendu : OpenRailwayMap"
 | 
				
			||||||
 | 
					        url="https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png"></TileLayer>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <MapContent />
 | 
				
			||||||
 | 
					    </MapContainer>
 | 
				
			||||||
 | 
					  </>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MapContent () {
 | 
				
			||||||
 | 
					  const [latitude, setLatitude] = useState(46.47)
 | 
				
			||||||
 | 
					  const [longitude, setLongitude] = useState(2.37)
 | 
				
			||||||
 | 
					  const [zoom, setZoom] = useState(6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/map/initial`)
 | 
				
			||||||
 | 
					      .then(response => response.json())
 | 
				
			||||||
 | 
					      .then(data => {
 | 
				
			||||||
 | 
					        setLatitude(data['lat'])
 | 
				
			||||||
 | 
					        setLongitude(data['lon'])
 | 
				
			||||||
 | 
					        setZoom(data['zoom'])
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const map = useMapEvents({
 | 
				
			||||||
 | 
					    moveend: () => {
 | 
				
			||||||
 | 
					      updateTrips(map, setTrips)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    zoomend: () => {
 | 
				
			||||||
 | 
					      updateTrips(map, setTrips)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    map.flyTo([latitude, longitude], zoom)
 | 
				
			||||||
 | 
					  }, [map, latitude, longitude, zoom])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [trips, setTrips] = useState([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    updateTrips(map, setTrips)
 | 
				
			||||||
 | 
					    setInterval(() => updateTrips(map, setTrips), 30000)
 | 
				
			||||||
 | 
					  }, [map])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <>
 | 
				
			||||||
 | 
					    {trips.map(trip => <TripMarker trip={trip} />)}
 | 
				
			||||||
 | 
					  </>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function TripMarker ({trip}) {
 | 
				
			||||||
 | 
					  const [position, setPosition] = useState([trip.from.lat, trip.from.lon])
 | 
				
			||||||
 | 
					  const [heading, setHeading] = useState(0)
 | 
				
			||||||
 | 
					  const style = getModeStyle(trip.mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const keyframes = useMemo(() => {
 | 
				
			||||||
 | 
					    const keyframes = []
 | 
				
			||||||
 | 
					    const departure = dayjs(trip.departure)
 | 
				
			||||||
 | 
					    const arrival = dayjs(trip.arrival)
 | 
				
			||||||
 | 
					    const coordinates = polyline.decode(trip.polyline)
 | 
				
			||||||
 | 
					    const totalDuration = arrival.diff(departure, 'seconds')
 | 
				
			||||||
 | 
					    let currDistance = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let totalDistance = 0
 | 
				
			||||||
 | 
							for (let i = 0; i < coordinates.length - 1; i++) {
 | 
				
			||||||
 | 
								let from = coordinates[i]
 | 
				
			||||||
 | 
								let to = coordinates[i + 1]
 | 
				
			||||||
 | 
								totalDistance += getDistance(from, to, { units: 'meters' })
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (let i = 0; i < coordinates.length - 1; i++) {
 | 
				
			||||||
 | 
								let from = coordinates[i]
 | 
				
			||||||
 | 
								let to = coordinates[i + 1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const distance = getDistance(from, to, { units: 'meters' })
 | 
				
			||||||
 | 
								const heading = getBearing(from, to)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const r = currDistance / totalDistance
 | 
				
			||||||
 | 
								keyframes.push({ point: from, time: departure.add(r * totalDuration, 'seconds'), heading: heading })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								currDistance += distance
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							keyframes.push({ point: coordinates[coordinates.length - 1], time: arrival, heading: 0 })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return keyframes
 | 
				
			||||||
 | 
					  }, [trip])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const interval = setInterval(() => {
 | 
				
			||||||
 | 
					      const now = dayjs()
 | 
				
			||||||
 | 
					      const index = keyframes.findIndex((kf) => kf.time >= now)
 | 
				
			||||||
 | 
					      if (index === -1 || index === 0)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const startState = keyframes[index - 1]
 | 
				
			||||||
 | 
							  const endState = keyframes[index]
 | 
				
			||||||
 | 
					      const r = (now.diff(startState.time)) / (endState.time.diff(startState.time))
 | 
				
			||||||
 | 
					      const lat = startState.point[0] * (1 - r) + endState.point[0] * r
 | 
				
			||||||
 | 
					      const lon = startState.point[1] * (1 - r) + endState.point[1] * r
 | 
				
			||||||
 | 
					      setPosition([lat, lon])
 | 
				
			||||||
 | 
					      setHeading(startState.heading)
 | 
				
			||||||
 | 
					    }, 100)
 | 
				
			||||||
 | 
					    return () => clearInterval(interval)
 | 
				
			||||||
 | 
					  }, [keyframes])
 | 
				
			||||||
 | 
					  const icon = L.divIcon({
 | 
				
			||||||
 | 
					    html: `<svg fill="${style[1]}" fill-opacity="0.8" xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					\t width="36px" height="36px" viewBox="0 0 512 512" xml:space="preserve">
 | 
				
			||||||
 | 
					<g transform="rotate(${-heading - 90}, 256, 256)">
 | 
				
			||||||
 | 
					\t<path d="M256 17.108c-75.73 0-137.122 61.392-137.122 137.122.055 23.25 6.022 46.107 11.58 56.262L256 494.892l119.982-274.244h-.063c11.27-20.324 17.188-43.18 17.202-66.418C393.122 78.5 331.73 17.108 256 17.108zm0 68.56a68.56 68.56 0 0 1 68.56 68.562A68.56 68.56 0 0 1 256 222.79a68.56 68.56 0 0 1-68.56-68.56A68.56 68.56 0 0 1 256 85.67z" />
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					</svg>`,
 | 
				
			||||||
 | 
					    className: "",
 | 
				
			||||||
 | 
					    iconSize: [36, 36],
 | 
				
			||||||
 | 
					    iconAnchor: [36, 36],
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  return <Marker position={position} icon={icon} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getModeStyle (mode) {
 | 
				
			||||||
 | 
						switch (mode) {
 | 
				
			||||||
 | 
							case 'WALK':
 | 
				
			||||||
 | 
							case 'FLEXIBLE':
 | 
				
			||||||
 | 
								return ['walk', 'hsl(var(--foreground) / 1)', 'hsl(var(--background) / 1)']
 | 
				
			||||||
 | 
							case 'BIKE':
 | 
				
			||||||
 | 
							case 'BIKE_TO_PARK':
 | 
				
			||||||
 | 
							case 'BIKE_RENTAL':
 | 
				
			||||||
 | 
							case 'SCOOTER_RENTAL':
 | 
				
			||||||
 | 
								return ['bike', '#075985', 'white']
 | 
				
			||||||
 | 
							case 'CAR':
 | 
				
			||||||
 | 
							case 'CAR_TO_PARK':
 | 
				
			||||||
 | 
							case 'CAR_HAILING':
 | 
				
			||||||
 | 
							case 'CAR_SHARING':
 | 
				
			||||||
 | 
							case 'CAR_PICKUP':
 | 
				
			||||||
 | 
							case 'CAR_RENTAL':
 | 
				
			||||||
 | 
								return ['car', '#333', 'white']
 | 
				
			||||||
 | 
							case 'TRANSIT':
 | 
				
			||||||
 | 
							case 'BUS':
 | 
				
			||||||
 | 
								return ['bus', '#ff9800', 'white']
 | 
				
			||||||
 | 
							case 'COACH':
 | 
				
			||||||
 | 
								return ['bus', '#9ccc65', 'white']
 | 
				
			||||||
 | 
							case 'TRAM':
 | 
				
			||||||
 | 
								return ['tram', '#ff9800', 'white']
 | 
				
			||||||
 | 
							case 'METRO':
 | 
				
			||||||
 | 
								return ['sbahn', '#4caf50', 'white']
 | 
				
			||||||
 | 
							case 'SUBWAY':
 | 
				
			||||||
 | 
								return ['ubahn', '#3f51b5', 'white']
 | 
				
			||||||
 | 
							case 'FERRY':
 | 
				
			||||||
 | 
								return ['ship', '#00acc1', 'white']
 | 
				
			||||||
 | 
							case 'AIRPLANE':
 | 
				
			||||||
 | 
								return ['plane', '#90a4ae', 'white']
 | 
				
			||||||
 | 
							case 'HIGHSPEED_RAIL':
 | 
				
			||||||
 | 
								return ['train', '#9c27b0', 'white']
 | 
				
			||||||
 | 
							case 'LONG_DISTANCE':
 | 
				
			||||||
 | 
								return ['train', '#e91e63', 'white']
 | 
				
			||||||
 | 
							case 'NIGHT_RAIL':
 | 
				
			||||||
 | 
								return ['train', '#1a237e', 'white']
 | 
				
			||||||
 | 
							case 'REGIONAL_FAST_RAIL':
 | 
				
			||||||
 | 
							case 'REGIONAL_RAIL':
 | 
				
			||||||
 | 
							case 'RAIL':
 | 
				
			||||||
 | 
								return ['train', '#f44336', 'white']
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ['train', '#000000', 'white']
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateTrips(map, setTrips) {
 | 
				
			||||||
 | 
					  const bounds = map.getBounds()
 | 
				
			||||||
 | 
					  const now = dayjs()
 | 
				
			||||||
 | 
					  const now_plus_1_min = now.add(60000)
 | 
				
			||||||
 | 
					  const query_params = new URLSearchParams({
 | 
				
			||||||
 | 
					    min: `${bounds.getNorth()},${bounds.getWest()}`,
 | 
				
			||||||
 | 
					    max: `${bounds.getSouth()},${bounds.getEast()}`,
 | 
				
			||||||
 | 
					    zoom: map.getZoom(),
 | 
				
			||||||
 | 
					    startTime: now.format(),
 | 
				
			||||||
 | 
					    endTime: now_plus_1_min.format(),
 | 
				
			||||||
 | 
					  }).toString()
 | 
				
			||||||
 | 
					  fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/map/trips?${query_params}`)
 | 
				
			||||||
 | 
					    .then(data => data.json())
 | 
				
			||||||
 | 
					    .then(setTrips)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,14 +1,13 @@
 | 
				
			|||||||
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
 | 
					import {useNavigate, useParams, useSearchParams} from "react-router-dom"
 | 
				
			||||||
import TrainsTable from "./TrainsTable"
 | 
					import TrainsTable from "./TrainsTable"
 | 
				
			||||||
import TripsFilter from "./TripsFilter"
 | 
					import {useEffect, useState} from "react"
 | 
				
			||||||
import {useState} from "react"
 | 
					import {Box, Checkbox, FormLabel} from "@mui/material"
 | 
				
			||||||
import {Box, Button, FormLabel} from "@mui/material"
 | 
					 | 
				
			||||||
import {DateTimePicker} from "@mui/x-date-pickers"
 | 
					import {DateTimePicker} from "@mui/x-date-pickers"
 | 
				
			||||||
import dayjs from "dayjs"
 | 
					import dayjs from "dayjs"
 | 
				
			||||||
import {useQuery, useQueryClient} from "@tanstack/react-query"
 | 
					import {useQuery, useQueryClient} from "@tanstack/react-query"
 | 
				
			||||||
import AutocompleteStation from "./AutocompleteStation"
 | 
					import AutocompleteStation from "./AutocompleteStation"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function DateTimeSelector({datetime, setDatetime}) {
 | 
					function DateTimeSelector({datetime, setDatetime, realtime, setRealtime}) {
 | 
				
			||||||
  const navigate = useNavigate()
 | 
					  const navigate = useNavigate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function onStationSelected(event, station) {
 | 
					  function onStationSelected(event, station) {
 | 
				
			||||||
@@ -25,8 +24,11 @@ function DateTimeSelector({datetime, setDatetime}) {
 | 
				
			|||||||
      <FormLabel>
 | 
					      <FormLabel>
 | 
				
			||||||
        Modifier la date et l'heure de recherche :
 | 
					        Modifier la date et l'heure de recherche :
 | 
				
			||||||
      </FormLabel>
 | 
					      </FormLabel>
 | 
				
			||||||
        <DateTimePicker name="date" label="Date" onChange={setDatetime} value={datetime} />
 | 
					      <DateTimePicker name="date" label="Date" onChange={setDatetime} value={datetime} disabled={realtime} readOnly={realtime} />
 | 
				
			||||||
        <Button type="submit">Rechercher</Button>
 | 
					      <Checkbox onChange={event => setRealtime(event.target.checked)} checked={realtime} />
 | 
				
			||||||
 | 
					      <FormLabel>
 | 
				
			||||||
 | 
					        Temps réel
 | 
				
			||||||
 | 
					      </FormLabel>
 | 
				
			||||||
    </Box>
 | 
					    </Box>
 | 
				
			||||||
  </>
 | 
					  </>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -36,7 +38,20 @@ function Station() {
 | 
				
			|||||||
  let {theme, stationId} = useParams()
 | 
					  let {theme, stationId} = useParams()
 | 
				
			||||||
  // eslint-disable-next-line no-unused-vars
 | 
					  // eslint-disable-next-line no-unused-vars
 | 
				
			||||||
  let [searchParams, setSearchParams] = useSearchParams()
 | 
					  let [searchParams, setSearchParams] = useSearchParams()
 | 
				
			||||||
  const [datetime, setDatetime] = useState(dayjs())
 | 
					  const [realtime, setRealtime] = useState(searchParams.get('realtime') === "1" || false)
 | 
				
			||||||
 | 
					  const [datetime, setDatetime] = useState(dayjs(searchParams.get('time') || undefined))
 | 
				
			||||||
 | 
					  if ((searchParams.get('realtime') === null || searchParams.get('realtime') === "0")
 | 
				
			||||||
 | 
					    && (searchParams.get('time') === null || realtime)) {
 | 
				
			||||||
 | 
					    searchParams.set('realtime', "1")
 | 
				
			||||||
 | 
					    searchParams.delete("time")
 | 
				
			||||||
 | 
					    setRealtime(true)
 | 
				
			||||||
 | 
					    window.history.replaceState({}, '', '?' + searchParams.toString())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else if (datetime.format() !== searchParams.get('time') && !realtime) {
 | 
				
			||||||
 | 
					    searchParams.set('time', datetime.format())
 | 
				
			||||||
 | 
					    searchParams.set('realtime', "0")
 | 
				
			||||||
 | 
					    window.history.replaceState({}, '', '?' + searchParams.toString())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useQueryClient()
 | 
					  useQueryClient()
 | 
				
			||||||
  const stationQuery = useQuery({
 | 
					  const stationQuery = useQuery({
 | 
				
			||||||
@@ -47,11 +62,14 @@ function Station() {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
  const station = stationQuery.data?.stopTimes[0].place ?? {name: "Chargement…"}
 | 
					  const station = stationQuery.data?.stopTimes[0].place ?? {name: "Chargement…"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (searchParams.get("time") === undefined) {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setInterval(() => {
 | 
					    if (realtime) {
 | 
				
			||||||
 | 
					      const interval = setInterval(() => {
 | 
				
			||||||
        setDatetime(dayjs())
 | 
					        setDatetime(dayjs())
 | 
				
			||||||
      }, 5000)
 | 
					      }, 5000)
 | 
				
			||||||
 | 
					      return () => clearInterval(interval)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					  }, [realtime])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="Station">
 | 
					    <div className="Station">
 | 
				
			||||||
@@ -60,10 +78,10 @@ function Station() {
 | 
				
			|||||||
      </header>
 | 
					      </header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <main>
 | 
					      <main>
 | 
				
			||||||
        <DateTimeSelector datetime={datetime} setDatetime={setDatetime} />
 | 
					        <DateTimeSelector datetime={datetime} setDatetime={setDatetime} realtime={realtime} setRealtime={setRealtime} />
 | 
				
			||||||
        <TripsFilter />
 | 
					        {/*<TripsFilter />*/}
 | 
				
			||||||
        <TrainsTable station={station} datetime={datetime} tableType="departures" />
 | 
					        <TrainsTable station={station} datetime={datetime} realtime={realtime} tableType="departures" />
 | 
				
			||||||
        <TrainsTable station={station} datetime={datetime} tableType="arrivals" />
 | 
					        <TrainsTable station={station} datetime={datetime} realtime={realtime} tableType="arrivals" />
 | 
				
			||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,12 +27,12 @@ const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function TrainsTable({station, datetime, tableType}) {
 | 
					function TrainsTable({station, datetime, realtime, tableType}) {
 | 
				
			||||||
  return <>
 | 
					  return <>
 | 
				
			||||||
    <TableContainer>
 | 
					    <TableContainer>
 | 
				
			||||||
      <Table>
 | 
					      <Table>
 | 
				
			||||||
        <TrainsTableHeader tableType={tableType} />
 | 
					        <TrainsTableHeader tableType={tableType} />
 | 
				
			||||||
        <TrainsTableBody station={station} datetime={datetime} tableType={tableType} />
 | 
					        <TrainsTableBody station={station} datetime={datetime} realtime={realtime} tableType={tableType} />
 | 
				
			||||||
      </Table>
 | 
					      </Table>
 | 
				
			||||||
    </TableContainer>
 | 
					    </TableContainer>
 | 
				
			||||||
  </>
 | 
					  </>
 | 
				
			||||||
@@ -50,7 +50,7 @@ function TrainsTableHeader({tableType}) {
 | 
				
			|||||||
  </>
 | 
					  </>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function TrainsTableBody({station, datetime, tableType}) {
 | 
					function TrainsTableBody({station, datetime, realtime, tableType}) {
 | 
				
			||||||
  const filterTime = useCallback((train) => {
 | 
					  const filterTime = useCallback((train) => {
 | 
				
			||||||
    if (tableType === "departures")
 | 
					    if (tableType === "departures")
 | 
				
			||||||
      return dayjs(train.place.departure) >= datetime
 | 
					      return dayjs(train.place.departure) >= datetime
 | 
				
			||||||
@@ -59,17 +59,20 @@ function TrainsTableBody({station, datetime, tableType}) {
 | 
				
			|||||||
  }, [datetime, tableType])
 | 
					  }, [datetime, tableType])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const updateTrains = useCallback(() => {
 | 
					  const updateTrains = useCallback(() => {
 | 
				
			||||||
    const query_params = new URLSearchParams({
 | 
					    const params = {
 | 
				
			||||||
      stopId: station.stopId,
 | 
					      stopId: station.stopId,
 | 
				
			||||||
      arriveBy: tableType === "arrivals",
 | 
					      arriveBy: tableType === "arrivals",
 | 
				
			||||||
      time: datetime.format(),
 | 
					      direction: "LATER",
 | 
				
			||||||
      n: 20,
 | 
					      n: 20,
 | 
				
			||||||
    }).toString()
 | 
					    }
 | 
				
			||||||
 | 
					    if (!realtime)
 | 
				
			||||||
 | 
					      params['time'] = datetime.format()
 | 
				
			||||||
 | 
					    const query_params = new URLSearchParams(params).toString()
 | 
				
			||||||
    return fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?${query_params}`)
 | 
					    return fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?${query_params}`)
 | 
				
			||||||
        .then(response => response.json())
 | 
					        .then(response => response.json())
 | 
				
			||||||
        .then(data => data.stopTimes)
 | 
					        .then(data => data.stopTimes)
 | 
				
			||||||
        .then(data => [...data])
 | 
					        .then(data => [...data])
 | 
				
			||||||
  }, [station.stopId, datetime, tableType])
 | 
					  }, [station.stopId, tableType, datetime, realtime])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const trainsQuery = useQuery({
 | 
					  const trainsQuery = useQuery({
 | 
				
			||||||
    queryKey: ['trains', station.stopId, tableType],
 | 
					    queryKey: ['trains', station.stopId, tableType],
 | 
				
			||||||
@@ -79,10 +82,12 @@ function TrainsTableBody({station, datetime, tableType}) {
 | 
				
			|||||||
  const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
 | 
					  const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (realtime) {
 | 
				
			||||||
      let validTrains = trains?.filter(filterTime) ?? []
 | 
					      let validTrains = trains?.filter(filterTime) ?? []
 | 
				
			||||||
    if (trains?.length > 0 && validTrains.length < trains?.length)
 | 
					      if ((trains?.length > 0 && validTrains.length < trains?.length))
 | 
				
			||||||
        trainsQuery.refetch().then()
 | 
					        trainsQuery.refetch().then()
 | 
				
			||||||
  }, [trains, filterTime, trainsQuery])
 | 
					    }
 | 
				
			||||||
 | 
					  }, [trains, filterTime, trainsQuery, realtime])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const nullRef = useRef(null)
 | 
					  const nullRef = useRef(null)
 | 
				
			||||||
  let table_rows = trains.map((train) => <CSSTransition key={train.id} timeout={500} classNames="shrink" nodeRef={nullRef}>
 | 
					  let table_rows = trains.map((train) => <CSSTransition key={train.id} timeout={500} classNames="shrink" nodeRef={nullRef}>
 | 
				
			||||||
@@ -202,9 +207,13 @@ function getTrainType(train) {
 | 
				
			|||||||
        default:
 | 
					        default:
 | 
				
			||||||
          return trainType
 | 
					          return trainType
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    case "FR-IDF-IDFM":
 | 
					    case "FR-IDFM":
 | 
				
			||||||
 | 
					      const route_split = train.routeShortName.split(" ")
 | 
				
			||||||
 | 
					      if (route_split[0] === "Bus")
 | 
				
			||||||
 | 
					        return route_split[1]
 | 
				
			||||||
 | 
					      return route_split[0]
 | 
				
			||||||
    case "FR-GES-CTS":
 | 
					    case "FR-GES-CTS":
 | 
				
			||||||
      return "A"
 | 
					      return train.routeShortName.split(" ")[1]
 | 
				
			||||||
    case "FR-EUROSTAR":
 | 
					    case "FR-EUROSTAR":
 | 
				
			||||||
      return "Eurostar"
 | 
					      return "Eurostar"
 | 
				
			||||||
    case "IT-FRA-TI":
 | 
					    case "IT-FRA-TI":
 | 
				
			||||||
@@ -216,7 +225,6 @@ function getTrainType(train) {
 | 
				
			|||||||
        return "NJ"
 | 
					        return "NJ"
 | 
				
			||||||
      return "ÖBB"
 | 
					      return "ÖBB"
 | 
				
			||||||
    case "CH-ALL":
 | 
					    case "CH-ALL":
 | 
				
			||||||
      return "A"
 | 
					 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return train.routeShortName?.split(" ")[0]
 | 
					      return train.routeShortName?.split(" ")[0]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user