190 lines
6.6 KiB
TypeScript
190 lines
6.6 KiB
TypeScript
import { useState } from 'react';
|
|
import { useRouter } from 'next/router';
|
|
import clsx from 'clsx';
|
|
import { Card } from '@/components/card';
|
|
import { CursorEffect } from '@/components/cursor';
|
|
import Star from '@/assets/images/thick-round-star-6.svg';
|
|
import NoteIcon from '@/assets/icons/note.svg';
|
|
import type { Alerts, City } from '@/lib/ridebus';
|
|
import useLocale from '@/utils/useLocale';
|
|
import { SelectMenu } from '../departuresCard/selectMenu';
|
|
import { useResource } from '../departuresCard/useResource';
|
|
import { locales } from './locales';
|
|
import styles from './changesCard.module.scss';
|
|
|
|
export function ChangesCard() {
|
|
const t = useLocale(locales);
|
|
const { locale } = useRouter();
|
|
const [revision, setRevision] = useState(0);
|
|
const [chosenCity, setChosenCity] = useState('');
|
|
const [expanded, setExpanded] = useState(false);
|
|
const cities = useResource<City[]>(
|
|
'/api/departures?resource=cities',
|
|
revision
|
|
);
|
|
const cityId = String(
|
|
cities.data?.find((city) => String(city.id) === chosenCity)?.id ??
|
|
cities.data?.find((city) => city.slug === 'polotsk')?.id ??
|
|
cities.data?.[0]?.id ??
|
|
''
|
|
);
|
|
const notices = useResource<Alerts>(
|
|
cityId ? `/api/departures?resource=alerts&cityId=${cityId}` : null,
|
|
revision,
|
|
true
|
|
);
|
|
const failed = cities.status === 'error' || notices.status === 'error';
|
|
const loading = cities.status === 'loading' || notices.status === 'loading';
|
|
const alerts = notices.data?.alerts ?? [];
|
|
const dateFormat = new Intl.DateTimeFormat(
|
|
locale === 'ru' ? 'ru-BY' : 'en-GB',
|
|
{
|
|
day: 'numeric',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
timeZone: 'Europe/Minsk',
|
|
}
|
|
);
|
|
const updateTime = notices.data
|
|
? new Intl.DateTimeFormat(locale === 'ru' ? 'ru-BY' : 'en-GB', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
timeZone: 'Europe/Minsk',
|
|
}).format(new Date(notices.data.updatedAt))
|
|
: '';
|
|
|
|
return (
|
|
<section aria-label={t('title')}>
|
|
<Card
|
|
className={styles.card}
|
|
title={t('title')}
|
|
subtitle={t('description')}
|
|
classes={{ content: styles.content }}
|
|
>
|
|
<Star className={styles.star} aria-hidden="true" />
|
|
<div className={styles.layout}>
|
|
<div className={styles.controls}>
|
|
<SelectMenu
|
|
label={t('city')}
|
|
value={cityId}
|
|
options={
|
|
cities.data?.map((city) => ({
|
|
value: String(city.id),
|
|
label: city.name,
|
|
})) ?? []
|
|
}
|
|
placeholder={
|
|
cities.status === 'loading'
|
|
? t('loadingCities')
|
|
: t('selectCity')
|
|
}
|
|
searchLabel={t('searchCity')}
|
|
noResults={t('noResults')}
|
|
onChange={(value) => {
|
|
setChosenCity(value);
|
|
setExpanded(false);
|
|
}}
|
|
/>
|
|
<p className={styles.note}>{t('note')}</p>
|
|
{updateTime && !failed && !loading && (
|
|
<p className={styles.updated}>
|
|
{t('updated')} {updateTime}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div
|
|
className={styles.notices}
|
|
aria-live="polite"
|
|
aria-busy={loading}
|
|
>
|
|
{failed || loading || !cities.data?.length ? (
|
|
<div className={styles.state}>
|
|
{loading && (
|
|
<div className={styles.loadingDots} aria-hidden="true">
|
|
<i />
|
|
<i />
|
|
<i />
|
|
</div>
|
|
)}
|
|
<p>
|
|
{t(failed ? 'unavailable' : loading ? 'loading' : 'noCities')}
|
|
</p>
|
|
{failed && (
|
|
<CursorEffect cursorBorderRadius={16} cursorPadding={4}>
|
|
<button
|
|
type="button"
|
|
className={styles.action}
|
|
onClick={() => setRevision((r) => r + 1)}
|
|
>
|
|
{t('retry')}
|
|
</button>
|
|
</CursorEffect>
|
|
)}
|
|
</div>
|
|
) : alerts.length ? (
|
|
<>
|
|
{(expanded ? alerts : alerts.slice(0, 3)).map((alert) => (
|
|
<article
|
|
className={styles.notice}
|
|
key={`${cityId}:${alert.id}`}
|
|
>
|
|
<div className={styles.badges}>
|
|
<span className={clsx(styles.type, styles[alert.type])}>
|
|
{t(alert.type)}
|
|
</span>
|
|
{alert.routeNumber && (
|
|
<span className={styles.route}>
|
|
{t('route')} №{alert.routeNumber}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<h4>{alert.title}</h4>
|
|
<p className={styles.message}>{alert.message}</p>
|
|
<div className={styles.dates}>
|
|
<span>
|
|
{t('published')}{' '}
|
|
<time dateTime={alert.createdAt}>
|
|
{dateFormat.format(new Date(alert.createdAt))}
|
|
</time>
|
|
</span>
|
|
{alert.endTime && (
|
|
<span>
|
|
{t('until')}{' '}
|
|
<time dateTime={alert.endTime}>
|
|
{dateFormat.format(new Date(alert.endTime))}
|
|
</time>
|
|
</span>
|
|
)}
|
|
</div>
|
|
</article>
|
|
))}
|
|
{alerts.length > 3 && (
|
|
<CursorEffect cursorBorderRadius={16} cursorPadding={4}>
|
|
<button
|
|
type="button"
|
|
className={styles.action}
|
|
aria-expanded={expanded}
|
|
onClick={() => setExpanded((value) => !value)}
|
|
>
|
|
{t(expanded ? 'showLess' : 'showAll')}
|
|
{!expanded && ` (${alerts.length})`}
|
|
</button>
|
|
</CursorEffect>
|
|
)}
|
|
</>
|
|
) : (
|
|
<div className={styles.state} key={cityId}>
|
|
<span className={styles.emptyIcon} aria-hidden="true">
|
|
<NoteIcon />
|
|
</span>
|
|
<h4>{t('emptyTitle')}</h4>
|
|
<p>{t('emptyDescription')}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</section>
|
|
);
|
|
}
|