import type { ReactNode } from "react";
import type { CheckInPlanTemplate, PlanQuestion } from "../../api/types";
import { closingAvailable, moveQuestion, type ScriptLine } from "../../logic/questions";
import { windowLabel, type Window } from "../../logic/windows";
import { Button, checkClass, cn, fieldClass, Icon, type IconName, Tip } from "../kit";
/** A white card for one part of the plan: its title, a quiet note on the right, then the part. */
export function PlanCard({ title, icon, note, children }: { title: string; icon?: IconName; note?: ReactNode; children: ReactNode }) {
return (
<section aria-label={title} className="flex min-w-0 flex-col gap-3.5 rounded-2xl bg-page p-5 shadow-card max-sm:p-4">
<div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
<h2 className="m-0 flex items-center gap-2 text-[15px] leading-5 font-semibold tracking-[-0.01em] text-ink">
{icon && <Icon name={icon} size={16} className="text-muted" />}
{title}
</h2>
{note && <span className="font-mono text-[11.5px] text-muted">{note}</span>}
</div>
{children}
</section>
);
}
export const Hint = ({ children, id, className }: { children: ReactNode; id?: string; className?: string }) => (
<small id={id} className={cn("block text-[12.5px] leading-[18px] text-muted", className)}>
{children}
</small>
);
/** A checkbox row: the box, the words, and a quiet line under them. */
export function CheckRow({ checked, disabled, onChange, children }: { checked: boolean; disabled?: boolean; onChange: (on: boolean) => void; children: ReactNode }) {
return (
<label className={cn("flex items-start gap-3 rounded-xl px-3 py-2.5 transition-colors duration-150", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:bg-tile")}>
<input type="checkbox" className={checkClass} checked={checked} disabled={disabled} onChange={(e) => onChange(e.target.checked)} />
<span className="flex min-w-0 flex-col gap-0.5 text-[14px] leading-5 text-ink">{children}</span>
</label>
);
}
/** The chosen questions in the order Yasmar asks them, with the fixed last question under them. */
export function QuestionOrder({ chosen, alwaysLast, onChange }: { chosen: PlanQuestion[]; alwaysLast: string; onChange: (next: (c: PlanQuestion[]) => PlanQuestion[]) => void }) {
const tool = (label: string, tip: string, icon: IconName, disabled: boolean, act: () => void) => (
<Tip label={tip} disabled={disabled}>
<Button variant="ghost" size="icon-sm" aria-label={label} disabled={disabled} onClick={act}>
<Icon name={icon} size={15} />
</Button>
</Tip>
);
return (
<ol className="m-0 flex list-none flex-col p-0">
{chosen.map((q, i) => (
<li key={q.id} className="flex items-start gap-3 py-2.5 shadow-[inset_0_-1px_0_var(--color-hairline)]">
<span className="mt-px flex size-5 shrink-0 items-center justify-center rounded-full bg-quiet font-mono text-[11px] text-cell">{i + 1}</span>
<span className="flex min-w-0 flex-1 flex-col gap-0.5 text-[14px] leading-5 text-ink">
{q.text}
<small className="text-[12.5px] leading-[18px] text-muted">{q.reason}</small>
</span>
<span className="flex shrink-0 items-center gap-0.5">
{tool(`Move "${q.text}" up`, "Move up", "arrow-up-line", i === 0, () => onChange((c) => moveQuestion(c, q.id, -1)))}
{tool(`Move "${q.text}" down`, "Move down", "arrow-down-line", i === chosen.length - 1, () => onChange((c) => moveQuestion(c, q.id, 1)))}
{tool(`Remove "${q.text}"`, "Remove", "delete-bin-line", false, () => onChange((c) => c.filter((x) => x.id !== q.id)))}
</span>
</li>
))}
<li className="flex items-start gap-3 py-2.5 text-muted">
<Icon name="lock-line" size={15} label="Fixed" className="mt-0.5 text-subtle" />
<span className="min-w-0 flex-1 text-[14px] leading-5">
{alwaysLast} <small className="text-[12.5px] text-subtle">Always last</small>
</span>
</li>
</ol>
);
}
/** The approved closing lines: some need you to read the answers today or a reminder. */
export function Closing({
template,
first,
readToday,
reminder,
closingIds,
onReadToday,
onReminder,
onClosings,
}: {
template: CheckInPlanTemplate;
first: string;
readToday: boolean;
reminder: boolean;
closingIds: string[];
onReadToday: (on: boolean) => void;
onReminder: (on: boolean) => void;
onClosings: (next: (ids: string[]) => string[]) => void;
}) {
return (
<>
<div className="flex flex-col">
<CheckRow checked={readToday} onChange={onReadToday}>
I will read the answers today
</CheckRow>
<CheckRow checked={reminder} onChange={onReminder}>
Set me a reminder to call {first} back
</CheckRow>
</div>
<fieldset className="m-0 flex flex-col border-0 p-0 shadow-[inset_0_1px_0_var(--color-hairline)] pt-2">
<legend className="sr-only">Closing lines</legend>
{template.closings.map((c) => {
const available = closingAvailable(c, readToday, reminder);
return (
<CheckRow
key={c.id}
checked={c.fixed || (available && closingIds.includes(c.id))}
disabled={c.fixed || !available}
onChange={(on) => onClosings((ids) => (on ? [...ids, c.id] : ids.filter((x) => x !== c.id)))}
>
{c.text}
{c.fixed ? <small className="text-[12.5px] text-muted">Always said</small> : !available && <small className="text-[12.5px] text-muted">{c.requires === "read_today" ? "Needs you to read them today" : "Needs a reminder"}</small>}
</CheckRow>
);
})}
</fieldset>
<Hint className="flex items-start gap-1.5">
<Icon name="lock-line" size={13} className="mt-0.5 text-subtle" /> <span>Always last: {template.safetyNet}</span>
</Hint>
</>
);
}
/** When Yasmar may ring: now, or one of the allowed two-hour windows, as chips. */
export function WhenChoice({ nowOk, choices, when, patientOffset, now, onWhen }: { nowOk: boolean; choices: Window[]; when: "now" | number; patientOffset: number; now: Date; onWhen: (w: "now" | number) => void }) {
const chip = (checked: boolean, disabled: boolean, label: string, key: string, pick: () => void) => (
<label
key={key}
className={cn(
"relative inline-flex h-9 cursor-pointer items-center rounded-full px-3.5 text-[13px] font-medium text-body shadow-[inset_0_0_0_1px_var(--color-line)] transition-[background-color,color,box-shadow] duration-150 select-none hover:bg-tile",
"has-[:checked]:bg-ink has-[:checked]:text-white has-[:checked]:shadow-[inset_0_0_0_1px_var(--color-ink)] has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-brand",
disabled && "pointer-events-none opacity-40",
)}
>
<input type="radio" name="when" className="sr-only" checked={checked} disabled={disabled} onChange={pick} />
{label}
</label>
);
return (
<div className="flex flex-wrap gap-2" role="radiogroup" aria-label="When Yasmar may ring">
{chip(when === "now", !nowOk, "Now", "now", () => onWhen("now"))}
{choices.map((w, i) => chip(when === i, false, windowLabel(w, patientOffset, now), w.start.toISOString(), () => onWhen(i)))}
</div>
);
}
/** The expiry choice, inline. */
export function Expiry({ value, max, onChange }: { value: number; max: number; onChange: (h: number) => void }) {
return (
<label className="flex flex-wrap items-center gap-2.5 text-[13px] font-medium text-cell">
The request expires after
<select className={cn(fieldClass, "h-[34px] w-auto pr-8 text-[13px]")} value={value} onChange={(e) => onChange(Number(e.target.value))}>
{[24, 48, 72].filter((h) => h <= max).map((h) => (
<option key={h} value={h}>
{h} hours
</option>
))}
</select>
</label>
);
}
const fixedPart: ScriptLine["part"][] = ["opening", "identity", "consent", "feeling", "last", "safety"];
/** Exactly what Yasmar will say, in order: your questions in ink, the fixed lines quieter. */
export function ScriptPreview({ script }: { script: ScriptLine[] }) {
return (
<ol className="m-0 flex list-none flex-col gap-1 p-0">
{script.map((l, i) => (
<li
key={i}
className={cn(
"rounded-lg px-3 py-2 text-[14px] leading-5",
l.part === "question" ? "bg-tile font-medium text-ink" : fixedPart.includes(l.part) ? "text-cell" : "text-body",
)}
>
{l.text}
{l.reason && <small className="text-[12.5px] font-normal text-muted"> · {l.reason}</small>}
</li>
))}
</ol>
);
}