Skip to content

Commit

Permalink
Added service maps
Browse files Browse the repository at this point in the history
  • Loading branch information
gareth.james committed Oct 21, 2023
1 parent f3728e9 commit b1815c6
Show file tree
Hide file tree
Showing 23 changed files with 667 additions and 4 deletions.
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="google-adsense-account" content="ca-pub-7493568358648316">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Roboto:wght@100;400&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;400&display=swap" rel="stylesheet">
<link rel="shortcut icon" href="/cdn/static/assets/icons/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" href="/favicon-196x196.png" sizes="196x196">
<link rel="icon" type="image/png" href="/favicon-160x160.png" sizes="160x160">
Expand Down
25 changes: 25 additions & 0 deletions client/src/components/Label.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import propTypes from 'prop-types';
import React from 'react';

Label.propTypes = {
id: propTypes.string.isRequired,
text: propTypes.string.isRequired,
};

function Label(props) {
const {
id,
text,
} = props;

return (
<label
className="label"
htmlFor={id}
>
{text}
</label>
);
}

export default Label;
180 changes: 180 additions & 0 deletions client/src/components/Map.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import {
useFetchLinesQuery,
} from '@api/linesApi';

import {
setLine,
} from '@stores/storeSliceLines';

import Alert from '@components/Alert';
import Error from '@components/Error';
import Loading from '@components/Loading';
import MapIconInternationalRail from '@components/icons/MapIconInternationalRail';
import MapIconNationalRail from '@components/icons/MapIconNationalRail';
import Select from '@components/Select';

Map.propTypes = {
service: PropTypes.object,
};

function Map(props) {
const {
service,
} = props;

const {
data,
error,
isLoading,
} = useFetchLinesQuery(service.id);

const dispatch = useDispatch();
const routes = useSelector((state) => state.lines.lines[service.id]);
const [ currentRoute, setCurrentRoute ] = useState();
const [ mapLoading, setMapLoading ] = useState();

const lineHasMultipleRoutes = routes && routes.length > 1;

useEffect(() => {
setMapLoading(true);
}, [ service ]);

useEffect(() => {
if (!routes) return;

setCurrentRoute(routes[0]);
setMapLoading(false);
}, [ routes ]);

useEffect(() => {
if (!data) return;

dispatch(setLine({
data,
id: service.id,
}));
}, [ data ]);

const loading = isLoading || mapLoading;

const stationHasInternationalRailInterchange = (station) => {
return [
'King\'s Cross & St Pancras International',
'Stratford International',
].includes(station.name);
};

return (
<div className='map'>
<h2>
Map
</h2>
{
error && (
<Error
message={error.data.message}
status={error.status}
/>
)
}
{
!error && (
<>
{
!loading && lineHasMultipleRoutes && (
<>
<Alert
text="This line has multiple routes. Please select a route to view a map."
type="info"
/>
<Select
id="map-route"
items={routes}
label="Select a route"
onChange={(event) => {
setCurrentRoute(routes.find((route) => route.name === event.target.value));
}}
/>
</>
)
}
<div className="map__diagram">
{
loading && (
<Loading />
)
}
{
!loading && (
<ul className="map__list">
{currentRoute && currentRoute.stations.map((station, index) => (
<li
className="map__list-item"
key={`${station.name}-${index}`}
>
<div className={`map__line brand-background--id-${service.id} brand-background--mode-${service.mode}`}>
<div className={`map__marker ${(station.interchanges.length || station.hasNationalRailInterchange || stationHasInternationalRailInterchange(station)) ? 'map__marker--interchange' : `map__marker--regular map__line brand-background--id-${service.id} brand-background--mode-${service.mode}`}`} />
<div className="map__station">
<span className="map__name">
<span className="visually-hidden">
Station stop:
</span>
{station.name}
</span>
{
station.hasNationalRailInterchange && (
<MapIconNationalRail />
)
}
{
stationHasInternationalRailInterchange(station) && (
<MapIconInternationalRail />
)
}
</div>
{
!!station.interchanges && (
<>
<span className="visually-hidden">
{station.name} interchanges:
</span>
<ul className="map__interchanges-list">
{station.interchanges.map((interchange) => (
<li
className="map__interchanges-list-item"
key={interchange.id}
>
<div className={`map__interchange brand-background--id-${interchange.id}`}>
<Link
className="map__link"
to={`/service/${interchange.id}`}
>
{interchange.name}
</Link>
</div>
</li>
))}
</ul>
</>
)
}
</div>
</li>
))}
</ul>
)
}
</div>
</>
)
}
</div>
);
}

