// transaction-monitor.jsx — Transaction Monitor page for Hibank QRIS BackOffice function TransactionMonitorPage({ merchants, primary, secondary }) { const TEAL = primary || '#0056D2'; const ORANGE = secondary || '#cf5a27'; // Seed initial mock transactions const [transactions, setTransactions] = React.useState([ { id: 'TX-260624-8831', time: '2026-06-24T11:05:12', merchantId: 'MRC-00246', merchantName: 'Kopi Senja Nusantara', type: 'QRIS MPM (GoPay)', amount: 45000, mdr: 315, net: 44685, status: 'success', customerName: 'Aditya Pratama', rrn: '992817283912', terminalId: 'POS-001', issuer: 'GoPay' }, { id: 'TX-260624-8830', time: '2026-06-24T10:58:44', merchantId: 'MRC-00244', merchantName: 'Batik Lestari Pekalongan', type: 'QRIS MPM (BCA)', amount: 285000, mdr: 1995, net: 283005, status: 'success', customerName: 'Siti Aminah', rrn: '992817283911', terminalId: 'POS-002', issuer: 'BCA Mobile' }, { id: 'TX-260624-8829', time: '2026-06-24T10:42:15', merchantId: 'MRC-00245', merchantName: 'Toko Bangunan Maju Jaya', type: 'QRIS MPM (Dana)', amount: 1540000, mdr: 10780, net: 1529220, status: 'success', customerName: 'Hendra Wijaya', rrn: '992817283910', terminalId: 'POS-001', issuer: 'Dana' }, { id: 'TX-260624-8828', time: '2026-06-24T10:30:00', merchantId: 'MRC-00246', merchantName: 'Kopi Senja Nusantara', type: 'QRIS MPM (OVO)', amount: 28000, mdr: 196, net: 27804, status: 'pending', customerName: 'Fajar Nugraha', rrn: '992817283909', terminalId: 'POS-003', issuer: 'OVO' }, { id: 'TX-260624-8827', time: '2026-06-24T10:15:33', merchantId: 'MRC-00244', merchantName: 'Batik Lestari Pekalongan', type: 'QRIS MPM (ShopeePay)', amount: 450000, mdr: 3150, net: 446850, status: 'failed', customerName: 'Rina Marlina', rrn: '992817283908', terminalId: 'POS-001', issuer: 'ShopeePay' }, { id: 'TX-260624-8826', time: '2026-06-24T09:55:12', merchantId: 'MRC-00246', merchantName: 'Kopi Senja Nusantara', type: 'QRIS CPM (Hibank)', amount: 120000, mdr: 840, net: 119160, status: 'success', customerName: 'Budi Santoso', rrn: '992817283907', terminalId: 'POS-002', issuer: 'LinkAja' }, { id: 'TX-260624-8825', time: '2026-06-24T09:41:00', merchantId: 'MRC-00245', merchantName: 'Toko Bangunan Maju Jaya', type: 'QRIS MPM (Hibank)', amount: 75000, mdr: 525, net: 74475, status: 'refunded', customerName: 'Dewi Lestari', rrn: '992817283906', terminalId: 'POS-002', issuer: 'GoPay' }, { id: 'TX-260624-8824', time: '2026-06-24T09:12:05', merchantId: 'MRC-00244', merchantName: 'Batik Lestari Pekalongan', type: 'QRIS MPM (Hibank)', amount: 95000, mdr: 665, net: 94335, status: 'success', customerName: 'Joko Widodo', rrn: '992817283905', terminalId: 'POS-003', issuer: 'OVO' } ]); // Filters state const [searchTerm, setSearchTerm] = React.useState(''); const [statusFilter, setStatusFilter] = React.useState('all'); const [merchantFilter, setMerchantFilter] = React.useState('all'); // Modal / Drawer state const [selectedTx, setSelectedTx] = React.useState(null); const [showRefundModal, setShowRefundModal] = React.useState(false); const [refundReason, setRefundReason] = React.useState(''); const [toastMessage, setToastMessage] = React.useState(null); // Formatting helpers const fmtRp = (v) => { return 'Rp ' + Number(v).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', second: '2-digit' }); }; // Helper: Trigger toast message const triggerToast = (msg) => { setToastMessage(msg); setTimeout(() => setToastMessage(null), 3000); }; // Process Refund action const handleRefund = () => { if (!selectedTx || !refundReason.trim()) return; setTransactions(prev => prev.map(t => { if (t.id !== selectedTx.id) return t; return { ...t, status: 'refunded', refundReason: refundReason.trim(), refundAt: new Date().toISOString() }; })); triggerToast(`Transaksi ${selectedTx.id} berhasil di-refund.`); setShowRefundModal(false); setSelectedTx(null); setRefundReason(''); }; // Simulator: Generate a random transaction const handleSimulateTransaction = () => { const randomMerchants = approvedMerchants.length > 0 ? approvedMerchants : [ { id: 'MRC-00246', merchantName: 'Kopi Senja Nusantara' }, { id: 'MRC-00244', merchantName: 'Batik Lestari Pekalongan' }, { id: 'MRC-00245', merchantName: 'Toko Bangunan Maju Jaya' } ]; const merchant = randomMerchants[Math.floor(Math.random() * randomMerchants.length)]; const issuers = ['GoPay', 'OVO', 'Dana', 'ShopeePay', 'BCA Mobile', 'LinkAja', 'HiBank']; const issuer = issuers[Math.floor(Math.random() * issuers.length)]; const types = [`QRIS MPM (${issuer})`, `QRIS CPM (${issuer})`]; const type = types[Math.floor(Math.random() * types.length)]; const statuses = ['success', 'success', 'success', 'pending', 'failed']; const status = statuses[Math.floor(Math.random() * statuses.length)]; const amount = Math.floor(Math.random() * 45 + 1) * 10000; // 10k to 450k const mdr = Math.floor(amount * 0.007); const net = amount - mdr; const txId = `TX-${new Date().toISOString().slice(2, 10).replace(/-/g, '')}-${Math.floor(Math.random() * 9000 + 1000)}`; const names = ['Andi', 'Siti', 'Budi', 'Rina', 'Dedi', 'Lina', 'Hendra', 'Dewi', 'Ahmad', 'Mega']; const customerName = names[Math.floor(Math.random() * names.length)] + ' ' + ['Wijaya', 'Rahayu', 'Santoso', 'Marlina', 'Kurniawan', 'Lestari'][Math.floor(Math.random() * 6)]; const rrn = Math.floor(Math.random() * 900000000000 + 100000000000).toString(); const terminalId = `POS-00${Math.floor(Math.random() * 3 + 1)}`; const newTx = { id: txId, time: new Date().toISOString(), merchantId: merchant.id, merchantName: merchant.merchantName, type, amount, mdr, net, status, customerName, rrn, terminalId, issuer }; setTransactions(prev => [newTx, ...prev]); triggerToast(`Transaksi baru masuk: ${fmtRp(amount)} di ${merchant.merchantName} (${status.toUpperCase()})`); }; // Find approved merchants list for dropdown filter const approvedMerchants = merchants ? merchants.filter(m => m.status === 'approved') : []; // Filtered transactions const filteredTxs = transactions.filter(t => { const matchesSearch = t.id.toLowerCase().includes(searchTerm.toLowerCase()) || t.customerName.toLowerCase().includes(searchTerm.toLowerCase()) || t.rrn.toLowerCase().includes(searchTerm.toLowerCase()); const matchesStatus = statusFilter === 'all' || t.status === statusFilter; const matchesMerchant = merchantFilter === 'all' || t.merchantId === merchantFilter; return matchesSearch && matchesStatus && matchesMerchant; }); // Calculate statistics const totalVolume = transactions.filter(t => t.status === 'success').reduce((sum, t) => sum + t.amount, 0); const successCount = transactions.filter(t => t.status === 'success').length; const totalCount = transactions.filter(t => t.status !== 'failed').length; const successRate = totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(1) : '100'; const pendingVolume = transactions.filter(t => t.status === 'pending').reduce((sum, t) => sum + t.amount, 0); return (
{/* Header & Simulator Button */}
Transaction Monitor

