102 lines
2.9 KiB
TypeScript
102 lines
2.9 KiB
TypeScript
|
|
import { createResource, For, Show } from 'solid-js';
|
||
|
|
import { ClipboardList } from 'lucide-solid';
|
||
|
|
import DashboardWidget from './DashboardWidget';
|
||
|
|
import type { RoleKey } from '../RoleDashboardShared';
|
||
|
|
|
||
|
|
const API = '/api/gateway';
|
||
|
|
|
||
|
|
async function apiFetch(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 ?? {}),
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
type Props = {
|
||
|
|
roleKey: RoleKey;
|
||
|
|
};
|
||
|
|
|
||
|
|
async function fetchRequirementsData(_roleKey: RoleKey) {
|
||
|
|
const res = await apiFetch('/api/customers/requirements?page=1&limit=100');
|
||
|
|
if (!res.ok) return null;
|
||
|
|
const json = await res.json();
|
||
|
|
return Array.isArray(json?.data) ? json.data : [];
|
||
|
|
}
|
||
|
|
|
||
|
|
function ReqStatRow(props: { label: string; value: number | string; color?: string }) {
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: 'flex',
|
||
|
|
'align-items': 'center',
|
||
|
|
'justify-content': 'space-between',
|
||
|
|
padding: '6px 0',
|
||
|
|
'border-bottom': '1px solid #F9FAFB',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<span style={{ 'font-size': '12px', color: '#6B7280' }}>{props.label}</span>
|
||
|
|
<span
|
||
|
|
style={{
|
||
|
|
'font-size': '15px',
|
||
|
|
'font-weight': '800',
|
||
|
|
color: props.color || '#111827',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{props.value}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function RequirementsWidget(props: Props) {
|
||
|
|
const [data] = createResource(() => props.roleKey, fetchRequirementsData);
|
||
|
|
|
||
|
|
const stats = () => {
|
||
|
|
const items = data() || [];
|
||
|
|
const total = items.length;
|
||
|
|
const open = items.filter(
|
||
|
|
(r: any) => String(r.status || '').toUpperCase() === 'OPEN'
|
||
|
|
).length;
|
||
|
|
const pending = items.filter((r: any) =>
|
||
|
|
String(r.status || '').toUpperCase().includes('PENDING')
|
||
|
|
).length;
|
||
|
|
const closed = items.filter(
|
||
|
|
(r: any) => String(r.status || '').toUpperCase() === 'CLOSED'
|
||
|
|
).length;
|
||
|
|
const draft = items.filter(
|
||
|
|
(r: any) => String(r.status || '').toUpperCase() === 'DRAFT'
|
||
|
|
).length;
|
||
|
|
return { total, open, pending, closed, draft };
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<DashboardWidget
|
||
|
|
title="My Requirements"
|
||
|
|
loading={data.loading}
|
||
|
|
error={data.error ? 'Failed to load' : undefined}
|
||
|
|
icon={<ClipboardList size={16} />}
|
||
|
|
>
|
||
|
|
<Show when={stats()}>
|
||
|
|
<For each={[
|
||
|
|
['Total Requirements', stats()!.total, '#374151'],
|
||
|
|
['Open', stats()!.open, '#059669'],
|
||
|
|
['In Verification', stats()!.pending, '#D97706'],
|
||
|
|
['Closed', stats()!.closed, '#6B7280'],
|
||
|
|
]}>
|
||
|
|
{([label, value, color]) => <ReqStatRow label={label} value={value} color={color} />}
|
||
|
|
</For>
|
||
|
|
</Show>
|
||
|
|
</DashboardWidget>
|
||
|
|
);
|
||
|
|
}
|