// approval.jsx — Merchant Onboarding Approval (2-level maker-checker workflow) function ApprovalPage({ onLogout, onNavigate, merchants, setMerchants, primary, secondary, sidebarColor, role: userRole = 'super', user }) { const TEAL = primary || '#0056D2'; const ORANGE = secondary || '#cf5a27'; const DARK = '#001F5B'; const GREEN = '#74b50c'; const RED = '#ed3151'; const AMBER = '#d9920b'; const [sidebarOpen, setSidebarOpen] = React.useState(true); const [role, setRole] = React.useState('l1'); // 'l1' = Verifikator, 'l2' = Approver const [activeTab, setActiveTab] = React.useState('pending'); // 'pending' | 'history' const [selected, setSelected] = React.useState(null); // merchant id open in detailed subpage const [note, setNote] = React.useState(''); const [toast, setToast] = React.useState(null); const fmtRp = (v) => { const n = String(v).replace(/[^0-9]/g, ''); return n ? 'Rp ' + Number(n).toLocaleString('id-ID') : '—'; }; const fmtDate = (iso) => { const d = new Date(iso); return d.toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }) + ' ' + d.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }); }; // ── status meta ── const STATUS = { pending_l1: { label: 'Menunggu Verifikasi L1', color: AMBER, bg: 'rgba(217,146,11,0.12)', icon: 'bx-time-five' }, pending_l2: { label: 'Menunggu Persetujuan L2', color: TEAL, bg: 'rgba(0,86,210,0.10)', icon: 'bx-time-five' }, approved: { label: 'Disetujui', color: GREEN, bg: 'rgba(116,181,12,0.12)', icon: 'bx-check-circle' }, rejected: { label: 'Ditolak', color: RED, bg: 'rgba(237,49,81,0.10)', icon: 'bx-x-circle' }, }; const counts = { pending_l1: merchants.filter(m => m.status === 'pending_l1').length, pending_l2: merchants.filter(m => m.status === 'pending_l2').length, approved: merchants.filter(m => m.status === 'approved').length, rejected: merchants.filter(m => m.status === 'rejected').length, }; // which stage can current role act on const myStage = role === 'l1' ? 'pending_l1' : 'pending_l2'; const pendingCount = role === 'l1' ? counts.pending_l1 : counts.pending_l2; const canAct = (m) => m.status === myStage; const visible = merchants.filter(m => { if (activeTab === 'pending') return m.status === myStage; return m.status === 'approved' || m.status === 'rejected'; }); const sel = merchants.find(m => m.id === selected); const showToast = (msg, kind) => { setToast({ msg, kind }); setTimeout(() => setToast(null), 2800); }; const decide = (decision) => { if (!sel) return; if (decision === 'reject' && !note.trim()) return; const now = new Date().toISOString(); const reviewer = role === 'l1' ? 'Dewi Anggraini (Verifikator)' : 'Arief Budiman (Approver)'; setMerchants(prev => prev.map(m => { if (m.id !== sel.id) return m; const rec = { by: reviewer, at: now, note: note.trim() || (decision === 'approve' ? 'Disetujui' : 'Ditolak'), decision }; if (role === 'l1') { return { ...m, l1: rec, status: decision === 'approve' ? 'pending_l2' : 'rejected' }; } else { return { ...m, l2: rec, status: decision === 'approve' ? 'approved' : 'rejected' }; } })); showToast( decision === 'approve' ? (role === 'l1' ? 'Diteruskan ke Approver L2' : 'Merchant disetujui & diaktifkan') : 'Pengajuan merchant ditolak', decision === 'approve' ? 'success' : 'reject' ); setNote(''); setSelected(null); }; const Badge = ({ status }) => { const s = STATUS[status]; if (!s) return null; return ( {s.label} ); }; // approval timeline node const Step = ({ n, title, rec, pending, active }) => (
{rec ? : {n}}
{n === 1 &&
}
{title}
{rec ? ( <>
{rec.decision === 'approve' ? 'Disetujui' : 'Ditolak'} oleh {rec.by}
{fmtDate(rec.at)}
{rec.note &&
"{rec.note}"
} ) : (
{active ? 'Menunggu tindakan Anda' : pending ? 'Menunggu langkah sebelumnya' : '—'}
)}
); const Row = ({ label, value, mono }) => (
{label} {value || '—'}
); return (
onNavigate(id)} sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} onLogout={onLogout} pendingApproval={counts.pending_l1 + counts.pending_l2} primary={TEAL} secondary={ORANGE} sidebarBg={sidebarColor} role={userRole} user={user} />
{/* TOPBAR */}
Approval
Persetujuan Onboarding Merchant
{/* CONTENT */}
{sel ? (
{/* Breadcrumb */}
Checker Approval / Detail Request {sel.id}
{/* Left side: Merchant Info */}
{/* Card 1: Request Details */}
Pemeriksaan Onboarding Merchant

Merchant ID: {sel.id}

Pengusul (Maker)
{sel.submittedBy || 'Arief Budiman'}
Waktu Pengajuan
{fmtDate(sel.submittedAt)}
Deskripsi Perubahan:
Pemeriksaan kelayakan & validasi dokumen onboarding untuk calon merchant: {sel.merchantName} (MID: {sel.id}, Kategori Usaha: {sel.businessType || '—'}).
{/* Special Section: Dokumen Onboarding */}

Dokumen & Informasi Onboarding Calon Merchant

{[ { label: 'NPWP Perusahaan/Pemilik', val: sel.npwp || '09.254.812.3-014.000', verified: true }, { label: 'NIK Pemilik (e-KTP)', val: sel.nik || '3273011505900002', verified: true }, { label: 'Rekening Bank Penerima', val: `${sel.bankName || 'Bank Hibank'} - ${sel.accountNumber || '8820145566'} a/n ${sel.accountHolder || sel.picName}`, verified: true }, { label: 'Dokumen Legalitas (SIUP/NIB/Badan Hukum)', val: sel.legalName || 'NIB-812739123-A', verified: true } ].map((doc, idx) => (
{doc.label}
{doc.val}
Terverifikasi
))}
{/* Log & Alur Persetujuan */}

Log & Alur Persetujuan

{/* Detailed profile layout */}
{/* Right side: Action Form Panel */}
{canAct(sel) ? (

Form Otorisasi