feat: add frontend manual extraction flow with review screen (refs #136)

- Create useManualExtraction hook: submit PDF to OCR, poll job status, track progress
- Create useCreateSchedulesFromExtraction hook: batch create maintenance schedules from extraction
- Create MaintenanceScheduleReviewScreen: dialog with checkboxes, inline editing, batch create
- Update DocumentForm: remove "(Coming soon)", trigger extraction after upload, show progress
- Add 12 unit tests for review screen (rendering, selection, empty state, errors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-11 10:48:46 -06:00
parent a281cea9c5
commit 40df5e5b58
5 changed files with 863 additions and 6 deletions

View File

@@ -0,0 +1,41 @@
/**
* @ai-summary Hook for batch-creating maintenance schedules from manual extraction results
* @ai-context Maps extracted MaintenanceScheduleItem[] to CreateScheduleRequest[] and creates via API
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { maintenanceApi } from '../api/maintenance.api';
import type { CreateScheduleRequest, MaintenanceScheduleResponse } from '../types/maintenance.types';
import type { MaintenanceScheduleItem } from '../../documents/hooks/useManualExtraction';
interface CreateSchedulesParams {
vehicleId: string;
items: MaintenanceScheduleItem[];
}
export function useCreateSchedulesFromExtraction() {
const queryClient = useQueryClient();
return useMutation<MaintenanceScheduleResponse[], Error, CreateSchedulesParams>({
mutationFn: async ({ vehicleId, items }) => {
const results: MaintenanceScheduleResponse[] = [];
for (const item of items) {
const request: CreateScheduleRequest = {
vehicleId,
category: 'routine_maintenance',
subtypes: item.subtypes.length > 0 ? item.subtypes : [],
scheduleType: 'interval',
intervalMiles: item.intervalMiles ?? undefined,
intervalMonths: item.intervalMonths ?? undefined,
};
const created = await maintenanceApi.createSchedule(request);
results.push(created);
}
return results;
},
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: ['maintenanceSchedules', variables.vehicleId] });
queryClient.invalidateQueries({ queryKey: ['maintenanceUpcoming', variables.vehicleId] });
},
});
}