Files
dyolink/frontend/src/app/[locale]/(dashboard)/today/page.tsx

123 lines
4.4 KiB
TypeScript
Raw Normal View History

'use client';
import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/navigation';
import { useAuth } from '@/lib/hooks/useAuth';
import {
canAccessAppointmentsSection,
canViewTasks,
canViewTreatment,
} from '@/components/shared/permissions';
import { formatApiErrorMessage } from '@/components/shared/formatApiError';
import { TodayKpiGrid } from '@/components/today/TodayKpiGrid';
import { TodayChartsSection } from '@/components/today/TodayChartsSection';
import { TodayUpcomingAppointments } from '@/components/today/TodayUpcomingAppointments';
import { TodayLoadErrorBanner } from '@/components/today/TodayLoadErrorBanner';
import { TodaySectionErrorFallback } from '@/components/today/TodaySectionErrorFallback';
import { TodayWidgetErrorBoundary } from '@/components/today/TodayWidgetErrorBoundary';
import { useTodaySummary } from '@/lib/hooks/useTodaySummary';
export default function TodayPage() {
const t = useTranslations('today');
const { currentOrganization } = useAuth();
const orgId = currentOrganization?.id;
const { data, loading, isInitialLoad, error, reload } = useTodaySummary(orgId);
const showNoSubscriptionNotice =
Boolean(currentOrganization?.isOwner) && !currentOrganization?.plan;
const showUpcoming = canAccessAppointmentsSection(currentOrganization);
const showCharts =
(currentOrganization?.type === 'CLINIC' && canViewTreatment(currentOrganization)) ||
(currentOrganization?.type === 'LAB' && canViewTasks(currentOrganization));
const actionLayoutClass = useMemo(() => {
if (showUpcoming && showCharts) {
return 'grid grid-cols-1 xl:grid-cols-2 gap-4 items-start';
}
return 'grid grid-cols-1 gap-4';
}, [showUpcoming, showCharts]);
const sectionErrorMessage = t('sectionLoadError');
return (
<div className="space-y-6">
<div className="flex flex-col gap-1 sm:flex-row sm:items-end sm:justify-between">
<h1 className="text-2xl font-semibold">{t('welcomeBack')}</h1>
{data?.generatedAt && !isInitialLoad ? (
<p className="text-xs text-text-muted">
{t('lastUpdated', {
time: new Intl.DateTimeFormat(undefined, {
hour: 'numeric',
minute: '2-digit',
}).format(new Date(data.generatedAt)),
})}
</p>
) : null}
</div>
{showNoSubscriptionNotice && (
<div className="rounded-[var(--radius-md)] border border-amber-500/30 bg-amber-500/10 p-4">
<p className="text-sm text-amber-200">
{t('noSubscriptionNotice')}{' '}
<Link href="/settings/subscriptions" className="font-medium underline underline-offset-2">
{t('choosePlanLink')}
</Link>{' '}
{t('noSubscriptionCta')}
</p>
</div>
)}
{error ? (
<TodayLoadErrorBanner
message={formatApiErrorMessage(error, t('loadError'))}
retryLabel={t('retryLoad')}
onRetry={() => void reload()}
isRetrying={loading && Boolean(data)}
/>
) : null}
<TodayWidgetErrorBoundary
fallback={<TodaySectionErrorFallback message={sectionErrorMessage} />}
>
<TodayKpiGrid
widgets={data?.widgets ?? {}}
loading={loading}
isInitialLoad={isInitialLoad}
hasError={Boolean(error)}
/>
</TodayWidgetErrorBoundary>
{(showUpcoming || showCharts) && (!error || data) ? (
<div className={actionLayoutClass}>
{showUpcoming ? (
<TodayWidgetErrorBoundary
fallback={<TodaySectionErrorFallback message={sectionErrorMessage} />}
>
<TodayUpcomingAppointments
actions={data?.actions ?? {}}
loading={loading}
isInitialLoad={isInitialLoad}
/>
</TodayWidgetErrorBoundary>
) : null}
{showCharts ? (
<TodayWidgetErrorBoundary
fallback={<TodaySectionErrorFallback message={sectionErrorMessage} />}
>
<TodayChartsSection
charts={data?.charts ?? {}}
loading={loading}
isInitialLoad={isInitialLoad}
className={showUpcoming ? '' : 'max-w-none'}
/>
</TodayWidgetErrorBoundary>
) : null}
</div>
) : null}
</div>
);
}