Add admin dashboard filtering and sorting
Provide stable server-side list queries and focused chart controls so operators can inspect large datasets without misleading partial-page ordering.
This commit is contained in:
+41
-19
@@ -4,16 +4,21 @@ import type {
|
||||
AdminOperatorProvisioning,
|
||||
AdminSecuritySummary,
|
||||
AdminLoginResponse,
|
||||
AuditQuery,
|
||||
AuditLogEntry,
|
||||
CreditGrantRequest,
|
||||
CreditGrantResponse,
|
||||
LedgerQuery,
|
||||
LedgerEntry,
|
||||
OperatorsQuery,
|
||||
Overview,
|
||||
PageResult,
|
||||
ProductAnalyticsOverview,
|
||||
ReferralsQuery,
|
||||
ReferralOverview,
|
||||
SessionResponse,
|
||||
UserDetail,
|
||||
UsersQuery,
|
||||
UserSummary,
|
||||
} from "./types";
|
||||
|
||||
@@ -140,15 +145,21 @@ async function request<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function query(params: Record<string, string | undefined>): string {
|
||||
export function encodeQuery<T extends object>(params: T): string {
|
||||
const search = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value) search.set(key, value);
|
||||
if (value !== undefined && value !== "") search.set(key, String(value));
|
||||
});
|
||||
const result = search.toString();
|
||||
return result ? `?${result}` : "";
|
||||
}
|
||||
|
||||
function cursorQuery<T extends CursorQuery>(value?: string | T): CursorQuery | T {
|
||||
return typeof value === "string" ? { cursor: value } : (value ?? {});
|
||||
}
|
||||
|
||||
type CursorQuery = { cursor?: string };
|
||||
|
||||
export const adminApi = {
|
||||
session: () => request<SessionResponse>("/auth/session"),
|
||||
|
||||
@@ -161,28 +172,35 @@ export const adminApi = {
|
||||
logout: () => request<void>("/auth/logout", { method: "POST" }),
|
||||
|
||||
overview: (range: string) =>
|
||||
request<Overview>(`/overview${query({ range })}`),
|
||||
request<Overview>(`/overview${encodeQuery({ range })}`),
|
||||
|
||||
referrals: (range: string) =>
|
||||
request<ReferralOverview>(`/referrals${query({ range })}`),
|
||||
referrals: (value: string | ReferralsQuery) => {
|
||||
const params = typeof value === "string" ? { range: value } : value;
|
||||
return request<ReferralOverview>(`/referrals${encodeQuery(params)}`);
|
||||
},
|
||||
|
||||
productAnalytics: (range: string) =>
|
||||
request<ProductAnalyticsOverview>(`/analytics${query({ range })}`),
|
||||
request<ProductAnalyticsOverview>(`/analytics${encodeQuery({ range })}`),
|
||||
|
||||
users: (search = "", cursor?: string) =>
|
||||
request<PageResult<UserSummary>>(
|
||||
`/users${query({ q: search.trim(), cursor })}`,
|
||||
),
|
||||
users: (value: string | UsersQuery = "", legacyCursor?: string) => {
|
||||
const params =
|
||||
typeof value === "string"
|
||||
? { q: value.trim(), cursor: legacyCursor }
|
||||
: { ...value, q: value.q?.trim() };
|
||||
return request<PageResult<UserSummary>>(`/users${encodeQuery(params)}`);
|
||||
},
|
||||
|
||||
user: (userId: string) =>
|
||||
request<UserDetail>(`/users/${encodeURIComponent(userId)}`),
|
||||
|
||||
latestLedger: (cursor?: string) =>
|
||||
request<PageResult<LedgerEntry>>(`/credits/ledger${query({ cursor })}`),
|
||||
|
||||
ledger: (userId: string, cursor?: string) =>
|
||||
latestLedger: (value?: string | LedgerQuery) =>
|
||||
request<PageResult<LedgerEntry>>(
|
||||
`/users/${encodeURIComponent(userId)}/ledger${query({ cursor })}`,
|
||||
`/credits/ledger${encodeQuery(cursorQuery(value))}`,
|
||||
),
|
||||
|
||||
ledger: (userId: string, value?: string | LedgerQuery) =>
|
||||
request<PageResult<LedgerEntry>>(
|
||||
`/users/${encodeURIComponent(userId)}/ledger${encodeQuery(cursorQuery(value))}`,
|
||||
),
|
||||
|
||||
grantCredits: (payload: CreditGrantRequest) =>
|
||||
@@ -196,11 +214,15 @@ export const adminApi = {
|
||||
headers: { "Idempotency-Key": payload.idempotencyKey },
|
||||
}),
|
||||
|
||||
auditLogs: (cursor?: string) =>
|
||||
request<PageResult<AuditLogEntry>>(`/audit${query({ cursor })}`),
|
||||
auditLogs: (value?: string | AuditQuery) =>
|
||||
request<PageResult<AuditLogEntry>>(
|
||||
`/audit${encodeQuery(cursorQuery(value))}`,
|
||||
),
|
||||
|
||||
operators: (cursor?: string) =>
|
||||
request<PageResult<AdminOperator>>(`/operators${query({ cursor })}`),
|
||||
operators: (value?: string | OperatorsQuery) =>
|
||||
request<PageResult<AdminOperator>>(
|
||||
`/operators${encodeQuery(cursorQuery(value))}`,
|
||||
),
|
||||
|
||||
operatorSummary: () =>
|
||||
request<AdminSecuritySummary>("/operators/summary"),
|
||||
|
||||
Reference in New Issue
Block a user