// report-generator.jsx — Report Generator (daftar permintaan laporan) function ReportGeneratorPage({ merchants, primary, secondary, role = 'super', user }) { const TEAL = primary || '#0056D2'; const ORANGE = secondary || '#cf5a27'; const GREEN = '#74b50c'; const RED = '#ed3151'; const AMBER = '#d9920b'; const isMerchant = role === 'merchant'; const requesterName = (user && user.name) || (isMerchant ? 'Rangga Pratama' : 'Arief Budiman'); // Report types vary by role const REPORT_TYPES = isMerchant ? [ { id: 'settlement', label: 'Laporan Settlement', icon: 'bx-wallet' }, { id: 'transaction', label: 'Laporan Transaksi QRIS', icon: 'bx-transfer' }, { id: 'refund', label: 'Laporan Refund', icon: 'bx-undo' }, { id: 'disbursement', label: 'Laporan Pencairan Dana', icon: 'bx-money-withdraw' }, ] : [ { id: 'settlement', label: 'Laporan Recon & Settlement', icon: 'bx-receipt' }, { id: 'disbursement', label: 'Laporan Disbursement', icon: 'bx-money-withdraw' }, { id: 'transaction', label: 'Laporan Transaksi QRIS', icon: 'bx-transfer' }, { id: 'onboarding', label: 'Laporan Onboarding Merchant', icon: 'bx-store' }, { id: 'fds', label: 'Laporan Fraud Detection (FDS)', icon: 'bx-shield-quarter' }, { id: 'refund', label: 'Laporan Refund', icon: 'bx-undo' }, ]; const typeLabel = (id) => (REPORT_TYPES.find(t => t.id === id) || {}).label || id; // Seed report requests const seedRequests = isMerchant ? [ { id: 'RPT-2606-0042', type: 'settlement', period: '01 Jun 2026 – 24 Jun 2026', format: 'Excel', by: requesterName, at: '2026-06-24T09:12:00', status: 'completed', size: '248 KB', rows: 1247 }, { id: 'RPT-2606-0039', type: 'transaction', period: '01 Jun 2026 – 23 Jun 2026', format: 'CSV', by: requesterName, at: '2026-06-23T16:40:00', status: 'completed', size: '1.4 MB', rows: 5820 }, { id: 'RPT-2606-0036', type: 'refund', period: 'Mei 2026', format: 'PDF', by: requesterName, at: '2026-06-22T11:05:00', status: 'processing', size: '—', rows: null }, { id: 'RPT-2605-0090', type: 'settlement', period: 'Mei 2026', format: 'Excel', by: requesterName, at: '2026-06-01T08:00:00', status: 'completed', size: '212 KB', rows: 1102 }, ] : [ { id: 'RPT-2606-0051', type: 'settlement', period: '24 Jun 2026', format: 'Excel', by: requesterName, at: '2026-06-24T10:30:00', status: 'completed', size: '512 KB', rows: 1250 }, { id: 'RPT-2606-0050', type: 'transaction', period: '01 Jun 2026 – 24 Jun 2026', format: 'CSV', by: requesterName, at: '2026-06-24T10:02:00', status: 'processing', size: '—', rows: null }, { id: 'RPT-2606-0048', type: 'disbursement', period: '23 Jun 2026', format: 'Excel', by: 'Dewi Anggraini', at: '2026-06-23T17:20:00', status: 'completed', size: '188 KB', rows: 312 }, { id: 'RPT-2606-0047', type: 'fds', period: 'Q2 2026', format: 'PDF', by: 'Arief Budiman', at: '2026-06-23T14:15:00', status: 'queued', size: '—', rows: null }, { id: 'RPT-2606-0044', type: 'onboarding', period: 'Jun 2026', format: 'Excel', by: 'Dewi Anggraini', at: '2026-06-22T09:40:00', status: 'failed', size: '—', rows: null, failReason: 'Sumber data onboarding tidak tersedia (timeout).' }, { id: 'RPT-2605-0210', type: 'settlement', period: 'Mei 2026', format: 'Excel', by: 'Arief Budiman', at: '2026-06-01T08:05:00', status: 'completed', size: '1.1 MB', rows: 28140 }, ]; const [requests, setRequests] = React.useState(seedRequests); const [showModal, setShowModal] = React.useState(false); const [filter, setFilter] = React.useState('all'); const [toast, setToast] = React.useState(null); // Form state const [fType, setFType] = React.useState(REPORT_TYPES[0].id); const [fStart, setFStart] = React.useState('2026-06-01'); const [fEnd, setFEnd] = React.useState('2026-06-24'); const [fFormat, setFFormat] = React.useState('Excel'); const triggerToast = (msg, kind = 'success') => { setToast({ msg, kind }); setTimeout(() => setToast(null), 3000); }; 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' }); }; const fmtPeriod = (s, e) => { const f = (iso) => new Date(iso).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }); return s === e ? f(s) : `${f(s)} – ${f(e)}`; }; const STATUS = { completed: { label: 'Selesai', color: GREEN, bg: 'rgba(116,181,12,0.12)', icon: 'bx-check-circle' }, processing: { label: 'Diproses', color: TEAL, bg: 'rgba(0,86,210,0.10)', icon: 'bx-loader bx-spin' }, queued: { label: 'Antrean', color: AMBER, bg: 'rgba(217,146,11,0.12)', icon: 'bx-time-five' }, failed: { label: 'Gagal', color: RED, bg: 'rgba(237,49,81,0.10)', icon: 'bx-x-circle' }, }; const counts = { total: requests.length, completed: requests.filter(r => r.status === 'completed').length, processing: requests.filter(r => r.status === 'processing' || r.status === 'queued').length, failed: requests.filter(r => r.status === 'failed').length, }; const visible = requests.filter(r => filter === 'all' || r.status === filter); const handleGenerate = (e) => { e.preventDefault(); const newId = 'RPT-2606-' + Math.floor(1000 + Math.random() * 9000); const newReq = { id: newId, type: fType, period: fmtPeriod(fStart, fEnd), format: fFormat, by: requesterName, at: new Date().toISOString(), status: 'processing', size: '—', rows: null, }; setRequests(prev => [newReq, ...prev]); setShowModal(false); triggerToast(`Permintaan laporan ${newId} sedang diproses...`, 'info'); setTimeout(() => { setRequests(prev => prev.map(r => { if (r.id !== newId) return r; const rows = Math.floor(300 + Math.random() * 5000); const kb = Math.floor(120 + Math.random() * 900); return { ...r, status: 'completed', rows, size: kb > 1024 ? (kb / 1024).toFixed(1) + ' MB' : kb + ' KB' }; })); triggerToast(`Laporan ${newId} selesai dibuat & siap diunduh.`, 'success'); }, 2200); }; const handleDownload = (r) => triggerToast(`Mengunduh ${r.id} (${typeLabel(r.type)}) — ${r.format}.`, 'success'); const handleRetry = (id) => { setRequests(prev => prev.map(r => r.id === id ? { ...r, status: 'processing', failReason: null } : r)); triggerToast('Memproses ulang permintaan laporan...', 'info'); setTimeout(() => { setRequests(prev => prev.map(r => { if (r.id !== id) return r; const rows = Math.floor(300 + Math.random() * 5000); return { ...r, status: 'completed', rows, size: Math.floor(120 + Math.random() * 600) + ' KB' }; })); triggerToast('Laporan berhasil dibuat ulang.', 'success'); }, 2000); }; const filterTabs = [ { key: 'all', label: 'Semua', count: counts.total }, { key: 'completed', label: 'Selesai', count: counts.completed }, { key: 'processing', label: 'Diproses', count: requests.filter(r => r.status === 'processing').length }, { key: 'queued', label: 'Antrean', count: requests.filter(r => r.status === 'queued').length }, { key: 'failed', label: 'Gagal', count: counts.failed }, ]; return (
Daftar permintaan ekspor laporan. Buat permintaan baru, pantau status, dan unduh berkas yang telah selesai.
| ID Laporan | Jenis Laporan | Periode | Format | Diminta Oleh | Waktu Permintaan | Status | Aksi |
|---|---|---|---|---|---|---|---|
| Tidak ada permintaan laporan pada filter ini. | |||||||
| {r.id} |
{typeLabel(r.type)}
{r.rows != null && {r.rows.toLocaleString('id-ID')} baris · {r.size} }
{r.status === 'failed' && r.failReason && {r.failReason} }
|
{r.period} | {r.format} | {r.by} | {fmtDate(r.at)} | {st.label} | {r.status === 'completed' && ( )} {(r.status === 'processing' || r.status === 'queued') && ( Menunggu... )} {r.status === 'failed' && ( )} |