// 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 (
Pantau seluruh transaksi QRIS merchant secara real-time dan kelola pembatalan/refund dana.
| 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)} | {/* Status Badge */}{statusLabel} | {/* Actions */}
|
Anda akan memproses refund dana sebesar {fmtRp(selectedTx.amount)} untuk transaksi {selectedTx.id} di merchant {selectedTx.merchantName}.