Improve soundbox ops dashboard and registry editing

This commit is contained in:
Wira Basalamah
2026-06-08 15:56:12 +07:00
parent 836eb7db85
commit 67dc286c1a
18 changed files with 768 additions and 120 deletions

View File

@ -178,7 +178,7 @@
</div>
<div class="flex items-center gap-6">
<div class="relative">
<input class="bg-slate-100 border-none rounded-full px-4 py-2 text-body-md w-64 focus:ring-2 focus:ring-primary/20" placeholder="Search transactions..." type="text"/>
<input id="merchant-settlement-search-input" class="bg-slate-100 border-none rounded-full px-4 py-2 text-body-md w-64 focus:ring-2 focus:ring-primary/20" placeholder="Search batch, status, or account..." type="text"/>
<span class="material-symbols-outlined absolute right-3 top-2 text-slate-500" data-icon="search">search</span>
</div>
<div class="flex items-center gap-4">
@ -608,6 +608,7 @@
const MerchantSettlementUI = (() => {
const api = window.MerchantUIAPI;
const rowsEl = document.getElementById('merchant-settlement-rows');
const searchInput = document.getElementById('merchant-settlement-search-input');
const statusFilter = document.getElementById('merchant-settlement-status-filter');
const summaryEl = document.getElementById('merchant-settlement-summary');
const downloadBtn = document.getElementById('drawer-download-report');
@ -628,15 +629,32 @@
return 'bg-warning/10 text-warning';
};
const statusLabel = (status) => status === 'created' ? 'PENDING' : String(status || '-').toUpperCase();
let loadedBatches = [];
let loadedMerchant = null;
const renderRows = (batches, merchant) => {
if (!rowsEl) return;
if (!batches.length) {
rowsEl.innerHTML = '<tr><td colspan="7" class="px-6 py-10 text-center text-slate-500">No settlement batches available.</td></tr>';
if (summaryEl) summaryEl.textContent = 'Showing 0 disbursements';
const query = String(searchInput?.value || '').trim().toLowerCase();
const filtered = (batches || []).filter((batch) => {
const haystack = [
batch.id,
batch.batch_code,
batch.status,
batch.failure_reason,
merchant?.settlement_account_reference,
batch.metadata_json?.paid_reference,
batch.metadata_json?.paid_note,
batch.gross_amount,
batch.net_payable_amount
].filter(Boolean).join(' ').toLowerCase();
return !query || haystack.includes(query);
});
if (!filtered.length) {
rowsEl.innerHTML = `<tr><td colspan="7" class="px-6 py-10 text-center text-slate-500">${query ? 'No disbursements matched the search.' : 'No settlement batches available.'}</td></tr>`;
if (summaryEl) summaryEl.textContent = query ? `No results for "${searchInput.value.trim()}"` : 'Showing 0 disbursements';
return;
}
rowsEl.innerHTML = batches.map((batch) => `
rowsEl.innerHTML = filtered.map((batch) => `
<tr class="hover:bg-slate-50 transition-colors h-row-height group cursor-pointer" data-batch-id="${batch.id}">
<td class="px-6 font-label-md text-label-md font-semibold text-primary">${batch.batch_code}</td>
<td class="px-6 font-body-md text-body-md text-on-surface">${dt(batch.paid_at || batch.created_at)}</td>
@ -655,7 +673,7 @@
rowsEl.querySelectorAll('tr[data-batch-id]').forEach((row) => {
row.addEventListener('click', () => openDrawer(row.dataset.batchId, merchant));
});
if (summaryEl) summaryEl.textContent = `Showing ${batches.length} disbursement(s)`;
if (summaryEl) summaryEl.textContent = `Showing ${filtered.length} of ${batches.length} disbursement(s)`;
};
const renderEvents = (events) => {
@ -729,6 +747,10 @@
const load = async () => {
api.requireSession();
const initialQuery = new URLSearchParams(window.location.search).get('q') || '';
if (initialQuery && searchInput && !searchInput.value) {
searchInput.value = initialQuery;
}
const [profile, summary, batches] = await Promise.all([
api.getProfile(),
api.getSettlementSummary(),
@ -746,10 +768,19 @@
setText('merchant-adjustment-amount', `Adj ${money(summary.adjustment_amount || 0)}`);
setText('merchant-next-payout-amount', money(summary.pending_amount));
setText('merchant-next-payout-date', Number(summary.created_batches || 0) > 0 ? 'On next payout run' : '-');
renderRows(batches || [], merchant);
loadedBatches = batches || [];
loadedMerchant = merchant;
renderRows(loadedBatches, loadedMerchant);
};
statusFilter?.addEventListener('change', load);
searchInput?.addEventListener('input', () => renderRows(loadedBatches, loadedMerchant));
searchInput?.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && searchInput.value) {
searchInput.value = '';
renderRows(loadedBatches, loadedMerchant);
}
});
downloadBtn?.addEventListener('click', downloadActiveCsv);
logoutBtn?.addEventListener('click', () => {
api.clearSession();