Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
96 lines
4.3 KiB
TypeScript
96 lines
4.3 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { Center, Icon, Spinner } from '@barev/ui';
|
|
import { I18nProvider, match, navigate, pickLocale, telegramLanguage, useI18n, useLocation, useTelegramBack, useTRPC } from '@barev/web';
|
|
import { useEffect, type ReactNode } from 'react';
|
|
import { Company } from './screens/Company.tsx';
|
|
import { History } from './screens/History.tsx';
|
|
import { Job } from './screens/Job.tsx';
|
|
import { Partner } from './screens/Partner.tsx';
|
|
import { Today } from './screens/Today.tsx';
|
|
|
|
const ROOTS = ['/', '/history', '/company', '/partner'];
|
|
|
|
export function App({ hasInitData }: { hasInitData: boolean }) {
|
|
const trpc = useTRPC();
|
|
const me = useQuery({ ...trpc.pro.me.queryOptions(), enabled: hasInitData });
|
|
const locale = pickLocale(me.data?.locale, telegramLanguage(), navigator.language);
|
|
const status = me.error?.data?.httpStatus;
|
|
return (
|
|
<I18nProvider initial={locale}>
|
|
{!hasInitData || status === 401 ? (
|
|
<Message titleKey="pro.openInTelegram.title" textKey="pro.openInTelegram.text" />
|
|
) : status === 403 ? (
|
|
<Message titleKey="pro.notLinked.title" textKey="pro.notLinked.text" />
|
|
) : me.isPending ? (
|
|
<Center>
|
|
<Spinner />
|
|
</Center>
|
|
) : me.error ? (
|
|
<Message titleKey="errors.generic" textKey="common.retryLater" />
|
|
) : (
|
|
<Routes isProvider={!!me.data.provider} companies={me.data.companies} partners={me.data.partners} />
|
|
)}
|
|
</I18nProvider>
|
|
);
|
|
}
|
|
|
|
function Routes({ isProvider, companies, partners }: { isProvider: boolean; companies: { id: string; name: string }[]; partners: { id: string; name: string; role: 'OWNER' | 'STAFF' }[] }) {
|
|
const { path } = useLocation();
|
|
useTelegramBack(path, ROOTS);
|
|
// не исполнитель — сразу на экран компании или кабинет партнёра
|
|
useEffect(() => {
|
|
if (!isProvider && path === '/') navigate(companies.length ? '/company' : '/partner', { replace: true });
|
|
}, [isProvider, path, companies.length]);
|
|
|
|
let screen: ReactNode;
|
|
let m: Record<string, string> | null;
|
|
if (path === '/' && isProvider) screen = <Today />;
|
|
else if ((m = match('/orders/:id', path)) && isProvider) screen = <Job id={m.id!} />;
|
|
else if (path === '/history' && isProvider) screen = <History />;
|
|
else if (path === '/company' && companies.length) screen = <Company companies={companies} />;
|
|
else if (path === '/partner' && partners.length) screen = <Partner partners={partners} />;
|
|
else screen = isProvider ? <Today /> : companies.length ? <Company companies={companies} /> : <Partner partners={partners} />;
|
|
// вкладки: Сегодня и История у исполнителя, Компания у диспетчера, Партнёр у собственника и сотрудников партнёра
|
|
const tabs = (isProvider ? 2 : 0) + (companies.length ? 1 : 0) + (partners.length ? 1 : 0);
|
|
const showNav = ROOTS.includes(path) && tabs > 1;
|
|
return (
|
|
<>
|
|
{screen}
|
|
{showNav && <Nav path={path} isProvider={isProvider} hasCompany={companies.length > 0} hasPartner={partners.length > 0} />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Nav({ path, isProvider, hasCompany, hasPartner }: { path: string; isProvider: boolean; hasCompany: boolean; hasPartner: boolean }) {
|
|
const { t } = useI18n();
|
|
const item = (href: string, icon: Parameters<typeof Icon>[0]['name'], label: string) => (
|
|
<a href={`#${href}`} aria-current={path === href ? 'page' : undefined}>
|
|
<Icon name={icon} />
|
|
{label}
|
|
</a>
|
|
);
|
|
return (
|
|
<nav className="bc-bottomnav" aria-label={t('pro.nav.label')}>
|
|
{isProvider && item('/', 'clock', t('pro.nav.today'))}
|
|
{isProvider && item('/history', 'star', t('pro.nav.history'))}
|
|
{hasCompany && item('/company', 'grid', t('pro.nav.company'))}
|
|
{hasPartner && item('/partner', 'gift', t('pro.nav.partner'))}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
function Message({ titleKey, textKey }: { titleKey: string; textKey: string }) {
|
|
const { t } = useI18n();
|
|
return (
|
|
<div className="app app--plain">
|
|
<Center>
|
|
<div className="bc-stack" style={{ textAlign: 'center', alignItems: 'center' }}>
|
|
<Icon name="logo" size={48} style={{ color: 'var(--bc-plum)' }} />
|
|
<h1 className="bc-h2">{t(titleKey)}</h1>
|
|
<p className="bc-muted">{t(textKey)}</p>
|
|
</div>
|
|
</Center>
|
|
</div>
|
|
);
|
|
}
|