Skip to content

Commit

Permalink
Adding historical alerts (#682)
Browse files Browse the repository at this point in the history
* Adding historical alerts

* Clean up styling

* Fixing lint

* V4/historical alerts 2 (#739)

* Alerts Modal

* Lint

* new add label and title

---------

Co-authored-by: Patrick Cleary <[email protected]>
Co-authored-by: Patrick Cleary <[email protected]>
  • Loading branch information
3 people authored Jul 12, 2023
1 parent a80fbe6 commit 1b9c4aa
Show file tree
Hide file tree
Showing 22 changed files with 365 additions and 152 deletions.
24 changes: 23 additions & 1 deletion common/api/alerts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AlertsResponse } from '../types/alerts';
import type { AlertsResponse, OldAlert } from '../types/alerts';
import type { LineShort } from '../types/lines';
import { APP_DATA_BASE_PATH } from '../utils/constants';

Expand Down Expand Up @@ -50,3 +50,25 @@ const fetchAlertsForBus = async (busRoute: string): Promise<AlertsResponse[]> =>
}
return await response.json();
};

export const fetchHistoricalAlerts = async (
date: string | undefined,
route: LineShort,
busRoute?: string
): Promise<OldAlert[]> => {
const url = new URL(`${APP_DATA_BASE_PATH}/api/alerts/${date}`, window.location.origin);
const options = { route: '' };
if (route === 'Bus' && busRoute) {
options['route'] = busRoute;
} else {
options['route'] = route;
}
Object.entries(options).forEach(([key, value]) => {
url.searchParams.append(key, value.toString());
});
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error('Failed to fetch alerts');
}
return await response.json();
};
26 changes: 23 additions & 3 deletions common/api/hooks/alerts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { fetchAlerts } from '../alerts';
import { fetchAlerts, fetchHistoricalAlerts } from '../alerts';
import type { LineShort } from '../../types/lines';
import { ONE_MINUTE } from '../../constants/time';
import { FIVE_MINUTES, ONE_MINUTE } from '../../constants/time';
import type { AlertsResponse } from '../../types/alerts';

