Task e5133d0f: CSV export of the filtered range (#209)

This commit was merged in pull request #209.
This commit is contained in:
2026-09-23 23:33:37 -04:00
parent bc3e64b4f8
commit 75b2ac888a
4 changed files with 291 additions and 12 deletions
+38 -1
View File
@@ -3,7 +3,7 @@
* append-only event log over a closed due-date range for one filter.
*/
import { apiFetch } from "./client";
import { readJson } from "./definitions";
import { ApiRequestError, readJson } from "./definitions";
export type HistoryFilter = "all" | "reversals" | "missed";
@@ -46,3 +46,40 @@ export async function fetchHistory(
const params = new URLSearchParams({ start, end, filter });
return readJson<HistoryResponse>(await apiFetch(`${HISTORY_PATH}?${params.toString()}`));
}
export const HISTORY_CSV_PATH = "/api/v1/admin/history.csv";
export interface HistoryCsv {
blob: Blob;
filename: string;
}
/** The `filename` from a Content-Disposition header, or null. */
export function csvFilename(disposition: string | null): string | null {
const match = disposition?.match(/filename="?([^";]+)"?/i);
return match ? match[1].trim() : null;
}
/**
* The CSV export of the same range and filter as `fetchHistory`. Goes through
* apiFetch so the admin token travels in the Authorization header.
*/
export async function exportHistoryCsv(
filter: HistoryFilter,
start: string,
end: string,
): Promise<HistoryCsv> {
const params = new URLSearchParams({ start, end, filter });
const response = await apiFetch(`${HISTORY_CSV_PATH}?${params.toString()}`);
if (!response.ok) {
// readJson raises ApiRequestError with the API's `detail` for a non-OK response.
await readJson<never>(response);
throw new ApiRequestError(response.status, null);
}
return {
blob: await response.blob(),
filename:
csvFilename(response.headers.get("Content-Disposition")) ??
`nestquest-history-${start}-to-${end}.csv`,
};
}