// Result dashboards (Bank Statement, Credit Bureau, Invoice), Admin, Logout, Borrowers. // Re-themed to Editorial Forest tokens. Data shape and props are unchanged. // ——— Tiny chart primitives — colors default to theme tokens ——— const Bars = ({data, w=240, h=60, color='hsl(var(--primary))', neg='hsl(var(--danger))'}) => { const max = Math.max(...data.map(d=>Math.abs(d))); const bw = w / data.length; return ( {data.map((d,i)=>{ const bh = (Math.abs(d)/max)*(h-4); return =0 ? (h-bh)/1 : (h/2)} width={bw-2} height={bh} fill={d>=0?color:neg} rx={2}/>; })} ); }; const Line = ({data, w=240, h=60, color='hsl(var(--primary))', fill='hsl(var(--accent-soft))'}) => { const max = Math.max(...data), min = Math.min(...data); const pts = data.map((d,i)=>[i*(w/(data.length-1)), h-2 - ((d-min)/(max-min||1))*(h-4)]); const dstr = 'M'+pts.map(p=>p.join(',')).join(' L'); const area = dstr+` L${w},${h} L0,${h} Z`; return ( ); }; const Donut = ({value, size=80, stroke=10, color='hsl(var(--primary))', track='hsl(var(--surface-2))', label}) => { const r = (size-stroke)/2, c = 2*Math.PI*r; return (
{value}
{label &&
{label}
}
); }; // ——— Result Dashboard: Bank Statement ——— function BankStatementReport({onBack, currency, report}) { const [tab, setTab] = React.useState('overview'); const tabs = ['overview','transactions','daily balance','counterparties','cam analysis']; const cur = currency || { code: 'IDR', symbol: 'Rp', locale: 'id-ID' }; const transactions = React.useMemo(() => { if (!report) return null; return report?.transactions || []; }, [report]); const sessionId = report?.session_id || ''; const customerId = report?.customer_id || ''; return (
{e.preventDefault(); onBack && onBack()}}>Sessions {sessionId}
Bank Statement Analysis
Completed
Borrower {customerId} · {transactions ? transactions.length + ' transactions' : 'Loading…'}
{tabs.map(t=>( ))}
{tab==='overview' && } {tab==='transactions' && } {tab==='daily balance' && } {tab==='counterparties' && } {tab==='cam analysis' && }
); } function KPI({label, value, delta, tone='brand', spark}) { const toneC = { brand:'hsl(var(--primary))', success:'hsl(var(--success))', warning:'hsl(var(--warning))', danger:'hsl(var(--danger))', }[tone]; const fillC = { brand:'hsl(var(--accent-soft))', success:'hsl(var(--success-bg))', warning:'hsl(var(--warning-bg))', danger:'hsl(var(--danger-bg))', }[tone]; return (
{label}
{value}
{delta &&
{delta}
}
{spark && }
); } function OverviewPane() { return (
Daily balance trend
{['30d','90d','All'].map((t,i)=>)}
Jan 1Feb 1Mar 1Apr 21
Transaction mix
{[['Payments in','hsl(var(--primary))',42],['Refunds','hsl(var(--accent))',18],['Transfers','hsl(var(--gold))',22],['Other','hsl(var(--border-strong))',18]].map(([l,c,v])=>
{l}
{v}%
)}
Key flags
3 flags
{[ {tone:'warning', title:'Round-number deposits detected', desc:'4 credits of exactly Rp 100M within 3 days (Feb 8–11). Manual review recommended.'}, {tone:'success', title:'Stable salary inflow', desc:'Monthly credit ~Rp 48M from PT SINAR MAS AGRO, 4 consecutive months.'}, {tone:'danger', title:'2 cheque bounces', desc:'Rp 12.5M on Mar 3 (insufficient funds), Rp 8.2M on Feb 17 (stop payment).'}, ].map((f,i)=>{ const bg = `hsl(var(--${f.tone}-bg))`; const border = `hsl(var(--${f.tone}) / 0.3)`; const fg = `hsl(var(--${f.tone}-fg))`; return (
{f.tone==='success'?:}
{f.title}
{f.desc}
); })}
); } function TransactionsPane({ transactions }) { const cur = useCurrency(); const [search, setSearch] = React.useState(''); const [catFilter, setCatFilter] = React.useState('All'); const categories = React.useMemo(() => { if (!transactions?.length) return []; return ['All', ...Array.from(new Set(transactions.map(t => t.category || 'Uncategorized'))).sort()]; }, [transactions]); const filtered = React.useMemo(() => { if (!transactions) return []; return transactions.filter(t => { const matchSearch = !search || (t.description || '').toLowerCase().includes(search.toLowerCase()); const matchCat = catFilter === 'All' || (t.category || 'Uncategorized') === catFilter; return matchSearch && matchCat; }); }, [transactions, search, catFilter]); if (transactions === null) return (
Loading transactions…
); return (
setSearch(e.target.value)} placeholder="Search transactions" style={{flex:1, border:'none', outline:'none', fontSize:13, fontFamily:'inherit', color:'hsl(var(--fg))', background:'transparent'}} />
{filtered.length} transactions
{filtered.length === 0 ? (
No transactions match your filter
) : (
{filtered.map((t, i) => { const isCredit = (t.type || '').toLowerCase() === 'credit'; const amt = parseFloat(t.amount) || 0; const bal = parseFloat(t.balance); const cat = t.category || 'Uncategorized'; return ( ); })}
Date Description Category Amount Balance
{t.date || '—'} {t.description || '—'} {cat} {isCredit ? '+' : '-'}{fmtAmt(amt, cur)} {isNaN(bal) ? '—' : fmtAmt(bal, cur)}
)} ); } function DailyBalancePane() { const data = Array.from({length:90}, (_,i)=>600+Math.sin(i/8)*80+i*3+Math.random()*40); return (
Daily closing balance — 90 days
); } function CounterpartiesPane() { const rows = [ {name:'PT BUMI RESOURCES', type:'Business', txns:24, total:'Rp 1.2B', direction:'in'}, {name:'CV KARYA JAYA', type:'Vendor', txns:18, total:'Rp 485M', direction:'out'}, {name:'PT SINAR MAS AGRO', type:'Employer', txns:4, total:'Rp 192M', direction:'in'}, {name:'BCA CREDIT CARD', type:'Card', txns:12, total:'Rp 294M', direction:'out'}, {name:'BNI HOME LOAN', type:'EMI', txns:4, total:'Rp 73M', direction:'out'}, ]; return ( {rows.map((r,i)=>( ))}
Counterparty Type Transactions Volume
{r.name}
{r.type} {r.txns} {r.direction==='in'?'+':'−'}{r.total}
); } function CamPane() { return (
Credit Appraisal Memo
Generated Apr 21, 2026 · Model v2.1
Executive Summary
Borrower demonstrates a consistent inflow pattern with an average daily balance of Rp 842M over 90 days. Stable business income from PT BUMI RESOURCES accounts for 48% of credits. Two cheque returns in the period warrant a soft enquiry. Overall cashflow profile is supportive of the proposed facility.
{[ {h:'Cashflow Analysis', b:'Net cashflow positive in 11 of 12 months. Inflow concentration from 3 counterparties (72%) is within acceptable limits for SME lending. No material seasonality observed.'}, {h:'Obligation Servicing', b:'Existing EMI commitments total Rp 73M/month (BNI Home Loan) with 100% on-time servicing. Credit card utilisation averaged 34% with full-payment behaviour.'}, {h:'Red Flags', b:'Four round-number deposits of Rp 100M detected between Feb 8–11; review recommended. Two cheque returns in the period (Rp 20.7M total). No adverse bureau record identified.'}, {h:'Recommendation', b:'APPROVE with a facility size up to Rp 5.2B, 24-month tenor, at a contracted rate of 11.5% p.a. Condition: quarterly bank statement refresh.'}, ].map((s,i)=>(
{s.h}
{s.b}
))}
Credit score
Good
Top 28% in segment
Ratios
{[['DSCR','1.86','success'],['Debt/Income','0.24','success'],['Cash buffer','3.2 mo','brand'],['Inflow stability','Low Var','success']].map(([l,v,t])=>
{l} {v}
)}
); } // ——— Credit Bureau ——— function CreditBureauReport({onBack}) { return (
Credit Bureau Report
Completed
Borrower BRW-2931 · Budi Santoso · CIBIL + Experian
CIBIL Score
782
Excellent
Credit Utilisation
24%
Healthy
Active accounts
6
3 CC 2 Loans 1 Home
Payment history — 24 months
{Array.from({length:24}).map((_,i)=>{ const v = i===7?'30':i===15?'30':'OK'; return
{v==='OK'?'':v}
; })}
Apr 2024Apr 2025Apr 2026
); } // ——— Invoice ——— function InvoiceReport({onBack}) { return (
Invoice Analysis
Completed
Invoice #INV-2024-8842 · PT Sinar Mas Agro
Invoice
INV-2024-8842
Total
Rp 284,500,000
Supplier
PT Karya Manufaktur Indonesia
Jl. Sudirman Kav. 52, Jakarta 12190
NPWP: 01.234.567.8-901.000
Buyer
PT Sinar Mas Agro
Jl. Thamrin No. 51, Jakarta 10350
NPWP: 02.345.678.9-012.000
{[['Palm oil refinery equipment — Module A',2,'85,000,000','170,000,000'],['Installation & commissioning',1,'42,500,000','42,500,000'],['Training (on-site, 5 days)',1,'18,000,000','18,000,000'],['Spare parts kit',3,'18,000,000','54,000,000']].map((r,i)=>( ))}
Item Qty Rate Amount
{r[0]} {r[1]} Rp {r[2]} Rp {r[3]}
Extraction confidence
All fields extracted. Manual review recommended for 2 line items.
Cross-checks
{[['NPWP format','pass'],['GST calculation','pass'],['Sum of line items','pass'],['Duplicate invoice','warn']].map(([l,s])=>
{s==='pass'?:}
{l} {s}
)}
); } // ——— Admin / Team ——— function AdminTeam() { const members = [ {name:'Fina Lestari', email:'fina.lestari@bankx.id', role:'Admin', last:'Active now'}, {name:'Aditya Rahman', email:'aditya.rahman@bankx.id', role:'Analyst', last:'12 min ago'}, {name:'Siti Nuraini', email:'siti.n@bankx.id', role:'Analyst', last:'2 hours ago'}, {name:'Rendra Pratama', email:'rendra.p@bankx.id', role:'Viewer', last:'Yesterday'}, {name:'Dewi Kusuma', email:'dewi.k@bankx.id', role:'Analyst', last:'3 days ago'}, ]; return (
Members
5 / 15
Active today
3
Plan
Growth
Pro-rated billing
Team members
{members.map((m,i)=>( ))}
Name Role Last active
{m.name}
{m.email}
{m.role} {m.last}
); } // ——— Borrowers ——— function BorrowersList({ borrowers }) { const list = borrowers || []; if (borrowers === undefined) return (
Loading borrowers…
); if (list.length === 0) return (
No borrowers yet.
); return (
{list.map((b) => (
{b.customer_id}
{b.total_sessions ?? 0} sessions {(b.completed_sessions ?? 0) > 0 && {b.completed_sessions} completed}
))}
); } // ——— Logout confirmation ——— function LogoutConfirm({onCancel, onConfirm}) { return (
Log out of CredNX?
You'll need to sign in again to access your sessions. Any in-progress uploads will continue in the background.
); } Object.assign(window, { Bars, Line, Donut, KPI, BankStatementReport, CreditBureauReport, InvoiceReport, AdminTeam, BorrowersList, LogoutConfirm });