website/src/views/components/map/ArrivalTimes.tsx
import { memo } from 'react';
import classnames from 'classnames';
import { entries, sortBy } from 'lodash';
import { RefreshCw as Refresh } from 'react-feather';
import { BusTiming, NextBus, NextBusTime } from 'types/venues';
import styles from './BusStops.scss';
type Props = BusTiming & {
name: string;
code: string;
reload: (code: string) => void;
};
/**
* Extract the route name from the start of a string
*/
const routes = ['A1', 'A2', 'B1', 'B2', 'C', 'D1', 'D2', 'BTC1', 'BTC2'];
export function extractRoute(route: string) {
for (let i = 0; i < routes.length; i++) {
if (route.startsWith(routes[i])) return routes[i];
}
return null;
}
/**
* Adds 'min' to numeric timings and highlight any buses that are arriving
* soon with a <strong> tag
*/
function renderTiming(time: NextBusTime) {
if (typeof time === 'number') {
if (time <= 3) return <strong>{time} min</strong>;
return `${time} min`;
}
if (time === 'Arr') return <strong>{time}</strong>;
return time;
}
/**
* Route names with parenthesis in them don't have a space in front of the
* opening bracket, causing the text to wrap weirdly. This forces the opening
* paren to always have a space in front of it.
*/
function fixRouteName(name: string) {
return name.replace(/\s?\(/, ' (');
}
export const ArrivalTimes = memo<Props>((props: Props) => {
if (props.error) {
return (
<>
<h3 className={styles.heading}>{props.name}</h3>
<p>Error loading arrival times</p>
<button
type="button"
className="btn btn-sm btn-primary"
onClick={() => props.reload(props.code)}
>
Retry
</button>
</>
);
}
// Make sure the routes are sorted
const timings = props.timings ? sortBy(entries(props.timings), ([route]) => route) : [];
return (
<>
<h3 className={styles.heading}>{props.name}</h3>
{props.timings && (
<table className={classnames(styles.timings, 'table table-sm')}>
<tbody>
{timings.map(([routeName, timing]: [string, NextBus]) => {
const route = extractRoute(routeName);
const className = route
? classnames(styles.routeHeading, styles[`route${route}`])
: '';
return (
<tr key={routeName}>
<th className={className}>{fixRouteName(routeName)}</th>
<td>{renderTiming(timing.arrivalTime)}</td>
<td>{renderTiming(timing.nextArrivalTime)}</td>
</tr>
);
})}
</tbody>
</table>
)}
<button
type="button"
className={classnames('btn btn-sm btn-link btn-svg', styles.refreshBtn, {
[styles.isLoading]: props.isLoading,
})}
disabled={props.isLoading}
onClick={() => props.reload(props.code)}
>
<Refresh size={14} className={styles.refreshIcon} />
{props.isLoading ? 'Loading...' : 'Refresh'}
</button>
</>
);
});