'use client'; import { FunctionComponent } from 'react'; import { useLocale, useTranslations } from 'next-intl'; import { Paper, Stack, Typography } from '@mui/material'; import { AppButton } from '@/components/common'; import StatusChip from '@/components/StatusChip'; import { formatIrrToToman, formatShamsiDate } from '@/utils'; import type { BookingSessionStatus, VisitVerificationStatus } from '@/services/bookings/types'; import EvvStatusBanner from '../EvvStatusBanner'; import { formatClock, formatElapsed, formatTimeRange } from '../format'; import { SESSION_STATUS_KIND } from '../statusKind'; export interface SessionCardProps { /** Optional heading above the visit row — the patient name on the nurse's day feed (omitted in the detail). */ title?: string; sessionIndex: number; /** ISO date `YYYY-MM-DD`. */ scheduledDate: string; /** `HH:mm:ss`. */ scheduledTimeStart: string; scheduledTimeEnd: string; status: BookingSessionStatus; evvStatus?: VisitVerificationStatus; /** Server `checkInAt`/`checkOutAt` — the banner + elapsed render from these, never a client clock. */ checkInAt?: string | null; checkOutAt?: string | null; /** Advisory match: `true` in range · `false` out-of-range (under review) · `null` GPS unavailable. */ checkInAddressMatch?: boolean | null; /** IRR digit-string — this session's payout share; shown in the nurse view when `showPayout`. */ visitPayoutAmount?: string | null; showPayout?: boolean; /** Render the nurse EVV check-in/out CTA (state-machine driven). */ showEvvControls?: boolean; /** This session's EVV mutation is in flight. */ evvPending?: boolean; /** Acquiring GPS for this session (pre-mutation). */ acquiringLocation?: boolean; onCheckIn?: () => void; onCheckOut?: () => void; } /** * One visit row: the Shamsi schedule, a per-session status chip, the EVV banner once checked in, and — * for the assigned nurse — the check-in/out CTA driven by the session + EVV state machine (`scheduled` → * «ثبت ورود» · `in_progress`/checked-in → «ثبت خروج» · `completed` → elapsed duration · `missed` → no * action). A single-visit booking renders exactly this same card. Presentational: the container supplies * the EVV handlers + busy flags; a GPS mismatch never disables the flow (advisory, not a block). * @component SessionCard */ const SessionCard: FunctionComponent = ({ title, sessionIndex, scheduledDate, scheduledTimeStart, scheduledTimeEnd, status, evvStatus, checkInAt, checkOutAt, checkInAddressMatch = null, visitPayoutAmount, showPayout = false, showEvvControls = false, evvPending = false, acquiringLocation = false, onCheckIn, onCheckOut, }) => { const t = useTranslations('booking'); const tc = useTranslations('common'); const locale = useLocale(); const dateLabel = formatShamsiDate(scheduledDate, locale); const timeLabel = formatTimeRange(scheduledTimeStart, scheduledTimeEnd, locale); const showBanner = (evvStatus === 'checked_in' || evvStatus === 'completed') && Boolean(checkInAt); const elapsed = formatElapsed(checkInAt ?? null, checkOutAt ?? null, locale); const busy = acquiringLocation || evvPending; return ( {title ? ( {title} ) : null} {t('session_index', { n: sessionIndex })} {dateLabel} · {timeLabel} {showBanner ? : null} {checkOutAt ? ( {t('evv_checked_out_at', { time: formatClock(checkOutAt, locale) })} {elapsed ? ` · ${t('session_elapsed', { duration: elapsed })}` : ''} ) : null} {showPayout && visitPayoutAmount ? ( {t('session_payout')}: {formatIrrToToman(visitPayoutAmount, locale)} {tc('currency_toman')} ) : null} {showEvvControls && status === 'scheduled' ? ( {acquiringLocation ? t('evv_acquiring_location') : evvPending ? t('evv_checking_in') : t('evv_check_in')} ) : null} {showEvvControls && status === 'in_progress' && evvStatus === 'checked_in' ? ( {acquiringLocation ? t('evv_acquiring_location') : evvPending ? t('evv_checking_out') : t('evv_check_out')} ) : null} ); }; export default SessionCard;