Initial commit

This commit is contained in:
Damien Broqua 2020-03-01 20:40:51 +01:00
commit 80c1729b2f
26 changed files with 311019 additions and 0 deletions

25
src/Components/Footer.js Normal file
View file

@ -0,0 +1,25 @@
import React from 'react';
import { Navbar } from "react-bootstrap";
import PropTypes from 'prop-types';
import GasTypes from "./GasTypes";
const Footer = (props) => {
const {
selectGasType,
selectedGasType,
} = props;
return (
<Navbar bg="light" variant="light" fixed="bottom">
<GasTypes selectedGasType={selectedGasType} selectGasType={selectGasType} />
</Navbar>
);
};
Footer.propTypes = {
selectedGasType: PropTypes.string.isRequired,
selectGasType: PropTypes.func.isRequired,
};
export default Footer;

View file

@ -0,0 +1,79 @@
import React from 'react';
import { Modal, Button, ListGroup } from "react-bootstrap";
import PropTypes from 'prop-types';
class GasStation extends React.Component {
renderPrices = () => {
const {
selectedGasStation,
} = this.props;
return (
<ListGroup variant="flush">
{selectedGasStation.prices ? selectedGasStation.prices.map(price => {
return (
<ListGroup.Item key={price.type}>
{`${price.type} : ${price.price}`}
</ListGroup.Item>
);
}) : (null)}
</ListGroup>
)
}
render () {
const {
showModal,
hideModal,
selectedGasStation,
} = this.props;
return (
<Modal
size="xl"
aria-labelledby="contained-modal-title-vcenter"
centered
show={showModal}
onHide={hideModal}
>
<Modal.Header closeButton>
<Modal.Title>
{`${selectedGasStation.address} - ${selectedGasStation.city}`}
</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.renderPrices()}
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={() => hideModal(true)}>
<img
className="locationIcon"
src="/waze.png"
alt="S'y rendre"
/>
</Button>
</Modal.Footer>
</Modal>
);
}
}
GasStation.defaultProps = {
selectedGasStation: {
address: null,
city: null,
prices: []
}
};
GasStation.propTypes = {
showModal: PropTypes.bool.isRequired,
hideModal: PropTypes.func.isRequired,
selectedGasStation: PropTypes.shape({
address: PropTypes.string,
city: PropTypes.string,
prices: PropTypes.array
}),
};
export default GasStation;

View file

@ -0,0 +1,64 @@
import React from 'react';
import { Form, Row, Col } from "react-bootstrap";
import PropTypes from 'prop-types';
class GasTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
gasTypes: [
{
name: "Ethanol e85",
type: 'E85',
},
{
name: "Sans plomb 95 E10",
type: "E10"
},
{
name: "Sans plomb 95",
type: "SP95"
},
{
name: "Sans plomb 98",
type: "SP98"
},
{
name: "Gazole",
type: "Gazole"
},
{
name: "GPL",
type: "GPLc"
}
]
};
}
render() {
const {
gasTypes,
} = this.state;
const {
selectGasType,
selectedGasType,
} = this.props;
return (
<Row style={{ width: "100%" }}>
<Col>
<Form.Control as="select" value={selectedGasType} onChange={selectGasType}>
{gasTypes.map(gasType => (<option key={gasType.type} value={gasType.type}>{gasType.name}</option>))}
</Form.Control>
</Col>
</Row>
);
}
}
GasTypes.propTypes = {
selectedGasType: PropTypes.string.isRequired,
selectGasType: PropTypes.func.isRequired,
};
export default GasTypes;

187
src/Components/Map.js Normal file
View file