Monitor Transaksi QRIS

Pantau seluruh transaksi QRIS merchant secara real-time dan kelola pembatalan/refund dana.

{/* Live Simulator Button */}
{/* ── STATS SUMMARY CARDS ── */}
{[ { label: 'Volume Transaksi Sukses', value: fmtRp(totalVolume), sub: 'Dana terkumpul bersih', icon: 'bx-wallet', color: TEAL }, { label: 'Tingkat Keberhasilan', value: `${successRate}%`, sub: 'Rasio transaksi berhasil', icon: 'bx-trending-up', color: '#74b50c' }, { label: 'Transaksi Menunggu (Pending)', value: fmtRp(pendingVolume), sub: 'Proses verifikasi gateway', icon: 'bx-time-five', color: '#d9920b' }, { label: 'Total Transaksi Masuk', value: `${transactions.length} Trx`, sub: 'Termasuk gagal & refund', icon: 'bx-transfer', color: ORANGE } ].map((card, i) => (
{card.label}
{card.value}
{card.sub}
))}
{/* ── FILTERS PANEL ── */}
{/* Search Input */}
setSearchTerm(e.target.value)} style={{ border: 'none', background: 'transparent', outline: 'none', fontSize: 13, color: '#171919', width: '100%', fontFamily: 'inherit' }} />
{/* Status Filter */}
Status:
{/* Merchant Filter */}
Merchant:
{/* ── TRANSACTION LIST TABLE ── */}
{filteredTxs.length === 0 ? ( ) : filteredTxs.map(tx => { const isSuccess = tx.status === 'success'; const isPending = tx.status === 'pending'; const isFailed = tx.status === 'failed'; const isRefunded = tx.status === 'refunded'; let statusBg = '#f6f6f6', statusColor = '#777', statusLabel = tx.status.toUpperCase(); if (isSuccess) { statusBg = 'rgba(116,181,12,0.12)'; statusColor = '#74b50c'; statusLabel = 'Sukses'; } else if (isPending) { statusBg = 'rgba(217,146,11,0.12)'; statusColor = '#d9920b'; statusLabel = 'Pending'; } else if (isFailed) { statusBg = 'rgba(237,49,81,0.1)'; statusColor = '#ed3151'; statusLabel = 'Gagal'; } else if (isRefunded) { statusBg = 'rgba(79,70,229,0.12)'; statusColor = '#4F46E5'; statusLabel = 'Refunded'; } return ( e.currentTarget.style.background = '#fafbfc'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}> {/* Status Badge */} {/* Actions */} ); })}
ID Transaksi Waktu Merchant Sumber / Metode Nominal Gross MDR (0.7%) Net Settlement Status Aksi
Tidak ada transaksi yang cocok dengan kriteria filter.
{tx.id} {fmtDate(tx.time)}
{tx.merchantName}
MID: {tx.merchantId}
{tx.type} {fmtRp(tx.amount)} {fmtRp(tx.mdr)} {fmtRp(tx.net)} {statusLabel}
{/* ── TOAST NOTIFICATION ── */} {toastMessage && (
{toastMessage}
)} {/* ── DETAIL DRAWER (SLIDE IN FROM RIGHT) ── */} {selectedTx && !showRefundModal && (
setSelectedTx(null)} style={{ position: 'absolute', inset: 0, background: 'rgba(23,25,25,0.4)', backdropFilter: 'blur(3px)', animation: 'fadeIn 200ms ease' }} />
{/* Drawer Header */}

Struk & Rincian Transaksi

{/* Drawer Content */}
{/* Receipt Visual Style */}
QRIS hibank Payment Gateway

{selectedTx.merchantName}

MID: {selectedTx.merchantId} | POS: {selectedTx.terminalId}
{fmtRp(selectedTx.amount)}
Status: {selectedTx.status.toUpperCase()}
{/* Info List */}
ID Transaksi {selectedTx.id}
RRN (Ref Number) {selectedTx.rrn}
Waktu Transaksi {fmtDate(selectedTx.time)}
Metode Bayar {selectedTx.type}
Sumber Dana / Issuer {selectedTx.issuer}
Nama Pelanggan {selectedTx.customerName}
Nominal Kotor (Gross) {fmtRp(selectedTx.amount)}
MDR Fee Bank (0.7%) -{fmtRp(selectedTx.mdr)}
Net Settlement {fmtRp(selectedTx.net)}
{selectedTx.status === 'refunded' && (
Informasi Refund
Alasan: {selectedTx.refundReason || '—'}
{selectedTx.refundAt &&
Waktu: {fmtDate(selectedTx.refundAt)}
}
)}
{/* Drawer Footer */}
)} {/* ── REFUND MODAL ── */} {showRefundModal && selectedTx && (
setShowRefundModal(false)}>
e.stopPropagation()}>

Ajukan Refund QRIS

Anda akan memproses refund dana sebesar {fmtRp(selectedTx.amount)} untuk transaksi {selectedTx.id} di merchant {selectedTx.merchantName}.