Add velov stations
This commit is contained in:
parent
6c96a05002
commit
11cfe19650
7 changed files with 148 additions and 30 deletions
75
src/App.tsx
75
src/App.tsx
|
@ -1,43 +1,57 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import Ligne from "./Ligne";
|
import Tcl, {ILigne} from "./Tcl";
|
||||||
import {ILigne} from "./interfaces";
|
import {clearInterval, setInterval} from "timers";
|
||||||
import {setInterval, clearInterval} from "timers";
|
import {Col, Container, Dropdown, DropdownButton, Row} from "react-bootstrap";
|
||||||
import {Col, Row, Container, Dropdown, DropdownButton} from "react-bootstrap";
|
import {IStationInfo, IStationsInfoWrapper, IStationsStatusWrapper, IVelovStation} from "./IVelov";
|
||||||
|
import Velov from "./Velov";
|
||||||
|
|
||||||
|
|
||||||
|
interface ITclFilteredApi {
|
||||||
|
passages: ILigne[];
|
||||||
|
}
|
||||||
|
|
||||||
interface IAppState {
|
interface IAppState {
|
||||||
passages: ILigne[];
|
passages: ILigne[];
|
||||||
refreshDate?: string;
|
refreshDate?: string;
|
||||||
|
stations: IVelovStation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends React.Component<{}, IAppState> {
|
class App extends React.Component<{}, IAppState> {
|
||||||
timerId?: ReturnType<typeof setInterval>;
|
timerId?: ReturnType<typeof setInterval>;
|
||||||
refreshSeconds: number;
|
refreshSeconds: number;
|
||||||
|
monitoredVelovStationIds: string[] = [];
|
||||||
|
|
||||||
constructor(props: {}) {
|
constructor(props: {}) {
|
||||||
super(props);
|
super(props);
|
||||||
this.timerId = undefined;
|
this.timerId = undefined;
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
this.refreshSeconds = Number(urlParams.get("refreshSeconds"));
|
this.refreshSeconds = Number(urlParams.get("refreshSeconds"));
|
||||||
|
const velovStationIds = urlParams.get("velovStationIds");
|
||||||
|
if (velovStationIds) {
|
||||||
|
this.monitoredVelovStationIds = velovStationIds.split(";");
|
||||||
|
}
|
||||||
if (this.refreshSeconds <= 5) {
|
if (this.refreshSeconds <= 5) {
|
||||||
this.refreshSeconds = 60;
|
this.refreshSeconds = 60;
|
||||||
}
|
}
|
||||||
this.state = {passages: [{ligne: undefined, delais: [undefined]}]};
|
this.state = {passages: [{ligne: undefined, delais: [undefined]}], stations: []};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
<Container className="main">
|
<Container className="main">
|
||||||
<Row md={4}>
|
<Row sm={2}>
|
||||||
{this.state.passages.map((ligne) => <Ligne key={ligne.ligne} ligne={ligne.ligne}
|
{this.state.passages.map((ligne) => <Tcl key={ligne.ligne} ligne={ligne.ligne}
|
||||||
delais={ligne.delais}/>)}
|
delais={ligne.delais}/>)}
|
||||||
|
</Row>
|
||||||
|
<Row sm={2}>
|
||||||
|
{this.state.stations.map((station) => <Velov key={station.info.station_id} info={station.info} status={station.status}/>)}
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<DropdownButton variant="secondary" size="lg" title="Refresh">
|
<DropdownButton variant="secondary" size="lg" title="Refresh">
|
||||||
<Dropdown.Item onClick={this.refreshData}>Data</Dropdown.Item>
|
<Dropdown.Item onClick={this.refreshData}>Data</Dropdown.Item>
|
||||||
<Dropdown.Item onClick={this.reload}>Full page</Dropdown.Item>
|
<Dropdown.Item onClick={this.reload}>Full page</Dropdown.Item>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -79,10 +93,45 @@ class App extends React.Component<{}, IAppState> {
|
||||||
private refresh() {
|
private refresh() {
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.set("Authorization", `Basic ${process.env.REACT_APP_TCL_AUTH}`);
|
headers.set("Authorization", `Basic ${process.env.REACT_APP_TCL_AUTH}`);
|
||||||
http<IAppState>("https://tcl.augendre.info/stop/290", {method: "GET", headers: headers}).then(json => {
|
const tclPromise = http<ITclFilteredApi>("https://tcl.augendre.info/stop/290", {
|
||||||
json.refreshDate = new Date().toLocaleString("fr-fr");
|
method: "GET",
|
||||||
this.setState(json);
|
headers: headers
|
||||||
});
|
});
|
||||||
|
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 => {
|
||||||
|
const tcl = values[0];
|
||||||
|
const velovInfo = values[1];
|
||||||
|
const stationsInfo: Record<string, IStationInfo> = {};
|
||||||
|
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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 = () => {
|
private reload = () => {
|
||||||
|
|
39
src/IVelov.ts
Normal file
39
src/IVelov.ts
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {ILigne, PassageType} from "./interfaces";
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||||
import {faBus} from '@fortawesome/free-solid-svg-icons'
|
import {faBus} from '@fortawesome/free-solid-svg-icons'
|
||||||
import {Card, Col, ListGroup} from "react-bootstrap";
|
import {Card, Col, ListGroup} from "react-bootstrap";
|
||||||
|
import {placeholder} from "./utils";
|
||||||
|
|
||||||
|
export type PassageType = string | undefined;
|
||||||
|
|
||||||
export default class Ligne extends React.Component<ILigne> {
|
export interface ILigne {
|
||||||
|
ligne?: string;
|
||||||
|
delais: PassageType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Tcl extends React.Component<ILigne> {
|
||||||
render() {
|
render() {
|
||||||
return <Col>
|
return <Col>
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -40,11 +46,3 @@ class Passage extends React.Component<IPassageProps> {
|
||||||
return <ListGroup.Item>{placeholder(this.props.passage)}</ListGroup.Item>
|
return <ListGroup.Item>{placeholder(this.props.passage)}</ListGroup.Item>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeholder(value?: string) {
|
|
||||||
if (value === undefined) {
|
|
||||||
return <span className="placeholder"/>
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
3
src/Velov.css
Normal file
3
src/Velov.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.card-header {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
27
src/Velov.tsx
Normal file
27
src/Velov.tsx
Normal file
|
@ -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<IVelovStation> {
|
||||||
|
render() {
|
||||||
|
return <Col>
|
||||||
|
<Card>
|
||||||
|
<Card.Header>
|
||||||
|
<FontAwesomeIcon icon={faBiking}/> {this.props.info.name.toLowerCase()}
|
||||||
|
</Card.Header>
|
||||||
|
<ListGroup variant="flush">
|
||||||
|
<ListGroup.Item>
|
||||||
|
<FontAwesomeIcon icon={faBicycle}/> {this.props.status.num_bikes_available}
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<FontAwesomeIcon icon={faAnchor}/> {this.props.status.num_docks_available}
|
||||||
|
</ListGroup.Item>
|
||||||
|
</ListGroup>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
export type PassageType = string|undefined;
|
|
||||||
|
|
||||||
export interface ILigne {
|
|
||||||
ligne?: string;
|
|
||||||
delais: PassageType[];
|
|
||||||
}
|
|
||||||
|
|
9
src/utils.tsx
Normal file
9
src/utils.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function placeholder(value?: string) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return <span className="placeholder"/>
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue