display/src/App.tsx

168 lines
5.8 KiB
TypeScript
Raw Permalink Normal View History

import React from 'react';
import './App.css';
2021-11-14 12:26:42 +01:00
import Tcl, {ILigne, IStop} from "./Tcl";
2021-11-13 15:53:10 +01:00
import {clearInterval, setInterval} from "timers";
2021-11-14 12:26:42 +01:00
import {Button, Col, Container, Row} from "react-bootstrap";
2021-11-13 15:53:10 +01:00
import {IStationInfo, IStationsInfoWrapper, IStationsStatusWrapper, IVelovStation} from "./IVelov";
import Velov from "./Velov";
2021-11-12 16:41:27 +01:00
2021-11-13 15:53:10 +01:00
interface ITclFilteredApi {
passages: ILigne[];
2021-11-14 12:26:42 +01:00
stop: IStop;
2021-11-13 15:53:10 +01:00
}
2021-11-12 16:41:27 +01:00
interface IAppState {
2021-11-14 12:26:42 +01:00
tcl?: ITclFilteredApi;
2021-11-12 18:04:38 +01:00
refreshDate?: string;
2021-11-13 15:53:10 +01:00
stations: IVelovStation[];
2021-11-12 16:41:27 +01:00
}
class App extends React.Component<{}, IAppState> {
timerId?: ReturnType<typeof setInterval>;
2021-11-12 17:59:14 +01:00
refreshSeconds: number;
2021-11-13 15:53:10 +01:00
monitoredVelovStationIds: string[] = [];
2021-11-14 12:26:42 +01:00
monitoredTclStopId: string;
2021-11-12 16:41:27 +01:00
constructor(props: {}) {
super(props);
this.timerId = undefined;
2021-11-12 17:59:14 +01:00
const urlParams = new URLSearchParams(window.location.search);
this.refreshSeconds = Number(urlParams.get("refreshSeconds"));
2021-11-13 15:53:10 +01:00
const velovStationIds = urlParams.get("velovStationIds");
if (velovStationIds) {
this.monitoredVelovStationIds = velovStationIds.split(";");
}
2021-11-14 12:26:42 +01:00
this.monitoredTclStopId = urlParams.get("tclStopId") || "290";
2021-11-12 17:59:14 +01:00
if (this.refreshSeconds <= 5) {
this.refreshSeconds = 60;
}
2021-11-14 12:26:42 +01:00
this.state = {stations: []};
2021-11-12 16:41:27 +01:00
}
render() {
2021-11-12 17:36:30 +01:00
return <div>
<Container className="main">
2021-11-14 10:44:30 +01:00
<Row sm={2} md={3} className="g-4">
2021-11-14 12:26:42 +01:00
{this.state.tcl?.passages.map((ligne) => <Tcl key={ligne.ligne} {...ligne}/>)}
{this.state.stations.map((station) => <Velov key={station.info.station_id} {...station}/>)}
</Row>
<Row>
<Col>
2021-11-14 12:26:42 +01:00
<Button variant="secondary" size="lg" onClick={this.refreshData}>Refresh</Button>
</Col>
</Row>
</Container>
2021-11-12 17:36:30 +01:00
<footer>
<Container>
{process.env.REACT_APP_VERSION} built on {process.env.REACT_APP_DATE}.
Refreshed: {this.state.refreshDate}.
</Container>
2021-11-12 17:36:30 +01:00
</footer>
2021-11-12 16:41:27 +01:00
</div>;
}
componentDidMount() {
this.refreshAndSetupTimer();
2021-11-12 16:41:27 +01:00
}
componentWillUnmount() {
this.resetTimer();
}
private refreshData = () => {
this.resetTimer();
this.refreshAndSetupTimer();
}
private resetTimer() {
2021-11-12 16:41:27 +01:00
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
2021-11-12 16:41:27 +01:00
}
}
private refreshAndSetupTimer() {
this.refresh();
this.timerId = setInterval(this.refresh.bind(this), this.refreshSeconds * 1000);
}
private refresh() {
2021-11-12 16:41:27 +01:00
const headers = new Headers();
headers.set("Authorization", `Basic ${process.env.REACT_APP_TCL_AUTH}`);
2021-11-14 12:26:42 +01:00
const tclPromise = http<ITclFilteredApi>(`https://tcl.augendre.info/stop/${this.monitoredTclStopId}`, {
2021-11-13 15:53:10 +01:00
method: "GET",
headers: headers
2021-11-12 16:41:27 +01:00
});
2021-11-13 15:53:10 +01:00
const velovInfoPromise = http<IStationsInfoWrapper>("https://transport.data.gouv.fr/gbfs/lyon/station_information.json", {method: "GET"});
const velovStatusPromise = http<IStationsStatusWrapper>("https://transport.data.gouv.fr/gbfs/lyon/station_status.json", {method: "GET"});
Promise.all([tclPromise, velovInfoPromise, velovStatusPromise]).then(values => {
2021-11-14 12:46:35 +01:00
let tcl = values[0];
let velovInfo = values[1];
let velovStatus = values[2];
let tclResult: ITclFilteredApi|undefined;
2021-11-13 15:53:10 +01:00
const stationsInfo: Record<string, IStationInfo> = {};
2021-11-14 12:46:35 +01:00
if (isError(tcl)) {
console.error(tcl);
tclResult = undefined;
2021-11-13 15:53:10 +01:00
}
2021-11-14 12:46:35 +01:00
else {
tclResult = tcl;
2021-11-13 15:53:10 +01:00
}
2021-11-14 12:46:35 +01:00
2021-11-13 15:53:10 +01:00
const stations: IVelovStation[] = [];
2021-11-14 12:46:35 +01:00
if (isError(velovInfo) || isError(velovStatus)) {
console.error(velovInfo);
console.error(velovStatus);
} else {
velovInfo = velovInfo as IStationsInfoWrapper;
for (const stationInfo of velovInfo.data.stations) {
if (this.monitoredVelovStationIds.includes(stationInfo.station_id)) {
stationsInfo[stationInfo.station_id] = stationInfo;
}
}
velovStatus = velovStatus as IStationsStatusWrapper;
const stationsDict = new Map<string, IVelovStation>();
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);
}
}
for (const monitoredVelovStationId of this.monitoredVelovStationIds) {
const stationInfo = stationsDict.get(monitoredVelovStationId);
if (stationInfo) {
stations.push(stationInfo);
}
2021-11-13 15:53:10 +01:00
}
}
2021-11-14 12:46:35 +01:00
2021-11-13 15:53:10 +01:00
this.setState({
refreshDate: new Date().toLocaleString("fr-fr"),
2021-11-14 12:46:35 +01:00
tcl: tclResult,
2021-11-13 15:53:10 +01:00
stations: stations,
});
})
2021-11-12 16:41:27 +01:00
}
2021-11-14 12:46:35 +01:00
}
2021-11-12 16:41:27 +01:00
2021-11-14 12:46:35 +01:00
interface ErrorResponse {
detail: string;
}
2021-11-14 12:46:35 +01:00
async function http<T>(request: RequestInfo, init?: RequestInit): Promise<T|ErrorResponse> {
2021-11-12 16:41:27 +01:00
const response = await fetch(request, init);
return await response.json();
}
2021-11-14 12:46:35 +01:00
function isError(response: any): response is ErrorResponse {
return response.detail !== undefined;
}
2021-11-12 16:41:27 +01:00
export default App;