import { useEffect, useMemo, useState } from 'react';
import { AlertTriangle, CheckCircle2, Sparkles } from 'lucide-react';
import { Button, Card, SelectField } from '../';
import { SchemaGuard } from '../form';
import { useQuery, useZino } from '../../api/provider';
import { fieldFromSchema } from '../../api/schema';
import { ACTIVITIES } from '../../api/config';
import type { ActivityBodyProps } from './types';
const F = ACTIVITIES.SUBMIT_UNDERWRITING.fields;
export function UnderwritingBody({ instanceId, record, onSuccess }: ActivityBodyProps) {
const { client } = useZino();
const schemaQ = useQuery(() => client.formSchema(ACTIVITIES.SUBMIT_UNDERWRITING.uid, instanceId), [instanceId]);
const statusOptions = useMemo(() => fieldFromSchema(schemaQ.data, F.status)?.options ?? [], [schemaQ.data]);
const [status, setStatus] = useState('approved');
useEffect(() => {
if (record?.underwriting_status) setStatus(String(record.underwriting_status));
}, [record]);
const [submitting, setSubmitting] = useState(false);
const [result, setResult] = useState<{ ok: boolean; msg: string } | null>(null);
async function submit() {
setSubmitting(true);
setResult(null);
try {
const res = await client.performActivity(instanceId, ACTIVITIES.SUBMIT_UNDERWRITING.uid, {
[F.status]: status,
});
setResult({ ok: true, msg: 'Underwriting verdict submitted.' });
onSuccess?.(res);
} catch (e) {
setResult({
ok: false,
msg: e instanceof Error ? e.message : ((e as { message?: string })?.message ?? 'Submit failed'),
});
} finally {
setSubmitting(false);
}
}
return (
{result && (
{result.ok && }
{result.msg}
)}
Underwriting reference is auto-generated on submit (UW-YYYY-####).
{record?.eligibility_notes ? (
) : (
No eligibility assessment on file yet — Aria Eligibility writes it when documents are collected.
)}
);
}
// --- Eligibility notes (AI-generated, free-form) -------------------------------
// Aria Eligibility writes eligibility_notes as semi-structured prose:
// "DOCUMENTS: …", "ELIGIBILITY CHECK (…):", "✓ Age …", "REFERRAL REASON: …".
// We parse it leniently into headings / check-items / bullets / paragraphs.
// Nothing is guaranteed — if no structure is detected we fall back to the raw
// text exactly as the model emitted it. Rendered as React children (escaped),
// never dangerouslySetInnerHTML.
type NoteBlock =
| { kind: 'heading'; text: string; inline?: string }
| { kind: 'check'; ok: boolean; text: string }
| { kind: 'bullet'; text: string }
| { kind: 'para'; text: string };
function parseNotes(raw: string): NoteBlock[] {
const blocks: NoteBlock[] = [];
for (const line of raw.split(/\r?\n/)) {
const t = line.trim();
if (!t) continue;
let m: RegExpMatchArray | null;
if ((m = t.match(/^[✓✔☑]\s*(.+)$/))) {
blocks.push({ kind: 'check', ok: true, text: m[1] });
continue;
}
if ((m = t.match(/^[✗✘×✕☒]\s*(.+)$/))) {
blocks.push({ kind: 'check', ok: false, text: m[1] });
continue;
}
if ((m = t.match(/^[-•*]\s+(.+)$/))) {
blocks.push({ kind: 'bullet', text: m[1] });
continue;
}
// Heading: short prefix before a colon whose first word is ALL-CAPS
// (DOCUMENTS, ELIGIBILITY, REFERRAL). Keeps mixed-case tails like
// "(ABSLI DigiShield Plan)" inside the heading.
const colon = t.indexOf(':');
if (colon > 1 && colon <= 60) {
const head = t.slice(0, colon).trim();
const firstWord = (head.split(/\s+/)[0] || '').replace(/[^A-Za-z]/g, '');
if (firstWord.length >= 2 && firstWord === firstWord.toUpperCase()) {
const inline = t.slice(colon + 1).trim();
blocks.push({ kind: 'heading', text: head, inline: inline || undefined });
continue;
}
}
blocks.push({ kind: 'para', text: t });
}
return blocks;
}
function EligibilityNotes({ notes, status }: { notes: string; status?: string | null }) {
const blocks = useMemo(() => parseNotes(notes), [notes]);
const structured = blocks.some((b) => b.kind !== 'para');
const s = (status ?? '').toLowerCase();
// On the navy card, status reads as a tinted text on a translucent chip.
const chipText =
s === 'eligible' ? 'text-emerald-300' : s === 'ineligible' ? 'text-ruby-500' : 'text-amber-300'; // refer / unknown
let first = true;
return (
Eligibility assessment · Aria Eligibility
{status && (
{status}
)}
{structured ? (
{blocks.map((b, i) => {
if (b.kind === 'heading') {
const el = (
{b.text}
{b.inline &&
{b.inline}
}
);
first = false;
return el;
}
if (b.kind === 'check') {
return (
{b.ok ? (
) : (
)}
{b.text}
);
}
if (b.kind === 'bullet') {
return (
{b.text}
);
}
return (
{b.text}
);
})}
) : (
// Fallback — render the model's output verbatim, line breaks preserved.
{notes}
)}
);
}