'use client';
import { useLocale, useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
import { Avatar, Box, Skeleton, Stack, Typography } from '@mui/material';
import {
AppButton,
AppIcon,
AppLink,
CountdownTimer,
EmptyState,
ErrorState,
Money,
SurfaceCard,
TrustBadge,
} from '@/components';
import { ROUTES } from '@/constants';
import { formatRelativeTime, formatShamsiDate, localeTag, parseIrr } from '@/utils';
import { useMe } from '@/services/auth';
import { useNurseRequestInbox } from '@/services/bookingRequests';
import { useTodaySessions } from '@/services/bookings';
import { useNurseEarningsBalance } from '@/services/payouts';
import { useUnreadCount } from '@/services/notifications';
import { useVerificationStatus } from '@/services/verification';
import { ownBadgeState } from '@/services/verification/types';
import { coarseResponseLabel } from '@/services/bookingRequests/format';
import DashboardActivationSlot from './DashboardActivationSlot';
const DASHBOARD_MAX_WIDTH = 960;
/** The pill's urgency tiers (ui-phase-7 §3.4): teal >2h · amber <2h · terracotta <30min. */
const URGENT_THRESHOLD_SECONDS = 30 * 60;
const WARN_THRESHOLD_SECONDS = 2 * 60 * 60;
/**
* The nurse "امروز" dashboard (ui-phase-7 §3.1) — the operational home replacing the `PlaceholderScreen`.
* Pure assembly: every widget reads an already-cached query. Order matters — the pending-requests strip
* is the most time-critical thing a nurse can miss, so it sits above the earnings snapshot.
*/
export default function NurseDashboardScreen() {
const t = useTranslations('dashboard');
const { data: me, isLoading: meLoading } = useMe();
const verification = useVerificationStatus();
const displayName = me ? [me.firstName, me.lastName].filter(Boolean).join(' ').trim() || me.phone : '';
return (
{meLoading ? (
<>
>
) : (
<>
{(displayName || '؟').charAt(0)}
{t('greeting', { name: displayName })}
{!verification.isLoading ? : null}
>
)}
);
}
/** First actionable session from `useTodaySessions` + a display-only "time until" line. */
function NextVisitCard() {
const t = useTranslations('dashboard');
const locale = useLocale();
const router = useRouter();
const { data, isLoading, isError, refetch } = useTodaySessions();
if (isLoading) return ;
if (isError) {
return refetch()} />;
}
const items = data?.items ?? [];
const next = items.find((item) => item.status === 'scheduled' || item.status === 'in_progress');
if (!next) {
return ;
}
const timeFmt = new Intl.DateTimeFormat(localeTag(locale), { hour: '2-digit', minute: '2-digit' });
const timeRangeLabel = `${timeFmt.format(new Date(`${next.scheduledDate}T${next.scheduledTimeStart}`))} – ${timeFmt.format(new Date(`${next.scheduledDate}T${next.scheduledTimeEnd}`))}`;
const timeUntil = formatRelativeTime(`${next.scheduledDate}T${next.scheduledTimeStart}`, locale, formatShamsiDate);
return (
{t('next_visit_title')}
{next.patientName}
{timeRangeLabel}
{timeUntil ? ` · ${t('next_visit_starts_in', { relative: timeUntil })}` : ''}
router.push(`/${locale}${ROUTES.NURSE_VISITS}`)}
sx={{ alignSelf: 'flex-start' }}
>
{t('next_visit_cta')}
);
}
/** The most time-critical widget: pending-request count + the most urgent countdown, inline into detail. */
function RequestsStrip() {
const t = useTranslations('dashboard');
const tb = useTranslations('booking');
const locale = useLocale();
const router = useRouter();
const { data, isLoading, isError, refetch } = useNurseRequestInbox();
if (isLoading) return ;
if (isError) {
return refetch()} />;
}
const items = data?.items ?? [];
const total = data?.total ?? 0;
if (items.length === 0) {
return ;
}
const mostUrgent = items[0];
return (
{t('requests_strip_title', { count: total })}
router.push(`/${locale}${ROUTES.NURSE_REQUESTS}`)}
>
{t('requests_strip_cta')}
{mostUrgent.counterpartyName}
{formatShamsiDate(mostUrgent.requestedDate, locale)}
coarseResponseLabel(minutes, tb)}
size="sm"
/>
router.push(`/${locale}${ROUTES.NURSE_REQUESTS}/${mostUrgent.id}`)}
sx={{ alignSelf: 'flex-start' }}
>
{t('requests_strip_open')}
);
}
/** A compact two-stat row (net payable + eligible) — never clamps a negative net balance. */
function EarningsSnapshotCard() {
const t = useTranslations('dashboard');
const tp = useTranslations('payouts');
const locale = useLocale();
const router = useRouter();
const { data, isLoading, isError, refetch } = useNurseEarningsBalance();
if (isLoading) return ;
if (isError) {
return refetch()} />;
}
if (!data) return null;
const net = parseIrr(data.netPayableBalanceIrr);
const isOwed = net < BigInt(0);
const magnitude = isOwed ? -net : net;
return (
{t('earnings_snapshot_title')}
router.push(`/${locale}${ROUTES.NURSE_EARNINGS}`)}
>
{t('earnings_snapshot_cta')}
{isOwed ? tp('balance_owed_label') : tp('balance_net_label')}
{tp('bucket_eligible')}
);
}
/** The unread-count entry row — the bell in the shell chrome is Phase 2's; this is a dashboard shortcut. */
function NotificationsEntryRow() {
const t = useTranslations('dashboard');
const locale = useLocale();
const unread = useUnreadCount();
return (
{t('notifications_entry_title')}
0 ? 'var(--bal-secondary-dark)' : 'text.secondary', fontWeight: unread > 0 ? 700 : 400 }}
>
{unread > 0 ? t('notifications_entry_unread', { count: unread }) : t('notifications_entry_empty')}
);
}