'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')} ); }