diff --git a/src/App.tsx b/src/App.tsx index 13374fe..07fc3c7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,43 +1,57 @@ import React from 'react'; import './App.css'; -import Ligne from "./Ligne"; -import {ILigne} from "./interfaces"; -import {setInterval, clearInterval} from "timers"; -import {Col, Row, Container, Dropdown, DropdownButton} from "react-bootstrap"; +import Tcl, {ILigne} from "./Tcl"; +import {clearInterval, setInterval} from "timers"; +import {Col, Container, Dropdown, DropdownButton, Row} from "react-bootstrap"; +import {IStationInfo, IStationsInfoWrapper, IStationsStatusWrapper, IVelovStation} from "./IVelov"; +import Velov from "./Velov"; +interface ITclFilteredApi { + passages: ILigne[]; +} + interface IAppState { passages: ILigne[]; refreshDate?: string; + stations: IVelovStation[]; } class App extends React.Component<{}, IAppState> { timerId?: ReturnType; refreshSeconds: number; + monitoredVelovStationIds: string[] = []; constructor(props: {}) { super(props); this.timerId = undefined; const urlParams = new URLSearchParams(window.location.search); this.refreshSeconds = Number(urlParams.get("refreshSeconds")); + const velovStationIds = urlParams.get("velovStationIds"); + if (velovStationIds) { + this.monitoredVelovStationIds = velovStationIds.split(";"); + } if (this.refreshSeconds <= 5) { this.refreshSeconds = 60; } - this.state = {passages: [{ligne: undefined, delais: [undefined]}]}; + this.state = {passages: [{ligne: undefined, delais: [undefined]}], stations: []}; } render() { return
- - {this.state.passages.map((ligne) => )} + + {this.state.passages.map((ligne) => )} + + + {this.state.stations.map((station) => )} - Data - Full page + Data + Full page @@ -79,10 +93,45 @@ class App extends React.Component<{}, IAppState> { private refresh() { const headers = new Headers(); headers.set("Authorization", `Basic ${process.env.REACT_APP_TCL_AUTH}`); - http("https://tcl.augendre.info/stop/290", {method: "GET", headers: headers}).then(json => { - json.refreshDate = new Date().toLocaleString("fr-fr"); - this.setState(json); + const tclPromise = http("https://tcl.augendre.info/stop/290", { + method: "GET", + headers: headers }); + const velovInfoPromise = http("https://transport.data.gouv.fr/gbfs/lyon/station_information.json", {method: "GET"}); + const velovStatusPromise = http("https://transport.data.gouv.fr/gbfs/lyon/station_status.json", {method: "GET"}); + Promise.all([tclPromise, velovInfoPromise, velovStatusPromise]).then(values => { + const tcl = values[0]; + const velovInfo = values[1]; + const stationsInfo: Record = {}; + for (const stationInfo of velovInfo.data.stations) { + if (this.monitoredVelovStationIds.includes(stationInfo.station_id)) { + stationsInfo[stationInfo.station_id] = stationInfo; + } + } + const velovStatus = values[2]; + const stationsDict = new Map(); + for (const stationStatus of velovStatus.data.stations) { + if (this.monitoredVelovStationIds.includes(stationStatus.station_id)) { + const velovStation: IVelovStation = { + status: stationStatus, + info: stationsInfo[stationStatus.station_id] + }; + stationsDict.set(velovStation.info.station_id, velovStation); + } + } + const stations: IVelovStation[] = []; + for (const monitoredVelovStationId of this.monitoredVelovStationIds) { + const stationInfo = stationsDict.get(monitoredVelovStationId); + if (stationInfo) { + stations.push(stationInfo); + } + } + this.setState({ + refreshDate: new Date().toLocaleString("fr-fr"), + passages: tcl.passages, + stations: stations, + }); + }) } private reload = () => { diff --git a/src/IVelov.ts b/src/IVelov.ts new file mode 100644 index 0000000..e2fcdc1 --- /dev/null +++ b/src/IVelov.ts @@ -0,0 +1,39 @@ +export interface IStationInfo { + address: string; + capacity: number; + lat: number; + lon: number; + name: string; + station_id: string; +} + +export interface IStationsInfo { + stations: IStationInfo[]; +} + +export interface IStationsInfoWrapper { + data: IStationsInfo; +} + +export interface IStationStatus { + is_installed: number; + is_renting: number; + is_returning: number; + last_reported: number; + num_bikes_available: number; + num_docks_available: number; + station_id: string; +} + +export interface IStationsStatus { + stations: IStationStatus[]; +} + +export interface IStationsStatusWrapper { + data: IStationsStatus; +} + +export interface IVelovStation { + info: IStationInfo; + status: IStationStatus; +} diff --git a/src/Ligne.tsx b/src/Tcl.tsx similarity index 79% rename from src/Ligne.tsx rename to src/Tcl.tsx index 65e9aff..465fe88 100644 --- a/src/Ligne.tsx +++ b/src/Tcl.tsx @@ -1,11 +1,17 @@ import React from "react"; -import {ILigne, PassageType} from "./interfaces"; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' import {faBus} from '@fortawesome/free-solid-svg-icons' import {Card, Col, ListGroup} from "react-bootstrap"; +import {placeholder} from "./utils"; +export type PassageType = string | undefined; -export default class Ligne extends React.Component { +export interface ILigne { + ligne?: string; + delais: PassageType[]; +} + +export default class Tcl extends React.Component { render() { return @@ -40,11 +46,3 @@ class Passage extends React.Component { return {placeholder(this.props.passage)} } } - -function placeholder(value?: string) { - if (value === undefined) { - return - } else { - return value; - } -} diff --git a/src/Velov.css b/src/Velov.css new file mode 100644 index 0000000..5cc7ae0 --- /dev/null +++ b/src/Velov.css @@ -0,0 +1,3 @@ +.card-header { + text-transform: capitalize; +} \ No newline at end of file diff --git a/src/Velov.tsx b/src/Velov.tsx new file mode 100644 index 0000000..e3ccabf --- /dev/null +++ b/src/Velov.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import './Velov.css'; +import {Card, Col, ListGroup} from "react-bootstrap"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faAnchor, faBicycle, faBiking} from "@fortawesome/free-solid-svg-icons"; +import {IVelovStation} from "./IVelov"; + +export default class Velov extends React.Component { + render() { + return + + + {this.props.info.name.toLowerCase()} + + + + {this.props.status.num_bikes_available} + + + {this.props.status.num_docks_available} + + + + + } +} + diff --git a/src/interfaces.ts b/src/interfaces.ts deleted file mode 100644 index 91289e9..0000000 --- a/src/interfaces.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type PassageType = string|undefined; - -export interface ILigne { - ligne?: string; - delais: PassageType[]; -} - diff --git a/src/utils.tsx b/src/utils.tsx new file mode 100644 index 0000000..a1d1e2a --- /dev/null +++ b/src/utils.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export function placeholder(value?: string) { + if (value === undefined) { + return + } else { + return value; + } +} \ No newline at end of file