@ -0,0 +1,187 @@
import React from 'react';
import ReactMapGL, { Marker } from 'react-map-gl';
import { Button } from "react-bootstrap";
import XmlReader from 'xml-reader';
import { withToastManager } from 'react-toast-notifications';
import PropTypes from 'prop-types';
import { haveSelectedGas, extractGasStationFromXml } from '../helpers';
import 'mapbox-gl/dist/mapbox-gl.css';
const mapboxToken = 'pk.eyJ1IjoiZGFya291IiwiYSI6ImNrNzkwdmlsdTBtMmwzZnM0ZmI4Z3h4czIifQ.GU2CdcMiKiApHNhI0ylGtQ';
class Map extends React.Component {
constructor(props) {
super(props);
this.state = {
viewport: {
width: '100vw',
height: '100vh',
latitude: 44.837789,
longitude: -0.57918,
zoom: 11,
},
userLocation: {
// latitude: 44.837789,
// longitude: -0.57918,
},
gasStations: [],
};
this.mapRef = React.createRef();
this.loadGasStations();
}
componentDidMount() {
this.setUserLocation();
}
/**
* Méthode permettant de mettre à jour la position de la map
* @param {Object} viewport
*/
onViewportChange = (viewport) => {
this.setState({ viewport });
}
/**
* Méthode permettant de sauvegarder la position de l'utilisateur
*/
setUserLocation() {
navigator.geolocation.getCurrentPosition((position) => {
const setUserLocation = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
};
const newViewport = {
height: '100vh',
width: '100vw',
latitude: position.coords.latitude,
longitude: position.coords.longitude,
zoom: 10,
};
this.setState({
viewport: newViewport,
userLocation: setUserLocation,
});
});
}
/**
* Méthode permettant de filter sur la liste des stations affichables sur la carte
* @param {Object} gasStation
* @return {Boolean}
*/
displayThisGasStation = (gasStation) => {
const mapGL = this.mapRef.getMap();
const bounds = mapGL.getBounds();
return (bounds._ne.lat > gasStation.latitude && bounds._sw.lat < gasStation.latitude)
&& (bounds._ne.lng > gasStation.longitude && bounds._sw.lng < gasStation.longitude);
}
/**
* Méthode permettant de charger le fichier xml contenant la liste des stations
*/
loadGasStations() {
const {
toastManager,
} = this.props;
fetch('/gasStations.xml')
.then((response) => response.text())
.then((response) => {
const reader = XmlReader.create();
reader.on('done', (data) => {
const pdv = data.children;
const gasStations = [];
for (let i = 0; i < pdv.length; i += 1) {
const currentPdv = pdv[i];
gasStations.push(extractGasStationFromXml(currentPdv));
}
this.setState((prevState) => ({
...prevState,
gasStations,
}));
});
reader.parse(response);
}).catch(() => {
toastManager.add('Erreur lors du chargement de la liste des stations', { appearance: 'error', autoDismiss: true });
});
}
/**
* Méthode gérant le rendu de la vue
*/
render() {
const {
viewport,
userLocation,
gasStations,
} = this.state;
const {
showGasStation,
selectedGasType,
} = this.props;
return (
<>
<ReactMapGL
{...viewport}
mapStyle="mapbox://styles/mapbox/outdoors-v11"
onViewportChange={this.onViewportChange}
mapboxApiAccessToken={mapboxToken}
ref={(map) => { if (map) this.mapRef = map; }}
>
{
Object.keys(userLocation).length !== 0 ? (
<Marker
latitude={userLocation.latitude}
longitude={userLocation.longitude}
>
<img className="locationIcon" src="/car.png" alt="My position" />
</Marker>
) : (null)
}
{gasStations.filter(this.displayThisGasStation).filter((station) => haveSelectedGas(station, selectedGasType)).map((gasStation) => (
<Marker
key={gasStation.id}
latitude={gasStation.latitude}
longitude={gasStation.longitude}
>
<Button
variant="link"
onClick={() => showGasStation(gasStation)}
onFocus={() => { }}
onBlur={() => { }}
>
<img
className="locationIcon"
src="/gas-station.png"
alt={`${gasStation.id}`}
/>
</Button>
</Marker>
))}
</ReactMapGL>
</>
);
}
}
Map.propTypes = {
selectedGasType: PropTypes.string.isRequired,
showGasStation: PropTypes.func.isRequired,
toastManager: PropTypes.shape({
add: PropTypes.func,
}).isRequired,
};
export default withToastManager(Map);