fix: Data validation for scheduled maintenance
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m24s
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 3m24s
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:
@@ -75,7 +75,7 @@ const sampleItems: MaintenanceScheduleItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const sampleItemsWithEmpty: MaintenanceScheduleItem[] = [
|
||||
const sampleItemsWithEmptySubtypes: MaintenanceScheduleItem[] = [
|
||||
...sampleItems,
|
||||
{
|
||||
service: 'Brake Fluid',
|
||||
@@ -87,6 +87,18 @@ const sampleItemsWithEmpty: MaintenanceScheduleItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const sampleItemsWithMissingIntervals: MaintenanceScheduleItem[] = [
|
||||
...sampleItems,
|
||||
{
|
||||
service: 'Coolant Flush',
|
||||
intervalMiles: null,
|
||||
intervalMonths: null,
|
||||
details: null,
|
||||
confidence: 0.55,
|
||||
subtypes: ['Coolant'],
|
||||
},
|
||||
];
|
||||
|
||||
describe('MaintenanceScheduleReviewScreen', () => {
|
||||
const defaultProps = {
|
||||
open: true,
|
||||
@@ -306,7 +318,7 @@ describe('MaintenanceScheduleReviewScreen', () => {
|
||||
|
||||
describe('Subtype validation', () => {
|
||||
it('should disable create button when selected item has empty subtypes', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmptySubtypes} />);
|
||||
|
||||
// All 4 items selected, but Brake Fluid has no subtypes
|
||||
const createButton = screen.getByRole('button', { name: /create/i });
|
||||
@@ -314,7 +326,7 @@ describe('MaintenanceScheduleReviewScreen', () => {
|
||||
});
|
||||
|
||||
it('should enable create button after deselecting item with empty subtypes', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmptySubtypes} />);
|
||||
|
||||
// Deselect the 4th item (Brake Fluid with empty subtypes)
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
@@ -325,13 +337,13 @@ describe('MaintenanceScheduleReviewScreen', () => {
|
||||
});
|
||||
|
||||
it('should show warning alert for items missing subtypes', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmptySubtypes} />);
|
||||
|
||||
expect(screen.getByText(/missing subtypes/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide warning alert after deselecting items with empty subtypes', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmpty} />);
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithEmptySubtypes} />);
|
||||
|
||||
// Deselect the Brake Fluid item
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
@@ -341,6 +353,50 @@ describe('MaintenanceScheduleReviewScreen', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Interval validation', () => {
|
||||
it('should disable create button when selected item has no intervals', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithMissingIntervals} />);
|
||||
|
||||
const createButton = screen.getByRole('button', { name: /create/i });
|
||||
expect(createButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable create button after deselecting item with missing intervals', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithMissingIntervals} />);
|
||||
|
||||
// Deselect the 4th item (Coolant Flush with null intervals)
|
||||
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 intervals', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithMissingIntervals} />);
|
||||
|
||||
expect(screen.getByText(/missing intervals/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should enable create button after editing interval on item', () => {
|
||||
render(<MaintenanceScheduleReviewScreen {...defaultProps} items={sampleItemsWithMissingIntervals} />);
|
||||
|
||||
// The Coolant Flush item shows '-' for both intervals. Click the Miles '-' to edit.
|
||||
// There are multiple '-' on screen, so find all and pick the right one.
|
||||
const dashTexts = screen.getAllByText('-');
|
||||
// Click the first dash (Miles field of the Coolant Flush item - last item's first dash)
|
||||
fireEvent.click(dashTexts[dashTexts.length - 2]);
|
||||
|
||||
// Type a value and save
|
||||
const input = screen.getByDisplayValue('');
|
||||
fireEvent.change(input, { target: { value: '50000' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter' });
|
||||
|
||||
const createButton = screen.getByRole('button', { name: /create 4 schedules/i });
|
||||
expect(createButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Responsive layout', () => {
|
||||
afterEach(() => {
|
||||
// Reset matchMedia after each test
|
||||
|
||||
@@ -191,6 +191,9 @@ export const MaintenanceScheduleReviewScreen: React.FC<MaintenanceScheduleReview
|
||||
|
||||
const selectedCount = editableItems.filter((i) => i.selected).length;
|
||||
const hasInvalidSubtypes = editableItems.some((i) => i.selected && i.subtypes.length === 0);
|
||||
const hasInvalidIntervals = editableItems.some(
|
||||
(i) => i.selected && i.intervalMiles === null && i.intervalMonths === null
|
||||
);
|
||||
|
||||
const handleToggle = useCallback((index: number) => {
|
||||
setEditableItems((prev) =>
|
||||
@@ -326,6 +329,7 @@ export const MaintenanceScheduleReviewScreen: React.FC<MaintenanceScheduleReview
|
||||
display: 'flex',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
gap: isMobile ? 0.5 : 2,
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<InlineField
|
||||
label="Miles"
|
||||
@@ -341,6 +345,11 @@ export const MaintenanceScheduleReviewScreen: React.FC<MaintenanceScheduleReview
|
||||
onSave={(v) => handleFieldUpdate(index, 'intervalMonths', v)}
|
||||
suffix="mo"
|
||||
/>
|
||||
{item.selected && item.intervalMiles === null && item.intervalMonths === null && (
|
||||
<Tooltip title="At least one interval (miles or months) is required">
|
||||
<WarningAmberIcon color="warning" sx={{ fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{item.details && (
|
||||
@@ -377,6 +386,12 @@ export const MaintenanceScheduleReviewScreen: React.FC<MaintenanceScheduleReview
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasInvalidIntervals && (
|
||||
<Alert severity="warning" sx={{ mt: 2 }}>
|
||||
Some selected items are missing intervals. Please set at least one interval (miles or months) 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>
|
||||
@@ -409,7 +424,7 @@ export const MaintenanceScheduleReviewScreen: React.FC<MaintenanceScheduleReview
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleCreate}
|
||||
disabled={selectedCount === 0 || hasInvalidSubtypes || createMutation.isPending}
|
||||
disabled={selectedCount === 0 || hasInvalidSubtypes || hasInvalidIntervals || createMutation.isPending}
|
||||
startIcon={createMutation.isPending ? <CircularProgress size={16} /> : <CheckIcon />}
|
||||
sx={{ minHeight: 44, order: isMobile ? 1 : 2, width: isMobile ? '100%' : 'auto' }}
|
||||
>
|
||||
|
||||
@@ -21,6 +21,7 @@ export function useCreateSchedulesFromExtraction() {
|
||||
const results: MaintenanceScheduleResponse[] = [];
|
||||
for (const item of items) {
|
||||
if (item.subtypes.length === 0) continue;
|
||||
if (item.intervalMiles === null && item.intervalMonths === null) continue;
|
||||
const request: CreateScheduleRequest = {
|
||||
vehicleId,
|
||||
category: 'routine_maintenance',
|
||||
|
||||
Reference in New Issue
Block a user