"use client"; import { Check, Loader2, Send, X } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Dialog, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; import { Textarea } from "@/components/ui/textarea"; import { ApiError } from "@/lib/api-client"; import type { Patient } from "@/lib/patients"; import { cn } from "@/lib/utils"; import { getWalletUpdate, pushWalletUpdate, type WalletUpdate, } from "@/lib/wallet-updates"; // The record sections a clinician can flag as changed. The labels double as the // human-readable change summary the patient sees when approving. const SECTION_KEYS = [ "demographics", "problems", "medications", "allergies", "labs", "vitals", "visits", ] as const; type Phase = "compose" | "sent"; // Push the current record to a wallet-linked patient's app. The patient must // approve it on their phone before their on-device record is replaced. export function WalletPushDialog({ patient, open, onOpenChange, }: { patient: Patient; open: boolean; onOpenChange: (open: boolean) => void; }) { const { t } = useTranslation(); const [phase, setPhase] = useState("compose"); const [selected, setSelected] = useState>(new Set()); const [note, setNote] = useState(""); const [update, setUpdate] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const reset = () => { setPhase("compose"); setSelected(new Set()); setNote(""); setUpdate(null); setBusy(false); setError(null); }; const handleOpenChange = (next: boolean) => { if (!next) reset(); onOpenChange(next); }; // Poll the update's status until the patient approves/denies (or the dialog // closes). Live pushes usually resolve within seconds. useEffect(() => { if (phase !== "sent" || !update || update.resolvedAt) return; let active = true; const timer = setInterval(async () => { try { const fresh = await getWalletUpdate(update.id); if (!active) return; setUpdate(fresh); if (fresh.resolvedAt) clearInterval(timer); } catch { /* keep polling */ } }, 3000); return () => { active = false; clearInterval(timer); }; }, [phase, update]); const toggle = (key: string) => { setSelected((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); }; const push = async () => { setBusy(true); setError(null); try { const changes = [ ...[...selected].map((k) => t(`walletPush.sections.${k}`)), ...(note.trim() ? [note.trim()] : []), ]; const created = await pushWalletUpdate({ fileNumber: patient.fileNumber, changes, }); setUpdate(created); setPhase("sent"); } catch (err) { setError( err instanceof ApiError ? err.message : t("walletPush.errors.generic"), ); } finally { setBusy(false); } }; const canPush = selected.size > 0 || note.trim().length > 0; const status = update?.status ?? "pending"; return ( {t("walletPush.title")} {t("walletPush.subtitle", { name: patient.name })} {phase === "compose" ? (
{SECTION_KEYS.map((key) => { const on = selected.has(key); return ( ); })}