Skip to content

Commit a679805

Browse files
kagiurazwliew
authored andcommitted
refactor: ♻️ separate api errors and actually having no more buses
1 parent 70374d5 commit a679805

File tree

3 files changed

+157
-75
lines changed

3 files changed

+157
-75
lines changed

website/src/apis/nextbus-new.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ const baseURL = 'https://nusmods.com'; // TODO: wait until we have an api proxy
22

33
export const getStopTimings = async (
44
stop: string,
5-
setState: (state: ShuttleServiceResult) => void,
5+
callback?: (data: any) => void,
6+
error?: (e: any) => void,
67
) => {
78
if (!stop) return;
89
const API_AUTH = ''; // TODO: wait until we have an api proxy
@@ -15,8 +16,9 @@ export const getStopTimings = async (
1516
});
1617
const data = await response.json();
1718
// console.log(data);
18-
setState(data.ShuttleServiceResult);
19+
if (callback) callback(data.ShuttleServiceResult);
1920
} catch (e) {
20-
console.error(e);
21+
// console.error(e);
22+
if (error) error(e);
2123
}
2224
};

website/src/utils/mobility.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ export const getShownArrivalTime = (eta: number, forceTime = false) => {
8585
});
8686
};
8787

88-
export const getDepartAndArriveTiming = (timings = [] as NUSShuttle[], isEnd: boolean) => {
88+
export const getDepartAndArriveTiming = (timings: NUSShuttle[] | string = [], isEnd: boolean) => {
89+
if (typeof timings === 'string') return { departTiming: undefined, arriveTiming: undefined };
90+
8991
if (isEnd) {
9092
const departTiming = timings.find((t) => t.busstopcode.endsWith('-S'));
9193
const arriveTiming = timings.find((t) => t.busstopcode.endsWith('-E')) || timings[0];

website/src/views/mobility/StopDetails.tsx

Lines changed: 149 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function ServiceSchedule(props: { timing?: NUSShuttle; title: string }) {
6060

6161
function StopServiceDetails(props: {
6262
service: ISBService;
63-
timings?: NUSShuttle[];
63+
timings?: NUSShuttle[] | string;
6464
currentStop: ISBStop;
6565
selectedService: string | null;
6666
setSelectedService: (service: string | null) => void;
@@ -282,10 +282,29 @@ function StopServiceDetails(props: {
282282
</div>
283283
)}
284284
<div className={styles.divider} />
285-
<div className={styles.serviceUpcoming}>
286-
{arriveTiming && <ServiceSchedule timing={arriveTiming} title="Arrivals" />}
287-
{departTiming && <ServiceSchedule timing={departTiming} title="Departures" />}
288-
</div>
285+
{
286+
// if the service is not running, show the next time it will run
287+
!departTiming && !arriveTiming ? (
288+
<div className={styles.serviceSchedule}>
289+
{/* if is error show error msg */}
290+
{timings === 'error' ? (
291+
<div className={classNames(styles.upcomingBuses, styles.none)}>
292+
Error fetching bus timings
293+
</div>
294+
) : (
295+
<div className={classNames(styles.upcomingBuses, styles.none)}>
296+
No upcoming departures
297+
</div>
298+
)}
299+
</div>
300+
) : (
301+
// if the service is running, show the next few buses
302+
<div className={styles.serviceUpcoming}>
303+
{arriveTiming && <ServiceSchedule timing={arriveTiming} title="Arrivals" />}
304+
{departTiming && <ServiceSchedule timing={departTiming} title="Departures" />}
305+
</div>
306+
)
307+
}
289308
</div>
290309
)}
291310
</div>
@@ -333,77 +352,125 @@ function StopDetails(props: Props) {
333352
const { stop } = props;
334353
const setSelectedServiceMap = props.setSelectedService;
335354
const stopDetails = isbStops.find((s) => s.name === stop);
336-
const [selectedStopTiming, setSelectedStopTiming] = useState<ShuttleServiceResult | null>(null);
355+
const [selectedStopTiming, setSelectedStopTiming] = useState<
356+
ShuttleServiceResult | 'error' | 'loading'
357+
>('loading');
337358
const [selectedService, setSelectedService] = useState<string | null>(null);
338359

339360
useEffect(() => {
340361
if (!stop) return;
341-
getStopTimings(stop, setSelectedStopTiming);
362+
setSelectedStopTiming('loading');
363+
getStopTimings(
364+
stop,
365+
(data) => {
366+
setSelectedStopTiming(data);
367+
},
368+
(error) => {
369+
console.error(error);
370+
setSelectedStopTiming('error');
371+
},
372+
);
342373
}, [stop]);
343374

344375
useEffect(() => {
345376
if (selectedService) {
346-
// console.log('selectedService', selectedService);
347377
setSelectedServiceMap(isbServices.find((s) => s.name === selectedService) || isbServices[0]);
348378
}
349-
}, [selectedService]);
350-
351-
if (!stopDetails) return <div>Stop not found</div>;
352-
353-
// console.log(selectedStopTiming);
354-
355-
const { ShortName, LongName, shuttles } = stopDetails;
356-
const nusShuttles = shuttles
357-
.filter((shuttle) => shuttle.routeid)
358-
.filter(
359-
(shuttle, index, self) => index === self.findIndex((s) => s.routeid === shuttle.routeid),
360-
)
361-
.sort((a, b) => a.name.localeCompare(b.name));
362-
// console.log('shuttles', stopDetails);
363-
364-
const incomingBuses: {
365-
service: ISBService;
366-
arrivingInSeconds: number;
367-
plate: string;
368-
}[] = [];
369-
nusShuttles.forEach((shuttle) => {
370-
const serviceDetail = isbServices.find((s) => s.id === shuttle.name.toLocaleLowerCase());
371-
if (!serviceDetail) return;
372-
const isEnd = stopDetails.name === serviceDetail.stops[serviceDetail.stops.length - 1];
373-
const serviceShuttles = selectedStopTiming?.shuttles.filter(
374-
(s) => s.name === shuttle.name,
375-
) as NUSShuttle[];
376-
const timings = getDepartAndArriveTiming(serviceShuttles, isEnd);
377-
const timing = timings.departTiming;
378-
379-
timing?._etas?.forEach((eta) => {
380-
incomingBuses.push({
381-
service: serviceDetail,
382-
arrivingInSeconds: eta.eta_s,
383-
plate: eta.plate,
379+
}, [selectedService, setSelectedServiceMap]);
380+
381+
const incoming = useMemo(() => {
382+
if (stopDetails === undefined) return null;
383+
384+
const nusShuttles = stopDetails.shuttles
385+
.filter((shuttle) => shuttle.routeid)
386+
.filter(
387+
(shuttle, index, self) => index === self.findIndex((s) => s.routeid === shuttle.routeid),
388+
)
389+
.sort((a, b) => a.name.localeCompare(b.name));
390+
391+
if (selectedStopTiming === 'loading' || selectedStopTiming === 'error') {
392+
return {
393+
services: nusShuttles,
394+
buses: [],
395+
buses_grouped: [],
396+
};
397+
}
398+
399+
const incomingBuses: {
400+
service: ISBService;
401+
arrivingInSeconds: number;
402+
plate: string;
403+
}[] = [];
404+
nusShuttles.forEach((shuttle) => {
405+
const serviceDetail = isbServices.find((s) => s.id === shuttle.name.toLocaleLowerCase());
406+
if (!serviceDetail) return;
407+
const isEnd = stopDetails.name === serviceDetail.stops[serviceDetail.stops.length - 1];
408+
const serviceShuttles = selectedStopTiming?.shuttles.filter(
409+
(s) => s.name === shuttle.name,
410+
) as NUSShuttle[];
411+
const timings = getDepartAndArriveTiming(serviceShuttles, isEnd);
412+
const timing = timings.departTiming;
413+
414+
timing?._etas?.forEach((eta) => {
415+
incomingBuses.push({
416+
service: serviceDetail,
417+
arrivingInSeconds: eta.eta_s,
418+
plate: eta.plate,
419+
});
384420
});
385421
});
386-
});
387-
incomingBuses.sort((a, b) => a.arrivingInSeconds - b.arrivingInSeconds);
388-
incomingBuses.splice(4);
389-
const incomingBusGroups = incomingBuses.reduce((acc, bus) => {
390-
const shownTime = getShownArrivalTime(bus.arrivingInSeconds);
391-
const newAcc = { ...acc };
392-
if (!newAcc[shownTime]) newAcc[shownTime] = [];
393-
newAcc[shownTime].push(bus);
394-
return newAcc;
395-
}, {} as Record<string, typeof incomingBuses>);
396-
397-
const publicShuttles = shuttles
398-
.filter((shuttle) => shuttle.name.startsWith('PUB:'))
399-
.map(
400-
(shuttle) =>
401-
({
402-
...selectedStopTiming?.shuttles?.find((s) => s.name === shuttle.name),
422+
incomingBuses.sort((a, b) => a.arrivingInSeconds - b.arrivingInSeconds);
423+
incomingBuses.splice(4);
424+
const incomingBusGroups = incomingBuses.reduce((acc, bus) => {
425+
const shownTime = getShownArrivalTime(bus.arrivingInSeconds);
426+
const newAcc = { ...acc };
427+
if (!newAcc[shownTime]) newAcc[shownTime] = [];
428+
newAcc[shownTime].push(bus);
429+
return newAcc;
430+
}, {} as Record<string, typeof incomingBuses>);
431+
432+
return {
433+
services: nusShuttles,
434+
buses: incomingBuses,
435+
buses_grouped: incomingBusGroups,
436+
};
437+
}, [selectedStopTiming, stopDetails]);
438+
439+
const incomingPublic = useMemo(() => {
440+
if (stopDetails === undefined) return null;
441+
const { shuttles } = stopDetails;
442+
443+
return shuttles
444+
.filter((shuttle) => shuttle.name.startsWith('PUB:'))
445+
.map((shuttle) => {
446+
let st = {
403447
number: parseInt(shuttle.name.replace('PUB:', ''), 10),
404-
} as PublicShuttle),
405-
)
406-
.sort((a, b) => a.number - b.number);
448+
} as PublicShuttle;
449+
if (selectedStopTiming !== 'loading' && selectedStopTiming !== 'error') {
450+
st = {
451+
...selectedStopTiming?.shuttles?.find((s) => s.name === shuttle.name),
452+
number: parseInt(shuttle.name.replace('PUB:', ''), 10),
453+
} as PublicShuttle;
454+
}
455+
return st;
456+
})
457+
.sort((a, b) => a.number - b.number);
458+
}, [selectedStopTiming, stopDetails]);
459+
460+
if (!stopDetails || !incoming || !incomingPublic) return <div>Stop not found</div>;
461+
462+
const { ShortName, LongName } = stopDetails;
463+
464+
// const publicShuttles = shuttles
465+
// .filter((shuttle) => shuttle.name.startsWith('PUB:'))
466+
// .map(
467+
// (shuttle) =>
468+
// ({
469+
// ...selectedStopTiming?.shuttles?.find((s) => s.name === shuttle.name),
470+
// number: parseInt(shuttle.name.replace('PUB:', ''), 10),
471+
// } as PublicShuttle),
472+
// )
473+
// .sort((a, b) => a.number - b.number);
407474

408475
return (
409476
<div>
@@ -413,8 +480,8 @@ function StopDetails(props: Props) {
413480

414481
<div className={styles.incomingBusesWrapper}>
415482
<ol className={styles.incomingBuses}>
416-
{Object.entries(incomingBusGroups).length ? (
417-
Object.entries(incomingBusGroups).map(([time, buses], i) => (
483+
{Object.entries(incoming.buses_grouped).length ? (
484+
Object.entries(incoming.buses_grouped).map(([time, buses], i) => (
418485
<Fragment key={`${time} ${stopDetails.name}`}>
419486
{i > 0 && <ChevronRight className={styles.chevron} />}
420487
<li className={styles.serviceWithChevron}>
@@ -440,17 +507,28 @@ function StopDetails(props: Props) {
440507
))
441508
) : (
442509
<span className={classNames(styles.noIncoming, 'text-muted')}>
443-
No upcoming buses today
510+
{/* No upcoming buses today */}
511+
{/* if there is an error fetching, show an error msg */}
512+
{selectedStopTiming === 'error'
513+
? 'Error fetching bus timings'
514+
: 'No upcoming buses today'}
444515
</span>
445516
)}
446517
</ol>
447518
</div>
448519

449-
{nusShuttles.map((shuttle) => {
520+
{incoming.services.map((shuttle) => {
450521
const service = isbServices.find((s) => s.id === shuttle.name.toLocaleLowerCase());
451-
const timings = selectedStopTiming?.shuttles.filter(
452-
(s) => s.name === shuttle.name,
453-
) as NUSShuttle[];
522+
let timings;
523+
if (selectedStopTiming === 'loading') {
524+
timings = 'loading';
525+
} else if (selectedStopTiming === 'error') {
526+
timings = 'error';
527+
} else {
528+
timings = selectedStopTiming?.shuttles.filter(
529+
(s) => s.name === shuttle.name,
530+
) as NUSShuttle[];
531+
}
454532
if (!service) return <Fragment key={shuttle.name} />;
455533
return (
456534
<StopServiceDetails
@@ -463,7 +541,7 @@ function StopDetails(props: Props) {
463541
/>
464542
);
465543
})}
466-
{publicShuttles.map((shuttle) => (
544+
{incomingPublic.map((shuttle) => (
467545
<PublicBusDetails service={shuttle} />
468546
))}
469547
</div>

0 commit comments

Comments
 (0)