export default Map;
45 changes: 45 additions & 0 deletions client/src/components/Select.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import propTypes from 'prop-types';
import React from 'react';

import Label from '@components/Label';

Select.propTypes = {
id: propTypes.string.isRequired,
items: propTypes.array.isRequired,
label: propTypes.string.isRequired,
onChange: propTypes.func.isRequired,
};

function Select(props) {
const {
id,
items,
label,
onChange,
} = props;

return (
<>
<Label
id={id}
text={label}
/>
<select
className="select"
id={id}
onChange={onChange}
>
{items.map((item) => (
<option
key={item.id}
value={item.name}
>
{item.name}
</option>
))}
</select>
</>
);
}

export default Select;
24 changes: 24 additions & 0 deletions client/src/components/icons/MapIconInternationalRail.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

function MapIconNationalRail() {
return (
<svg
className="map__icon map__icon--international-rail"
height="11"
viewBox="0 0 40.2 11"
width="40.2"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.9 10.8V7.5h2.2v.4h-1.7v.9h1.5v.4h-1.5v1.1h1.8v.4l-2.3.1zm3.2-1.3v-2h.5v1.9c0 .5.2 1 .9 1s.9-.4.9-.9V7.6h.5v2c0 .8-.4 1.3-1.5 1.3-.9 0-1.3-.7-1.3-1.4zm3.8 1.3V7.5h.9c.9 0 1.4.3 1.4 1 0 .4-.2.7-.7.9l1 1.4v.1h-.6L20 9.7h-.7v1.2l-.4-.1zm1.4-1.6c.2-.1.4-.3.4-.6 0-.4-.2-.5-.9-.5h-.4v1.2l.9-.1zm1.6 0c0-.9.6-1.7 1.7-1.7s1.7.8 1.7 1.7c0 .9-.6 1.7-1.7 1.7s-1.7-.8-1.7-1.7zm2.9 0c0-.7-.4-1.3-1.2-1.3-.8 0-1.2.5-1.2 1.2s.4 1.3 1.2 1.3c.7.1 1.2-.4 1.2-1.2zm1.1 1.5v-.5h.1c.3.1.7.2 1 .2.6 0 .9-.2.9-.5s-.2-.4-.6-.6l-.5-.2c-.6 0-.8-.3-.8-.7 0-.6.5-.9 1.3-.9.3 0 .8.1 1 .2v.4h-.1c-.3 0-.7-.1-.9-.1-.5 0-.8.1-.8.4 0 .3.2.4.5.5l.5.2c.5.2.8.4.8 1s-.5.9-1.4.9c-.4 0-.7-.1-1-.3zm4.1.2V8h-1.2v-.4h2.8V8h-1.1v2.9H30zm1.7 0L33 7.7h.6l1.2 3.2v.1h-.5l-.3-.9h-1.4l-.4.9-.5-.1zm2.1-1.2-.5-1.5-.6 1.5h1.1zm1.7 1.3V7.7h.9c.9 0 1.4.3 1.4 1 0 .4-.2.7-.7.9l1 1.4h-.6l-.9-1.2h-.7V11h-.4zm1.4-1.7c.2-.1.4-.3.4-.6 0-.4-.2-.5-.9-.5H36v1.2l.9-.1zm1.8-.8v-.7h-.2v-.1h.6v.1h-.2v.7h-.2zm1.3.1V8l-.2.3h-.1l-.2-.3v.5h-.2v-.8h.2l.2.4.3-.4h.2v.8H40z"
style={{ fill: '#fbdd10' }}
/>
<path
d="M11.5 3.4c.2-.3.4-.5.5-.8 0-.1 0-.2.1-.2v-.3c0-1.1-1.1-2-2.4-2h-.2C7.7 0 6.5 1 5.9 1.7 4.5 3 3.6 4.8 3.3 6.7c-.3.1-.7.3-1 .4-.3 0-.6.1-1 .2-.2.1-.5.1-.8.1H.1c-.1 0-.1.1-.1.2 0 .2.3.5 1 .5.6 0 1.4-.2 2.3-.4v-.1c0 1 .2 1.9.6 2.4.3.3.8.5 1.2.5.5.1 1.1 0 1.6-.2 1.1-.4 2.1-1 3.1-1.7.1-.1.3-.2.4-.3.1-.1.2-.1.3-.2.1-.1.3-.2.4-.3l.1-.1V7.4h-.1c-.1 0-.2.1-.3.1-.1.1-.2.1-.3.1-.1 0-.2.1-.3.1-.1.2-.3.3-.4.4-.1 0-.2.1-.3.1-.6.2-1.1.5-1.7.6-.6.2-1.6.5-2-.2-.1-.3-.1-.6-.1-.9 0-.2 0-.5.1-.7.4-.1.8-.3 1.1-.4.6-.3 1.3-.6 2-.9.4-.1.7-.3 1.1-.4.3-.1.7-.2 1-.3.4-.1.7-.2 1.1-.3.8-.1 1.6-.1 2.4 0 .4.1.7.2 1.1.3.7.2 1.4.4 2 .6 1.1.3 2.1.5 3.2.6h.5c1 0 2-.1 2.9-.4.6-.2 1.1-.4 1.6-.7h.1c.1-.1.3-.2.4-.3.2-.1.4-.3.6-.5.3 0 .3-.1.4-.2.1-.1.2-.2.2-.3v-.2s0-.1-.1 0c-.1 0-.2.1-.3.2l-.6.3c-.5.2-1 .3-1.4.4-1.4.3-2.9.3-4.3.2-1.4-.2-2.7-.6-4.1-.9-.4-.1-.9-.2-1.3-.2-.4-.1-.9-.1-1.3-.1h-1.3c-.4 0-.9.1-1.3.1l.2-.2m-1.5.2c-.8.7-1.5.9-2.5 1.4-.6.2-1.2.5-1.8.7.3-1 .8-1.9 1.5-2.8.3-.4.6-.7 1-1 .6-.5 1.5-.9 2.1-.3.2.3.3.6.3 1-.1.4-.3.7-.6 1"
style={{ fill: '#25b5c9' }}
/>
</svg>
);
}