export const useAlertsData = (route: LineShort, busRoute?: string) => {
export const useHistoricalAlertsData = (
date: string | undefined,
route: LineShort,
busRoute?: string
) => {
return useQuery(
['alerts', date, route, busRoute],
() => fetchHistoricalAlerts(date, route, busRoute),
{
staleTime: FIVE_MINUTES,
enabled: date !== undefined,
}
);
};

export const useAlertsData = (
route: LineShort,
busRoute?: string
): UseQueryResult<AlertsResponse[]> => {
return useQuery(['alerts', route, busRoute], () => fetchAlerts(route, busRoute), {
staleTime: ONE_MINUTE,
});
Expand Down
113 changes: 0 additions & 113 deletions common/components/alerts/AlertBar.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion common/components/alerts/AlertFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const anti = [
* Given an alert object, findMatch returns a boolean with whether or not the alert is a known format
* like "Reduced speeds" or "Up to 15 minutes"
*/
export const findMatch = (alert: OldAlert) => {
export const findMatch = (alert: OldAlert): boolean => {
const { text } = alert;

if (anti.some((exp) => text.match(exp))) {
Expand Down
20 changes: 20 additions & 0 deletions common/components/alerts/AlertNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { PastAlertModal } from './PastAlertModal';

export const AlertNotice: React.FC = () => {
const [alertsOpen, setAlertsOpen] = useState(false);
return (
<>
<div
className="pb-safe fixed bottom-24 right-2 z-10 cursor-pointer md:right-4 lg:bottom-4"
onClick={() => setAlertsOpen(!alertsOpen)}
title="Alerts"
>
<p className="text-4xl md:text-5xl " style={{ fontFamily: 'Helvetica Neue' }}>
⚠️
</p>
</div>
<PastAlertModal alertsOpen={alertsOpen} setAlertsOpen={setAlertsOpen} />
</>
);
};
88 changes: 88 additions & 0 deletions common/components/alerts/PastAlertModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { Fragment } from 'react';
import type { SetStateAction } from 'react';
import { Dialog, Transition } from '@headlessui/react';
import classNames from 'classnames';
import { useAlertStore } from '../../../modules/tripexplorer/AlertStore';
import { getDateString } from '../../../modules/commute/alerts/AlertUtils';

interface PastAlertModalProps {
alertsOpen: boolean;
setAlertsOpen: React.Dispatch<SetStateAction<boolean>>;
}

export const PastAlertModal: React.FC<PastAlertModalProps> = ({ alertsOpen, setAlertsOpen }) => {
const alertStore = useAlertStore();
return (
<Transition.Root show={alertsOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={() => setAlertsOpen(false)}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-md sm:p-6">
<div className="flex flex-col items-center gap-2">
<div className="flex flex-col items-center text-lg">
<p
style={{ fontFamily: 'Helvetica Neue' }}
className="text-5xl text-yellow-300 "
>
⚠️
</p>
<p>Alerts</p>
</div>
<div className="flex max-h-[50vh] flex-col gap-4 overflow-y-auto md:max-h-[66vh]">
{alertStore.alerts?.map((alert, index) => (
<div
key={index}
className={classNames(
alert.applied ? 'bg-yellow-200' : 'bg-yellow-100 ',
'flex cursor-pointer flex-col rounded-md border border-yellow-200 bg-yellow-100 p-2 shadow-sm hover:bg-yellow-200 '
)}
onClick={() => {
alertStore.changeAlertApplied(alertStore.alerts, index);
}}
>
<p className="font-bold">
{getDateString(alert.valid_from, alert.valid_to)}
</p>
<p className="text-sm">{alert.text}</p>
<p className="w-full pt-2 text-center text-sm italic">
{alert.applied ? 'Remove' : 'Add to chart'}
</p>
</div>
))}
</div>
<button
className="hidden w-full rounded-md border border-stone-200 py-1 hover:bg-stone-200 lg:block"
onClick={() => setAlertsOpen(false)}
>
Close
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
2 changes: 1 addition & 1 deletion common/components/charts/AggregateLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export const AggregateLineChart: React.FC<AggregateLineProps> = ({
]}
/>
</ChartDiv>
<div className="flex flex-row items-end gap-4 pl-6 pr-2">
<div className="flex flex-row items-end gap-4 ">
{showLegend && <LegendLongTerm />}
{!isHomescreen && startDate && (
<DownloadButton
Expand Down
49 changes: 47 additions & 2 deletions common/components/charts/Legend.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,57 @@
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Disclosure } from '@headlessui/react';
import React from 'react';
import { useBreakpoint } from '../../hooks/useBreakpoint';

export const Legend: React.FC = () => {
export const LegendSingleDay: React.FC = () => {
const isMobile = !useBreakpoint('md');
if (isMobile) return <LegendMobile />;
return <LegendDesktop />;
};

const LegendMobile: React.FC = () => {
return (
<Disclosure>
{({ open }) => (
<div className="flex w-full flex-col rounded-md border border-stone-100 shadow-sm">
<Disclosure.Button className="">
<div className="flex flex-row items-center justify-between px-4 py-1">
<p className="text-xs italic text-stone-700">Legend</p>
<FontAwesomeIcon
icon={open ? faChevronUp : faChevronDown}
className="text-stone-700"
/>
</div>
</Disclosure.Button>
<Disclosure.Panel
className={
'grid w-full grid-cols-2 items-baseline p-1 text-left text-xs lg:flex lg:flex-row lg:gap-4'
}
>
<LegendSingle />
</Disclosure.Panel>
</div>
)}
</Disclosure>
);
};

const LegendDesktop: React.FC = () => {
return (
<div
className={
'grid w-full grid-cols-2 items-baseline p-1 text-left text-xs lg:flex lg:flex-row lg:gap-4'
}
>
<LegendSingle />
</div>
);
};

const LegendSingle: React.FC = () => {
return (
<>
<div className="col-span-2 flex flex-row items-baseline gap-2 pb-1 lg:pb-0">
<p>
Compare to{' '}
Expand Down Expand Up @@ -34,7 +79,7 @@ export const Legend: React.FC = () => {
></span>{' '}
{'100%+ above'}
</p>
</div>
</>
);
};

Expand Down
Loading

0 comments on commit 1b9c4aa

Please sign in to comment.