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}

)}
); }