From 33b489d526d0c53c99086a97077659087871efc5 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:29:33 -0600 Subject: [PATCH] fix: Update auto schedule creation --- frontend/.claude/tdd-guard/data/test.json | 24 ++++++- .../MaintenanceScheduleReviewScreen.test.tsx | 70 ++++++++++++++++++- .../MaintenanceScheduleReviewScreen.tsx | 49 ++++++++++--- .../hooks/useCreateSchedulesFromExtraction.ts | 3 +- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/frontend/.claude/tdd-guard/data/test.json b/frontend/.claude/tdd-guard/data/test.json index bf2a3fc..a9ae213 100644 --- a/frontend/.claude/tdd-guard/data/test.json +++ b/frontend/.claude/tdd-guard/data/test.json @@ -19,8 +19,8 @@ "state": "passed" }, { - "name": "should display subtype chips", - "fullName": "MaintenanceScheduleReviewScreen Rendering should display subtype chips", + "name": "should display subtypes in SubtypeCheckboxGroup", + "fullName": "MaintenanceScheduleReviewScreen Rendering should display subtypes in SubtypeCheckboxGroup", "state": "passed" }, { @@ -68,6 +68,26 @@ "fullName": "MaintenanceScheduleReviewScreen Editing should update item data via inline editing", "state": "passed" }, + { + "name": "should disable create button when selected item has empty subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should disable create button when selected item has empty subtypes", + "state": "passed" + }, + { + "name": "should enable create button after deselecting item with empty subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should enable create button after deselecting item with empty subtypes", + "state": "passed" + }, + { + "name": "should show warning alert for items missing subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should show warning alert for items missing subtypes", + "state": "passed" + }, + { + "name": "should hide warning alert after deselecting items with empty subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should hide warning alert after deselecting items with empty subtypes", + "state": "passed" + }, { "name": "should render in fullscreen mode on mobile viewports", "fullName": "MaintenanceScheduleReviewScreen Responsive layout should render in fullscreen mode on mobile viewports", diff --git a/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.test.tsx b/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.test.tsx index 16e9ea8..534e007 100644 --- a/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.test.tsx +++ b/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.test.tsx @@ -33,6 +33,21 @@ jest.mock('../hooks/useCreateSchedulesFromExtraction', () => ({ }), })); +// Track SubtypeCheckboxGroup onChange callbacks per instance +const subtypeOnChangeCallbacks: Array<(subtypes: string[]) => void> = []; +jest.mock('./SubtypeCheckboxGroup', () => ({ + SubtypeCheckboxGroup: ({ selected, onChange }: { category: string; selected: string[]; onChange: (subtypes: string[]) => void }) => { + subtypeOnChangeCallbacks.push(onChange); + return ( +
+ {selected.map((s: string) => ( + {s} + ))} +
+ ); + }, +})); + const sampleItems: MaintenanceScheduleItem[] = [ { service: 'Engine Oil Change', @@ -60,6 +75,18 @@ const sampleItems: MaintenanceScheduleItem[] = [ }, ]; +const sampleItemsWithEmpty: MaintenanceScheduleItem[] = [ + ...sampleItems, + { + service: 'Brake Fluid', + intervalMiles: 30000, + intervalMonths: 24, + details: null, + confidence: 0.65, + subtypes: [], + }, +]; + describe('MaintenanceScheduleReviewScreen', () => { const defaultProps = { open: true, @@ -72,6 +99,7 @@ describe('MaintenanceScheduleReviewScreen', () => { beforeEach(() => { jest.clearAllMocks(); mockMutateAsync.mockResolvedValue([]); + subtypeOnChangeCallbacks.length = 0; }); describe('Rendering', () => { @@ -109,9 +137,12 @@ describe('MaintenanceScheduleReviewScreen', () => { expect(screen.getByText('Use 0W-20 full synthetic oil')).toBeInTheDocument(); }); - it('should display subtype chips', () => { + it('should display subtypes in SubtypeCheckboxGroup', () => { render(); + const groups = screen.getAllByTestId('subtype-checkbox-group'); + expect(groups).toHaveLength(3); + expect(screen.getByText('Engine Oil')).toBeInTheDocument(); expect(screen.getByText('Tires')).toBeInTheDocument(); expect(screen.getByText('Cabin Air Filter / Purifier')).toBeInTheDocument(); @@ -273,6 +304,43 @@ describe('MaintenanceScheduleReviewScreen', () => { }); }); + describe('Subtype validation', () => { + it('should disable create button when selected item has empty subtypes', () => { + render(); + + // All 4 items selected, but Brake Fluid has no subtypes + const createButton = screen.getByRole('button', { name: /create/i }); + expect(createButton).toBeDisabled(); + }); + + it('should enable create button after deselecting item with empty subtypes', () => { + render(); + + // Deselect the 4th item (Brake Fluid with empty subtypes) + const checkboxes = screen.getAllByRole('checkbox'); + fireEvent.click(checkboxes[3]); + + const createButton = screen.getByRole('button', { name: /create 3 schedules/i }); + expect(createButton).not.toBeDisabled(); + }); + + it('should show warning alert for items missing subtypes', () => { + render(); + + expect(screen.getByText(/missing subtypes/)).toBeInTheDocument(); + }); + + it('should hide warning alert after deselecting items with empty subtypes', () => { + render(); + + // Deselect the Brake Fluid item + const checkboxes = screen.getAllByRole('checkbox'); + fireEvent.click(checkboxes[3]); + + expect(screen.queryByText(/missing subtypes/)).not.toBeInTheDocument(); + }); + }); + describe('Responsive layout', () => { afterEach(() => { // Reset matchMedia after each test diff --git a/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.tsx b/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.tsx index 259dc60..289a572 100644 --- a/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.tsx +++ b/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.tsx @@ -17,7 +17,7 @@ import { IconButton, Alert, CircularProgress, - Chip, + Tooltip, useTheme, useMediaQuery, } from '@mui/material'; @@ -26,8 +26,11 @@ import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import SelectAllIcon from '@mui/icons-material/SelectAll'; import DeselectIcon from '@mui/icons-material/Deselect'; +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; import type { MaintenanceScheduleItem } from '../../documents/hooks/useManualExtraction'; import { useCreateSchedulesFromExtraction } from '../hooks/useCreateSchedulesFromExtraction'; +import { SubtypeCheckboxGroup } from './SubtypeCheckboxGroup'; +import { getSubtypesForCategory } from '../types/maintenance.types'; export interface MaintenanceScheduleReviewScreenProps { open: boolean; @@ -176,12 +179,18 @@ export const MaintenanceScheduleReviewScreen: React.FC(() => - items.map((item) => ({ ...item, selected: true })) + items.map((item) => ({ + ...item, + subtypes: item.subtypes.filter((st) => validRoutineSubtypes.includes(st)), + selected: true, + })) ); const [createError, setCreateError] = useState(null); const selectedCount = editableItems.filter((i) => i.selected).length; + const hasInvalidSubtypes = editableItems.some((i) => i.selected && i.subtypes.length === 0); const handleToggle = useCallback((index: number) => { setEditableItems((prev) => @@ -203,6 +212,12 @@ export const MaintenanceScheduleReviewScreen: React.FC { + setEditableItems((prev) => + prev.map((item, i) => (i === index ? { ...item, subtypes } : item)) + ); + }, []); + const handleCreate = async () => { setCreateError(null); const selectedItems = editableItems.filter((i) => i.selected); @@ -334,18 +349,34 @@ export const MaintenanceScheduleReviewScreen: React.FC )} - {item.subtypes.length > 0 && ( - - {item.subtypes.map((subtype) => ( - - ))} + + + + Subtypes: + + {item.subtypes.length === 0 && item.selected && ( + + + + )} - )} + handleSubtypesChange(index, subtypes)} + /> + ))} + {hasInvalidSubtypes && ( + + Some selected items are missing subtypes. Please select at least one subtype for each selected item. + + )} + Tap any field to edit before creating schedules. @@ -378,7 +409,7 @@ export const MaintenanceScheduleReviewScreen: React.FC : } sx={{ minHeight: 44, order: isMobile ? 1 : 2, width: isMobile ? '100%' : 'auto' }} > diff --git a/frontend/src/features/maintenance/hooks/useCreateSchedulesFromExtraction.ts b/frontend/src/features/maintenance/hooks/useCreateSchedulesFromExtraction.ts index cb3da87..934b057 100644 --- a/frontend/src/features/maintenance/hooks/useCreateSchedulesFromExtraction.ts +++ b/frontend/src/features/maintenance/hooks/useCreateSchedulesFromExtraction.ts @@ -20,10 +20,11 @@ export function useCreateSchedulesFromExtraction() { mutationFn: async ({ vehicleId, items }) => { const results: MaintenanceScheduleResponse[] = []; for (const item of items) { + if (item.subtypes.length === 0) continue; const request: CreateScheduleRequest = { vehicleId, category: 'routine_maintenance', - subtypes: item.subtypes.length > 0 ? item.subtypes : [], + subtypes: item.subtypes, scheduleType: 'interval', intervalMiles: item.intervalMiles ?? undefined, intervalMonths: item.intervalMonths ?? undefined,