feat(scholars): add multiselect and bulk actions (delete, toggle, export)

Add Set<number>-based selection state to ScholarsPage with checkboxes
in both table and card views, select-all toggle, and stale-key pruning.

Backend: POST /scholars/bulk-delete, POST /scholars/bulk-toggle, and
GET /scholars/export?ids= filter. Service functions use user-scoped
queries. Structured logging for bulk operations.

Frontend: useScholarBulkActions composable extracts selection and bulk
action logic. AppSelect-based action dropdown with dynamic options.
Delete prompts window.confirm; enable/disable applies immediately.
Export downloads filtered JSON.

Includes integration tests for all bulk endpoints and frontend unit
tests for selection toggle, select-all, and stale pruning.
This commit is contained in:
Justin Visser 2026-03-07 14:03:06 +01:00
parent 61ac6f206f
commit 6742eff773
9 changed files with 730 additions and 79 deletions

View file

@ -160,8 +160,30 @@ export async function clearScholarImage(
return response.data;
}
export async function exportScholarData(): Promise<DataExportPayload> {
const response = await apiRequest<DataExportPayload>("/scholars/export", {
export interface BulkCountResult {
deleted_count: number;
updated_count: number;
}
export async function bulkDeleteScholars(scholarProfileIds: number[]): Promise<BulkCountResult> {
const response = await apiRequest<BulkCountResult>("/scholars/bulk-delete", {
method: "POST",
body: { scholar_profile_ids: scholarProfileIds },
});
return response.data;
}
export async function bulkToggleScholars(scholarProfileIds: number[], isEnabled: boolean): Promise<BulkCountResult> {
const response = await apiRequest<BulkCountResult>("/scholars/bulk-toggle", {
method: "POST",
body: { scholar_profile_ids: scholarProfileIds, is_enabled: isEnabled },
});
return response.data;
}
export async function exportScholarData(ids?: number[]): Promise<DataExportPayload> {
const params = ids && ids.length > 0 ? `?ids=${ids.join(",")}` : "";
const response = await apiRequest<DataExportPayload>(`/scholars/export${params}`, {
method: "GET",
});
return response.data;