export default MapIconNationalRail;
20 changes: 20 additions & 0 deletions client/src/components/icons/MapIconNationalRail.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

function MapIconNationalRail() {
return (
<svg
className="map__icon map__icon--national-rail"
height="11"
viewBox="0 0 17.4 11"
width="17.4"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 4.3 8.4 6.7h9v1.7h-9l5.5 2.6H9.8L4.3 8.4H0V6.7h4.3L9 4.3H0V2.6h9L3.5 0h4L13 2.6h4.4v1.7z"
style={{ fill: '#ed1c24' }}
/>
</svg>
);
}

export default MapIconNationalRail;
3 changes: 3 additions & 0 deletions client/src/scss/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,18 @@
@import './partials/components/container';
@import './partials/components/hr';
@import './partials/components/icon';
@import './partials/components/label';
@import './partials/components/loading';
@import './partials/components/loading-spinner';
@import './partials/components/map';
@import './partials/components/navigation-menu';
@import './partials/components/navigation-primary';
@import './partials/components/page-footer';
@import './partials/components/page-header';
@import './partials/components/page';
@import './partials/components/pinned-services';
@import './partials/components/pull-to-refresh-message';
@import './partials/components/select';
@import './partials/components/service';
@import './partials/components/services';
@import './partials/components/services-table';
Expand Down
1 change: 1 addition & 0 deletions client/src/scss/partials/components/_loading-spinner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ $loading-spinner-animation-duration: 1.8s;

.loading-spinner {
position: relative;
transform: translateY($loading-spinner-size * -1);
animation-delay: $loading-spinner-animation-delay;

.ptr__loader & {
Expand Down
Loading

0 comments on commit b1815c6

Please sign in to comment.