feat: enhance operations dashboard and reporting controls
This commit is contained in:
@@ -194,6 +194,8 @@ export const adminGovernanceApi = {
|
||||
listDrainageFields: () => request<DictionaryItem[]>('/admin/dictionaries/drainage-fields'),
|
||||
createDrainageField: (body: { code: string; name: string; fieldType: 'string' | 'image' | 'file'; required?: boolean; status?: string; description?: string }) =>
|
||||
request<DictionaryItem>('/admin/dictionaries/drainage-fields', { method: 'POST', body: JSON.stringify(body) }),
|
||||
updateDrainageField: (id: string, body: { code: string; name: string; fieldType: 'string' | 'image' | 'file'; description?: string }) =>
|
||||
request<DictionaryItem>(`/admin/dictionaries/drainage-fields/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
|
||||
deleteDrainageField: (id: string) => request<DictionaryItem>(`/admin/dictionaries/drainage-fields/${id}`, { method: 'DELETE' }),
|
||||
listDrainageDetectionRules: (query: { keyword?: string; status?: string } = {}) =>
|
||||
request<DrainageDetectionRule[]>(withQuery('/admin/dictionaries/drainage-detection-rules', query)),
|
||||
@@ -208,6 +210,11 @@ export const adminGovernanceApi = {
|
||||
listCommonReportFields: () => request<CommonReportField[]>('/admin/dictionaries/common-report-fields'),
|
||||
createCommonReportField: (body: { drainageFieldId: string; reportType: 'signature' | 'drainage'; required: boolean; sortOrder?: number }) =>
|
||||
request<CommonReportField>('/admin/dictionaries/common-report-fields', { method: 'POST', body: JSON.stringify(body) }),
|
||||
reorderCommonReportFields: (body: { reportType: 'signature' | 'drainage'; ids: string[] }) =>
|
||||
request<CommonReportField[]>('/admin/dictionaries/common-report-fields/order', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
deleteCommonReportField: (id: string) => request<CommonReportField>(`/admin/dictionaries/common-report-fields/${id}`, { method: 'DELETE' }),
|
||||
updateCommonReportField: (id: string, body: { drainageFieldId: string; reportType: 'signature' | 'drainage'; required: boolean }) =>
|
||||
request<CommonReportField>(`/admin/dictionaries/common-report-fields/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
|
||||
|
||||
@@ -140,6 +140,23 @@ describe('request tenant and error boundaries', () => {
|
||||
await expect(requestBlob('/client/file-fail')).rejects.toThrow('文件不存在');
|
||||
});
|
||||
|
||||
it('marks string-body blob requests as JSON so download endpoints receive their payload', async () => {
|
||||
let receivedContentType = '';
|
||||
let receivedBody: unknown;
|
||||
server.use(
|
||||
http.post('http://localhost/api/admin/report-export', async ({ request: incoming }) => {
|
||||
receivedContentType = incoming.headers.get('content-type') ?? '';
|
||||
receivedBody = await incoming.json();
|
||||
return new HttpResponse('xlsx-data', { status: 200 });
|
||||
}),
|
||||
);
|
||||
await expect(
|
||||
(await requestBlob('/admin/report-export', { method: 'POST', body: JSON.stringify({ reportType: 'signature' }) })).text(),
|
||||
).resolves.toBe('xlsx-data');
|
||||
expect(receivedContentType).toContain('application/json');
|
||||
expect(receivedBody).toEqual({ reportType: 'signature' });
|
||||
});
|
||||
|
||||
it('retries blob downloads after recent authentication and supports admin tenant selection', async () => {
|
||||
writeSession({
|
||||
portal: 'admin',
|
||||
|
||||
@@ -114,6 +114,9 @@ export async function request<T>(path: string, options: RequestOptions = {}): Pr
|
||||
|
||||
export async function requestBlob(path: string, options: RequestOptions = {}): Promise<Blob> {
|
||||
const headers = new Headers(options.headers);
|
||||
if (typeof options.body === 'string' && !headers.has('Content-Type')) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
const portal = requestPortal(path);
|
||||
const session = portal ? readSession(portal) : null;
|
||||
if (session && hasRecentUserActivity()) headers.set('x-session-activity', 'user');
|
||||
|
||||
@@ -152,6 +152,12 @@ export type DashboardResponse = {
|
||||
spendCents: number;
|
||||
returnedCents: number;
|
||||
billingUnits: number;
|
||||
segmentCount: number;
|
||||
deliveredSegmentCount: number;
|
||||
arrivalRate: number;
|
||||
billedCents: number;
|
||||
profitCents: number;
|
||||
profitRate: number;
|
||||
};
|
||||
uplinkCount: number;
|
||||
billing: { _count: { _all: number }; _sum: { amountCents?: number | null; billingUnits?: number | null } };
|
||||
|
||||
Reference in New Issue
Block a user