import { useState, type FormEvent } from 'react' import { Link, useParams } from 'react-router-dom' import { toast } from 'sonner' import { Check, ClipboardCopy, Flag, HelpCircle, Plus, X } from 'lucide-react' import { DetailPageLayout } from '@/layouts/DetailPageLayout' import { ActivityTimeline, DetailSection, type TimelineEvent } from '@/components/detail/ActivityTimeline' import { ApprovalStepper, type ApprovalStep } from '@/components/detail/ApprovalStepper' import { KeyValueGrid } from '@/components/detail/KeyValueGrid' import { RowActions, type RowAction } from '@/components/table/RowActions' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { SelectNative } from '@/components/ui/select-native' import { StatusBadge, statusLabel } from '@/components/ui/status-badge' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { Textarea } from '@/components/ui/textarea' import { branches, doctors, prescriptions } from '@/mocks/data' import { useAuthStore } from '@/zustand/authStore' import { formatDoseTimes, formatFrequencyLabel } from '@/lib/dosingSchedule' import { PRESCRIPTION_FLAG_CODES, defaultSeverityForFlag, flagCodeLabel, openFlagCount, severityBadgeVariant, sortFlags, } from '@/lib/prescriptionFlags' import { formatDateTime } from '@/lib/utils' import type { Prescription, PrescriptionFlag, PrescriptionFlagCode, PrescriptionFlagSeverity, } from '@/types' const OPEN_STATUSES: Prescription['status'][] = ['submitted', 'under_review', 'queried'] function buildSteps(rx: Prescription): ApprovalStep[] { const decided = rx.status === 'approved' || rx.status === 'rejected' const reviewSeen = decided || rx.status === 'queried' return [ { key: 'submitted', label: 'Submitted', state: 'done', actor: rx.customerName, at: rx.submittedAt, }, { key: 'review', label: 'Pharmacist review', state: reviewSeen ? 'done' : 'current', actor: reviewSeen ? rx.reviewedBy : rx.reviewStartedAt ? 'Review in progress' : undefined, at: rx.reviewStartedAt, hint: rx.status === 'submitted' ? 'Waiting to be picked up from the queue' : undefined, }, { key: 'decision', label: rx.status === 'approved' ? 'Approved for dispensing' : rx.status === 'rejected' ? 'Rejected' : rx.status === 'queried' ? 'Queried with customer' : 'Decision', state: rx.status === 'approved' ? 'done' : rx.status === 'rejected' ? 'failed' : rx.status === 'queried' ? 'current' : 'upcoming', actor: rx.status === 'queried' || rx.status === 'approved' || rx.status === 'rejected' ? rx.reviewedBy : undefined, at: rx.status === 'submitted' || rx.status === 'under_review' ? undefined : rx.reviewedAt, hint: rx.status === 'queried' ? 'Awaiting a clearer scan from the customer' : undefined, }, ] } export function PrescriptionDetailPage() { const { id } = useParams() const user = useAuthStore((s) => s.user) const seed = prescriptions.find((p) => p.id === id) const [rx, setRx] = useState( seed ? { ...seed, items: [...seed.items], flags: seed.flags ? [...seed.flags] : [] } : undefined, ) const [addOpen, setAddOpen] = useState(false) const [resolveTarget, setResolveTarget] = useState(null) const [addForm, setAddForm] = useState({ code: 'needs_clarification' as PrescriptionFlagCode, severity: 'warning' as PrescriptionFlagSeverity, note: '', }) const [resolutionNote, setResolutionNote] = useState('') if (!rx) { return ( ) } const branch = branches.find((b) => b.id === rx.branchId) const doctor = rx.doctorId ? doctors.find((d) => d.id === rx.doctorId) : undefined const controlled = rx.items.some((i) => i.controlled) const isOpen = OPEN_STATUSES.includes(rx.status) const flags = sortFlags(rx.flags ?? []) const openCount = openFlagCount(rx) const steps = buildSteps(rx) const persistFlags = (nextFlags: PrescriptionFlag[]) => { setRx((prev) => (prev ? { ...prev, flags: nextFlags } : prev)) const target = prescriptions.find((p) => p.id === rx.id) if (target) target.flags = nextFlags } const act = (next: Prescription['status']) => { const patch = { status: next, reviewedBy: user?.name ?? 'Pharmacist', reviewedAt: new Date().toISOString(), } setRx((prev) => (prev ? { ...prev, ...patch } : prev)) const target = prescriptions.find((p) => p.id === rx.id) if (target) Object.assign(target, patch) toast.success(`Prescription ${next}`) } const copyId = () => { void navigator.clipboard?.writeText(rx.id) toast.message(`${rx.id} copied to clipboard`) } const openAddDialog = () => { setAddForm({ code: 'needs_clarification', severity: defaultSeverityForFlag('needs_clarification'), note: '', }) setAddOpen(true) } const onAddFlag = (e: FormEvent) => { e.preventDefault() const note = addForm.note.trim() if (!note) { toast.error('Add a short note for the flag') return } const flag: PrescriptionFlag = { id: `rxf-${Date.now().toString(36)}`, code: addForm.code, severity: addForm.severity, note, createdBy: user?.name ?? 'Pharmacist', createdAt: new Date().toISOString(), status: 'open', } persistFlags([flag, ...(rx.flags ?? [])]) setAddOpen(false) toast.success('Flag added') } const onResolveFlag = (e: FormEvent) => { e.preventDefault() if (!resolveTarget) return const note = resolutionNote.trim() if (!note) { toast.error('Add a resolution note') return } const nextFlags = (rx.flags ?? []).map((f) => f.id === resolveTarget.id ? { ...f, status: 'resolved' as const, resolvedBy: user?.name ?? 'Pharmacist', resolvedAt: new Date().toISOString(), resolutionNote: note, } : f, ) persistFlags(nextFlags) setResolveTarget(null) setResolutionNote('') toast.success('Flag resolved') } const overflow: RowAction[] = [ ...(isOpen ? [ { label: 'Reject prescription', icon: , variant: 'destructive' as const, onClick: () => act('rejected'), }, ] : []), { label: 'Copy Rx ID', icon: , onClick: copyId }, ] const decisionCluster = (
{isOpen ? ( <> ) : ( )}
) const trailEvents: TimelineEvent[] = [] trailEvents.push({ id: '1', at: rx.submittedAt, title: 'Submitted by customer', detail: branch?.name, tone: 'default', }) if (rx.reviewStartedAt) { trailEvents.push({ id: '2', at: rx.reviewStartedAt, title: 'Picked up for pharmacist review', detail: branch?.name, tone: 'warning', }) } else { trailEvents.push({ id: '2', at: rx.submittedAt, title: 'Queued for pharmacist review', tone: 'warning', }) } if (rx.status === 'approved') { trailEvents.push({ id: '3', at: rx.reviewedAt ?? rx.submittedAt, title: 'Approved for dispensing', detail: rx.reviewedBy, tone: 'success', }) } else if (rx.status === 'queried') { trailEvents.push({ id: '3', at: rx.reviewedAt ?? rx.submittedAt, title: 'Query sent to customer', detail: rx.reviewedBy, tone: 'warning', }) } else if (rx.status === 'rejected') { trailEvents.push({ id: '3', at: rx.reviewedAt ?? rx.submittedAt, title: 'Rejected', detail: rx.reviewedBy, tone: 'danger', }) } for (const f of flags) { trailEvents.push({ id: `flag-${f.id}`, at: f.status === 'resolved' && f.resolvedAt ? f.resolvedAt : f.createdAt, title: f.status === 'resolved' ? `Resolved flag · ${flagCodeLabel(f.code)}` : `Flagged · ${flagCodeLabel(f.code)}`, detail: f.status === 'resolved' ? `${f.resolvedBy ?? 'Staff'}: ${f.resolutionNote ?? f.note}` : `${f.createdBy}: ${f.note}`, tone: f.status === 'resolved' ? 'success' : f.severity === 'critical' ? 'danger' : 'warning', }) } trailEvents.sort((a, b) => new Date(a.at).getTime() - new Date(b.at).getTime()) return ( <> {controlled ? Controlled : null} {openCount > 0 ? ( {openCount} open flag{openCount === 1 ? '' : 's'} ) : null} } actions={ <> {isOpen ? ( <> ) : null} } sidebar={ } >

{rx.status === 'approved' ? 'Approved and ready to dispense' : rx.status === 'rejected' ? 'Rejected — nothing will be dispensed' : rx.status === 'queried' ? 'Waiting on the customer' : rx.status === 'under_review' ? 'Open with a pharmacist' : 'Waiting for a pharmacist'}

{rx.reviewedBy && rx.reviewedAt ? `${statusLabel('prescription', rx.status)} by ${rx.reviewedBy} · ${formatDateTime(rx.reviewedAt)}` : `Submitted ${formatDateTime(rx.submittedAt)} · no decision recorded yet`}

{rx.decisionNote ? (

{rx.decisionNote}

) : null}
Add flag } > {flags.length === 0 ? (

No flags on this prescription yet.

) : (
    {flags.map((flag) => (
  • {flag.severity} {flagCodeLabel(flag.code)} {flag.status === 'open' ? 'Open' : 'Resolved'}

    {flag.note}

    Added by {flag.createdBy} · {formatDateTime(flag.createdAt)}

    {flag.status === 'resolved' ? (

    Resolved by {flag.resolvedBy} {flag.resolvedAt ? ` · ${formatDateTime(flag.resolvedAt)}` : ''} {flag.resolutionNote ? ` — ${flag.resolutionNote}` : ''}

    ) : null}
    {flag.status === 'open' ? ( ) : null}
  • ))}
)}
{doctor.name} ) : ( rx.prescriber ?? '—' ), }, { label: 'Hospital', value: doctor?.orgName ?? '—' }, { label: 'License', value: doctor?.licenseNumber ?? '—' }, { label: 'Submitted', value: formatDateTime(rx.submittedAt) }, { label: 'Items', value: String(rx.items.length) }, { label: 'Controlled items', value: controlled ? 'Yes — stamp required' : 'None' }, { label: 'Open flags', value: String(openCount) }, { label: 'Reviewed by', value: rx.reviewedBy ?? 'Not reviewed yet' }, ]} /> Medication Qty Interval Dose times Flags {rx.items.map((item) => ( {item.name} {item.qty} {formatFrequencyLabel(item.frequency, item.intervalHours)} {item.frequency ? ( {item.frequency} {item.frequency === 'QXH' && item.intervalHours ? ` · ${item.intervalHours}h` : ''} ) : null} {formatDoseTimes(item.times)} {item.controlled ? ( Controlled ) : ( OTC/Rx )} ))}

{isOpen ? controlled ? 'Approving stamps a dispensing record and logs the controlled item against your licence.' : 'Approving stamps a dispensing record against this branch’s stock.' : `Decision recorded by ${rx.reviewedBy ?? 'a pharmacist'}.`}

{decisionCluster}
Add prescription flag Mark a review, risk, or operational issue. Open flags show on the queue until someone resolves them.
{ const code = e.target.value as PrescriptionFlagCode setAddForm((prev) => ({ ...prev, code, severity: defaultSeverityForFlag(code), })) }} > {PRESCRIPTION_FLAG_CODES.map((opt) => ( ))}
setAddForm((prev) => ({ ...prev, severity: e.target.value as PrescriptionFlagSeverity, })) } >