119 lines
4.7 KiB
TypeScript
119 lines
4.7 KiB
TypeScript
|
|
import { createResource, createSignal, createMemo, Show } from 'solid-js';
|
||
|
|
import AdminShell from '~/components/AdminShell';
|
||
|
|
|
||
|
|
const API = '/api/gateway';
|
||
|
|
|
||
|
|
async function loadInvoices(): Promise<any[]> {
|
||
|
|
try {
|
||
|
|
const res = await fetch(`${API}/api/admin/invoices`);
|
||
|
|
if (!res.ok) throw new Error('Failed to load');
|
||
|
|
const data = await res.json();
|
||
|
|
return Array.isArray(data) ? data : (data.invoices || []);
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function InvoicePage() {
|
||
|
|
const [invoices] = createResource(loadInvoices);
|
||
|
|
const [search, setSearch] = createSignal('');
|
||
|
|
|
||
|
|
const filteredInvoices = createMemo(() => {
|
||
|
|
const q = search().toLowerCase();
|
||
|
|
const all = invoices() ?? [];
|
||
|
|
if (!q) return all;
|
||
|
|
return all.filter((inv) =>
|
||
|
|
(inv.invoice_number || inv.id || '').toLowerCase().includes(q) ||
|
||
|
|
(inv.user_id || '').toLowerCase().includes(q) ||
|
||
|
|
(inv.package_name || '').toLowerCase().includes(q) ||
|
||
|
|
(inv.status || '').toLowerCase().includes(q)
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
return (
|
||
|
|
<AdminShell>
|
||
|
|
<div class="page-actions">
|
||
|
|
<div>
|
||
|
|
<h1 class="page-title">Invoice Management</h1>
|
||
|
|
<p class="page-subtitle">View and download all platform invoices.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="margin-bottom:16px">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
placeholder="Search invoices by number, user, package, or status..."
|
||
|
|
value={search()}
|
||
|
|
onInput={(e) => setSearch(e.currentTarget.value)}
|
||
|
|
style="padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:320px"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<section class="card" style="padding: 0; overflow: hidden;">
|
||
|
|
<div class="table-wrap">
|
||
|
|
<table class="list-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Invoice #</th>
|
||
|
|
<th>User</th>
|
||
|
|
<th>Package</th>
|
||
|
|
<th>Total (₹)</th>
|
||
|
|
<th>Tax (₹)</th>
|
||
|
|
<th>Status</th>
|
||
|
|
<th>Date</th>
|
||
|
|
<th class="align-right">Actions</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<Show when={invoices.loading}>
|
||
|
|
<tr><td colspan="8" style="text-align:center;padding:32px;color:#64748b">Loading...</td></tr>
|
||
|
|
</Show>
|
||
|
|
<Show when={!invoices.loading && invoices.error}>
|
||
|
|
<tr><td colspan="8" style="text-align:center;padding:32px;color:#b91c1c">Failed to load. Is the backend running?</td></tr>
|
||
|
|
</Show>
|
||
|
|
<Show when={!invoices.loading && !invoices.error && filteredInvoices().length === 0}>
|
||
|
|
<tr><td colspan="8" style="text-align:center;padding:32px;color:#94a3b8">No records found.</td></tr>
|
||
|
|
</Show>
|
||
|
|
<Show when={!invoices.loading && !invoices.error && filteredInvoices().length > 0}>
|
||
|
|
{filteredInvoices().map((item) => (
|
||
|
|
<tr>
|
||
|
|
<td style="font-weight:600;color:#0f172a;font-family:monospace">{item.invoice_number || item.id}</td>
|
||
|
|
<td style="color:#475569">{item.user_id || '—'}</td>
|
||
|
|
<td style="color:#475569">{item.package_name || '—'}</td>
|
||
|
|
<td style="color:#475569">₹{item.total != null ? (item.total / 100).toFixed(2) : '—'}</td>
|
||
|
|
<td style="color:#475569">₹{item.tax != null ? (item.tax / 100).toFixed(2) : '—'}</td>
|
||
|
|
<td>
|
||
|
|
<span class={`status-chip ${item.status === 'PAID' || item.status === 'ISSUED' ? 'active' : ''}`}>
|
||
|
|
{item.status || '—'}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
<td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||
|
|
<td>
|
||
|
|
<div class="table-actions">
|
||
|
|
<Show when={item.download_url || item.pdf_url}>
|
||
|
|
<a
|
||
|
|
class="btn"
|
||
|
|
href={item.download_url || item.pdf_url}
|
||
|
|
target="_blank"
|
||
|
|
rel="noopener noreferrer"
|
||
|
|
download
|
||
|
|
>
|
||
|
|
Download
|
||
|
|
</a>
|
||
|
|
</Show>
|
||
|
|
<Show when={!item.download_url && !item.pdf_url}>
|
||
|
|
<span style="color:#94a3b8;font-size:12px">—</span>
|
||
|
|
</Show>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
))}
|
||
|
|
</Show>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</AdminShell>
|
||
|
|
);
|
||
|
|
}
|