Add wallet invoices page
- wallet/invoices.tsx: table of invoices with download link; uses role-specific API prefix; handles loading/empty states Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f5d294abbf
commit
64ec515393
1 changed files with 120 additions and 0 deletions
120
src/routes/dashboard/wallet/invoices.tsx
Normal file
120
src/routes/dashboard/wallet/invoices.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import { createResource, createSignal, Show, For } from 'solid-js';
|
||||
import { A } from '@solidjs/router';
|
||||
import { getAuthHeader, authState, getRoleApiPath } from '~/lib/auth';
|
||||
|
||||
const API = import.meta.env.VITE_API_URL ?? 'http://localhost:8000';
|
||||
|
||||
const STATUS_BADGE: Record<string, string> = {
|
||||
ISSUED: 'badge--blue',
|
||||
PAID: 'badge--green',
|
||||
};
|
||||
|
||||
export default function WalletInvoices() {
|
||||
const [page, setPage] = createSignal(1);
|
||||
|
||||
const rc = () => authState().runtime_config;
|
||||
const rolePrefix = () => getRoleApiPath(rc()?.role);
|
||||
|
||||
const [invoices] = createResource(() => page(), async (p) => {
|
||||
const auth = getAuthHeader();
|
||||
if (!auth.Authorization || !rc()?.role) return { data: [], pagination: null };
|
||||
const res = await fetch(`${API}${rolePrefix()}/invoices?page=${p}&limit=20`, { headers: auth });
|
||||
if (!res.ok) return { data: [], pagination: null };
|
||||
return res.json();
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ 'max-width': '900px' }}>
|
||||
<div style={{ 'margin-bottom': '24px', display: 'flex', 'align-items': 'center', 'justify-content': 'space-between' }}>
|
||||
<div>
|
||||
<h1 style={{ margin: 0, 'font-size': '22px', 'font-weight': '800' }}>Invoices</h1>
|
||||
<p style={{ margin: '6px 0 0', color: '#64748b', 'font-size': '14px' }}>
|
||||
Download receipts for all your purchases.
|
||||
</p>
|
||||
</div>
|
||||
<A href="/dashboard/wallet" class="btn" style={{ 'text-decoration': 'none' }}>← Wallet</A>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<Show when={invoices.loading}>
|
||||
<div class="loading-spinner">Loading invoices…</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!invoices.loading && (invoices()?.data?.length ?? 0) === 0}>
|
||||
<div style={{ padding: '48px', 'text-align': 'center', color: '#64748b' }}>
|
||||
<div style={{ 'font-size': '36px', 'margin-bottom': '12px' }}>🧾</div>
|
||||
<p>No invoices yet. Invoices are generated after a successful payment.</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={(invoices()?.data?.length ?? 0) > 0}>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice #</th>
|
||||
<th>Date</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={invoices()?.data}>
|
||||
{(inv: any) => (
|
||||
<tr>
|
||||
<td style={{ 'font-family': 'monospace', 'font-size': '13px', 'font-weight': '600' }}>
|
||||
{inv.invoice_number ?? '—'}
|
||||
</td>
|
||||
<td style={{ 'font-size': '13px' }}>
|
||||
{inv.issued_at ? new Date(inv.issued_at).toLocaleDateString('en-IN', { dateStyle: 'medium' }) : '—'}
|
||||
</td>
|
||||
<td style={{ 'font-weight': '600' }}>
|
||||
₹{((inv.total ?? 0) / 100).toLocaleString('en-IN', { minimumFractionDigits: 2 })}
|
||||
</td>
|
||||
<td>
|
||||
<span class={`badge ${STATUS_BADGE[inv.status] ?? 'badge--gray'}`}>
|
||||
{inv.status ?? '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ 'text-align': 'right' }}>
|
||||
<Show when={inv.file_url}>
|
||||
<a
|
||||
href={inv.file_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm"
|
||||
style={{ 'text-decoration': 'none' }}
|
||||
>
|
||||
Download PDF
|
||||
</a>
|
||||
</Show>
|
||||
<Show when={!inv.file_url}>
|
||||
<span style={{ 'font-size': '12px', color: '#94a3b8' }}>Generating…</span>
|
||||
</Show>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={invoices()?.pagination?.total_pages > 1}>
|
||||
<div style={{ display: 'flex', gap: '8px', 'margin-top': '20px', 'justify-content': 'center' }}>
|
||||
<button class="btn btn-sm" disabled={page() === 1} onClick={() => setPage(p => p - 1)}>← Prev</button>
|
||||
<span style={{ padding: '6px 12px', 'font-size': '13px', color: '#64748b' }}>
|
||||
Page {page()} of {invoices()?.pagination?.total_pages}
|
||||
</span>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
disabled={page() >= invoices()?.pagination?.total_pages}
|
||||
onClick={() => setPage(p => p + 1)}
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue