fix: Update auto schedule creation
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m29s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 25s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m29s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 25s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<div data-testid="subtype-checkbox-group">
|
||||
{selected.map((s: string) => (
|
||||
<span key={s} data-testid="subtype-chip">{s}</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
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(<MaintenanceScheduleReviewScreen {...defaultProps} />);
|
||||
|
||||
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(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
|
||||
// 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(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
|
||||
// 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(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
|
||||
expect(screen.getByText(/missing subtypes/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide warning alert after deselecting items with empty subtypes', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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<MaintenanceScheduleReview
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const createMutation = useCreateSchedulesFromExtraction();
|
||||
|
||||
const validRoutineSubtypes = getSubtypesForCategory('routine_maintenance');
|
||||
const [editableItems, setEditableItems] = useState<EditableItem[]>(() =>
|
||||
items.map((item) => ({ ...item, selected: true }))
|
||||
items.map((item) => ({
|
||||
...item,
|
||||
subtypes: item.subtypes.filter((st) => validRoutineSubtypes.includes(st)),
|
||||
selected: true,
|
||||
}))
|
||||
);
|
||||
const [createError, setCreateError] = useState<string | null>(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<MaintenanceScheduleReview
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleSubtypesChange = useCallback((index: number, subtypes: string[]) => {
|
||||
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<MaintenanceScheduleReview
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{item.subtypes.length > 0 && (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 0.5 }}>
|
||||
{item.subtypes.map((subtype) => (
|
||||
<Chip key={subtype} label={subtype} size="small" variant="outlined" />
|
||||
))}
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Subtypes:
|
||||
</Typography>
|
||||
{item.subtypes.length === 0 && item.selected && (
|
||||
<Tooltip title="At least one subtype is required">
|
||||
<WarningAmberIcon color="warning" sx={{ fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<SubtypeCheckboxGroup
|
||||
category="routine_maintenance"
|
||||
selected={item.subtypes}
|
||||
onChange={(subtypes) => handleSubtypesChange(index, subtypes)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{hasInvalidSubtypes && (
|
||||
<Alert severity="warning" sx={{ mt: 2 }}>
|
||||
Some selected items are missing subtypes. Please select at least one subtype for each selected item.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 2, textAlign: 'center' }}>
|
||||
Tap any field to edit before creating schedules.
|
||||
</Typography>
|
||||
@@ -378,7 +409,7 @@ export const MaintenanceScheduleReviewScreen: React.FC<MaintenanceScheduleReview
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleCreate}
|
||||
disabled={selectedCount === 0 || createMutation.isPending}
|
||||
disabled={selectedCount === 0 || hasInvalidSubtypes || createMutation.isPending}
|
||||
startIcon={createMutation.isPending ? <CircularProgress size={16} /> : <CheckIcon />}
|
||||
sx={{ minHeight: 44, order: isMobile ? 1 : 2, width: isMobile ? '100%' : 'auto' }}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user