nxtgauge-admin-solid/src/routes/admin/report.tsx

121 lines
5 KiB
TypeScript
Raw Normal View History

import { createSignal, Show } from 'solid-js';
import AdminShell from '~/components/AdminShell';
const API = '/api/gateway';
type UserReport = {
total_users?: number;
new_users?: number;
active_users?: number;
};
type RevenueReport = {
total_revenue?: number;
total_orders?: number;
total_tracecoins_sold?: number;
};
export default function ReportPage() {
const [from, setFrom] = createSignal('');
const [to, setTo] = createSignal('');
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal('');
const [userReport, setUserReport] = createSignal<UserReport | null>(null);
const [revenueReport, setRevenueReport] = createSignal<RevenueReport | null>(null);
const handleLoad = async (e: Event) => {
e.preventDefault();
if (!from() || !to()) return;
try {
setLoading(true);
setError('');
const [usersRes, revenueRes] = await Promise.all([
fetch(`${API}/api/admin/reports/users?from=${from()}&to=${to()}`),
fetch(`${API}/api/admin/reports/revenue?from=${from()}&to=${to()}`),
]);
if (!usersRes.ok || !revenueRes.ok) throw new Error('Failed to load report data');
const [usersData, revenueData] = await Promise.all([usersRes.json(), revenueRes.json()]);
setUserReport(usersData);
setRevenueReport(revenueData);
} catch (err: any) {
setError(err.message || 'Failed to load reports');
} finally {
setLoading(false);
}
};
return (
<AdminShell>
<div class="page-actions">
<div>
<h1 class="page-title">Report Management</h1>
<p class="page-subtitle">View platform analytics and generate reports.</p>
</div>
</div>
<section class="card" style="margin-bottom:16px">
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">Date Range</h2>
<form onSubmit={handleLoad} style="display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap">
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">From</label>
<input
type="date"
value={from()}
onInput={(e) => setFrom(e.currentTarget.value)}
required
style="padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
/>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">To</label>
<input
type="date"
value={to()}
onInput={(e) => setTo(e.currentTarget.value)}
required
style="padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
/>
</div>
<button class="btn navy" type="submit" disabled={loading()}>
{loading() ? 'Loading...' : 'Load Report'}
</button>
</form>
<Show when={error()}>
<div class="error-box" style="margin-top:12px">{error()}</div>
</Show>
</section>
<Show when={userReport() || revenueReport()}>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:16px">
<div class="card" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Users</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.total_users ?? '—'}</p>
</div>
<div class="card" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">New Users</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.new_users ?? '—'}</p>
</div>
<div class="card" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Active Users</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.active_users ?? '—'}</p>
</div>
<div class="card" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Revenue</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">
{revenueReport()?.total_revenue != null ? `${(revenueReport()!.total_revenue! / 100).toFixed(2)}` : ''}
</p>
</div>
<div class="card" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Orders</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_orders ?? '—'}</p>
</div>
<div class="card" style="text-align:center">
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">TraceCoins Sold</p>
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_tracecoins_sold ?? '—'}</p>
</div>
</div>
</Show>
</AdminShell>
);
}