228 lines
7.1 KiB
TypeScript
228 lines
7.1 KiB
TypeScript
import { For, Show, createMemo, createSignal, onMount } from 'solid-js';
|
|
import { CARD, BTN_PRIMARY, BTN_GHOST } from '~/components/DashboardShell';
|
|
|
|
const API = '/api/gateway';
|
|
|
|
type AdminMetrics = {
|
|
totalUsers: number;
|
|
pendingVerifications: number;
|
|
activeSessions: number;
|
|
totalRoles: number;
|
|
totalPhotographers: number;
|
|
totalCustomers: number;
|
|
totalCompanies: number;
|
|
totalJobSeekers: number;
|
|
};
|
|
|
|
async function adminFetch(path: string, opts?: RequestInit) {
|
|
const token =
|
|
typeof window !== 'undefined'
|
|
? window.sessionStorage.getItem('nxtgauge_access_token') || ''
|
|
: '';
|
|
const cleanPath = path.startsWith('/api/') ? path.slice(4) : path;
|
|
return fetch(`${API}${cleanPath}`, {
|
|
...opts,
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...(opts?.headers ?? {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
export default function AdminDashboardPage() {
|
|
const [metrics, setMetrics] = createSignal<AdminMetrics>({
|
|
totalUsers: 0,
|
|
pendingVerifications: 0,
|
|
activeSessions: 0,
|
|
totalRoles: 0,
|
|
totalPhotographers: 0,
|
|
totalCustomers: 0,
|
|
totalCompanies: 0,
|
|
totalJobSeekers: 0,
|
|
});
|
|
const [loading, setLoading] = createSignal(true);
|
|
const [error, setError] = createSignal('');
|
|
|
|
const loadAdminMetrics = async () => {
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
const [usersRes, rolesRes, photographersRes, customersRes, companiesRes, jobSeekersRes] =
|
|
await Promise.all([
|
|
adminFetch('/api/admin/users?page=1&limit=1'),
|
|
adminFetch('/api/admin/roles'),
|
|
adminFetch('/api/admin/users?role=photographer&page=1&limit=1'),
|
|
adminFetch('/api/admin/users?role=customer&page=1&limit=1'),
|
|
adminFetch('/api/admin/users?role=company&page=1&limit=1'),
|
|
adminFetch('/api/admin/users?role=jobseeker&page=1&limit=1'),
|
|
]);
|
|
|
|
const usersJson = await usersRes.json().catch(() => ({}));
|
|
const rolesJson = await rolesRes.json().catch(() => ({}));
|
|
const photographersJson = await photographersRes.json().catch(() => ({}));
|
|
const customersJson = await customersRes.json().catch(() => ({}));
|
|
const companiesJson = await companiesRes.json().catch(() => ({}));
|
|
const jobSeekersJson = await jobSeekersRes.json().catch(() => ({}));
|
|
|
|
setMetrics({
|
|
totalUsers: usersJson?.total ?? usersJson?.count ?? 0,
|
|
pendingVerifications: 0,
|
|
activeSessions: 0,
|
|
totalRoles: Array.isArray(rolesJson) ? rolesJson.length : 0,
|
|
totalPhotographers: photographersJson?.total ?? photographersJson?.count ?? 0,
|
|
totalCustomers: customersJson?.total ?? customersJson?.count ?? 0,
|
|
totalCompanies: companiesJson?.total ?? companiesJson?.count ?? 0,
|
|
totalJobSeekers: jobSeekersJson?.total ?? jobSeekersJson?.count ?? 0,
|
|
});
|
|
} catch (e: any) {
|
|
setError('Failed to load admin metrics: ' + e.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
onMount(loadAdminMetrics);
|
|
|
|
const statCards = createMemo(() => [
|
|
{ label: 'Total Users', value: metrics().totalUsers, color: '#0D0D2A' },
|
|
{ label: 'Photographers', value: metrics().totalPhotographers, color: '#FF5E13' },
|
|
{ label: 'Customers', value: metrics().totalCustomers, color: '#059669' },
|
|
{ label: 'Companies', value: metrics().totalCompanies, color: '#7C3AED' },
|
|
{ label: 'Job Seekers', value: metrics().totalJobSeekers, color: '#DC2626' },
|
|
{ label: 'Pending Verifications', value: metrics().pendingVerifications, color: '#D97706' },
|
|
]);
|
|
|
|
return (
|
|
<div style={{ display: 'grid', gap: '14px', 'max-width': '1200px' }}>
|
|
<div style={CARD}>
|
|
<p style={{ margin: '0', 'font-size': '18px', 'font-weight': '800', color: '#111827' }}>
|
|
Admin Dashboard
|
|
</p>
|
|
<p style={{ margin: '4px 0 0', 'font-size': '13px', color: '#6B7280' }}>
|
|
Platform overview and management metrics.
|
|
</p>
|
|
</div>
|
|
|
|
<Show when={error()}>
|
|
<div
|
|
style={{
|
|
...CARD,
|
|
border: '1px solid #FECACA',
|
|
background: '#FEF2F2',
|
|
padding: '12px 14px',
|
|
color: '#B91C1C',
|
|
'font-size': '13px',
|
|
'font-weight': '600',
|
|
}}
|
|
>
|
|
{error()}
|
|
</div>
|
|
</Show>
|
|
|
|
<Show when={loading()}>
|
|
<div style={{ ...CARD, 'text-align': 'center', color: '#9CA3AF' }}>
|
|
Loading admin metrics...
|
|
</div>
|
|
</Show>
|
|
|
|
<Show when={!loading()}>
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
'grid-template-columns': 'repeat(3, minmax(0, 1fr))',
|
|
gap: '14px',
|
|
}}
|
|
>
|
|
<For each={statCards()}>
|
|
{(stat) => (
|
|
<div
|
|
style={{
|
|
border: '1px solid #E5E7EB',
|
|
background: 'white',
|
|
'border-radius': '16px',
|
|
padding: '16px',
|
|
'box-shadow': '0 1px 4px rgba(0,0,0,0.06)',
|
|
}}
|
|
>
|
|
<p
|
|
style={{
|
|
margin: '0',
|
|
'font-size': '11px',
|
|
'letter-spacing': '0.06em',
|
|
'text-transform': 'uppercase',
|
|
color: '#6B7280',
|
|
}}
|
|
>
|
|
{stat.label}
|
|
</p>
|
|
<p
|
|
style={{
|
|
margin: '8px 0 0',
|
|
'font-size': '32px',
|
|
'font-weight': '800',
|
|
color: stat.color,
|
|
}}
|
|
>
|
|
{stat.value}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</For>
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
border: '1px solid #E5E7EB',
|
|
background: 'white',
|
|
'border-radius': '16px',
|
|
padding: '16px',
|
|
'box-shadow': '0 1px 4px rgba(0,0,0,0.06)',
|
|
}}
|
|
>
|
|
<p
|
|
style={{
|
|
margin: '0 0 12px',
|
|
'font-size': '16px',
|
|
'font-weight': '700',
|
|
color: '#111827',
|
|
}}
|
|
>
|
|
Quick Actions
|
|
</p>
|
|
<div style={{ display: 'flex', gap: '10px', 'flex-wrap': 'wrap' }}>
|
|
<button
|
|
type="button"
|
|
onClick={() => (window.location.href = '/admin/users')}
|
|
style={BTN_PRIMARY}
|
|
>
|
|
Manage Users
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => (window.location.href = '/admin/verifications')}
|
|
style={BTN_GHOST}
|
|
>
|
|
Pending Verifications
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => (window.location.href = '/admin/roles')}
|
|
style={BTN_GHOST}
|
|
>
|
|
Manage Roles
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={loadAdminMetrics}
|
|
style={BTN_GHOST}
|
|
>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Show>
|
|
</div>
|
|
);
|
|
}
|