Merge pull request #141 from beatz174-bit/codex/add-packaging-filter-with-radio-buttons
Add packaging filter to active pick list
This commit is contained in:
@@ -67,6 +67,8 @@ vi.mock('../context/DBProvider', () => ({
|
||||
}));
|
||||
|
||||
describe('ActivePickListScreen product search', () => {
|
||||
const getRadio = (testId: string) => within(screen.getByTestId(testId)).getByRole('radio');
|
||||
|
||||
beforeEach(() => {
|
||||
addMock.mockReset();
|
||||
updateMock.mockReset();
|
||||
@@ -573,4 +575,136 @@ describe('ActivePickListScreen product search', () => {
|
||||
expect(togglePicked).toBeDisabled();
|
||||
expect(togglePicked).toBeChecked();
|
||||
});
|
||||
|
||||
it('enables packaging filters when both packaging types are visible and filters items', async () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-2',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-2',
|
||||
quantity: 1,
|
||||
is_carton: true,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/pick-lists/1']}>
|
||||
<Routes>
|
||||
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const allRadio = getRadio('packaging-all');
|
||||
const unitsRadio = getRadio('packaging-units');
|
||||
const cartonsRadio = getRadio('packaging-cartons');
|
||||
|
||||
expect(allRadio).toBeChecked();
|
||||
expect(unitsRadio).not.toBeDisabled();
|
||||
expect(cartonsRadio).not.toBeDisabled();
|
||||
|
||||
await user.click(unitsRadio);
|
||||
expect(unitsRadio).toBeChecked();
|
||||
expect(screen.getByText('Cola')).toBeVisible();
|
||||
expect(screen.queryByText('Chips')).not.toBeInTheDocument();
|
||||
|
||||
await user.click(cartonsRadio);
|
||||
expect(cartonsRadio).toBeChecked();
|
||||
expect(screen.getByText('Chips')).toBeVisible();
|
||||
expect(screen.queryByText('Cola')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables units and cartons packaging options when only units are visible', () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/pick-lists/1']}>
|
||||
<Routes>
|
||||
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(getRadio('packaging-all')).toBeChecked();
|
||||
expect(getRadio('packaging-units')).toBeDisabled();
|
||||
expect(getRadio('packaging-cartons')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('resets packaging filter to all and disables options when visible items become single packaging type', async () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-2',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-2',
|
||||
quantity: 1,
|
||||
is_carton: true,
|
||||
status: 'picked',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/pick-lists/1']}>
|
||||
<Routes>
|
||||
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const cartonsRadio = getRadio('packaging-cartons');
|
||||
|
||||
expect(cartonsRadio).not.toBeDisabled();
|
||||
|
||||
await user.click(cartonsRadio);
|
||||
expect(cartonsRadio).toBeChecked();
|
||||
expect(screen.getByText('Chips')).toBeVisible();
|
||||
expect(screen.queryByText('Cola')).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByLabelText(/show picked/i));
|
||||
|
||||
expect(getRadio('packaging-all')).toBeChecked();
|
||||
expect(getRadio('packaging-units')).toBeDisabled();
|
||||
expect(getRadio('packaging-cartons')).toBeDisabled();
|
||||
expect(screen.getByText('Cola')).toBeVisible();
|
||||
expect(screen.queryByText('Chips')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
} from '@mui/material';
|
||||
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
@@ -37,6 +41,7 @@ export const ActivePickListScreen = () => {
|
||||
const [showPicked, setShowPicked] = useState(true);
|
||||
const [itemState, setItemState] = useState(items);
|
||||
const [isBatchUpdating, setIsBatchUpdating] = useState(false);
|
||||
const [packagingFilter, setPackagingFilter] = useState<'all' | 'units' | 'cartons'>('all');
|
||||
|
||||
useEffect(() => {
|
||||
setItemState((current) => {
|
||||
@@ -63,6 +68,21 @@ export const ActivePickListScreen = () => {
|
||||
[itemState, showPicked],
|
||||
);
|
||||
|
||||
const packagingInfo = useMemo(() => {
|
||||
const visible = itemsAfterShowPicked ?? [];
|
||||
const uniqueValues = new Set(visible.map((it) => !!it.is_carton));
|
||||
return {
|
||||
visibleCount: visible.length,
|
||||
uniquePackagingCount: uniqueValues.size,
|
||||
};
|
||||
}, [itemsAfterShowPicked]);
|
||||
|
||||
useEffect(() => {
|
||||
if (packagingInfo.visibleCount === 0 || packagingInfo.uniquePackagingCount === 1) {
|
||||
setPackagingFilter('all');
|
||||
}
|
||||
}, [packagingInfo.visibleCount, packagingInfo.uniquePackagingCount]);
|
||||
|
||||
const allItemsPicked = useMemo(
|
||||
() => itemState.length > 0 && itemState.every((item) => item.status === 'picked'),
|
||||
[itemState],
|
||||
@@ -165,9 +185,16 @@ export const ActivePickListScreen = () => {
|
||||
}
|
||||
}, [allItemsPicked, showPicked]);
|
||||
|
||||
// Sort the items that are actually visible (after showPicked)
|
||||
// Sort the items that are actually visible (after showPicked and packaging filter)
|
||||
const visibleItems = useMemo(() => {
|
||||
const arr = [...itemsAfterShowPicked];
|
||||
let arr = [...itemsAfterShowPicked];
|
||||
|
||||
if (packagingFilter === 'units') {
|
||||
arr = arr.filter((item) => !item.is_carton);
|
||||
} else if (packagingFilter === 'cartons') {
|
||||
arr = arr.filter((item) => item.is_carton);
|
||||
}
|
||||
|
||||
arr.sort((a, b) => {
|
||||
const nameA = normalizeName(productMap.get(a.product_id)?.name ?? '');
|
||||
const nameB = normalizeName(productMap.get(b.product_id)?.name ?? '');
|
||||
@@ -178,7 +205,7 @@ export const ActivePickListScreen = () => {
|
||||
return timeA - timeB;
|
||||
});
|
||||
return arr;
|
||||
}, [itemsAfterShowPicked, productMap]);
|
||||
}, [itemsAfterShowPicked, productMap, packagingFilter]);
|
||||
|
||||
const updateItemState = (itemId: string, updater: (item: PickItem) => PickItem) => {
|
||||
setItemState((current) => current.map((item) => (item.id === itemId ? updater(item) : item)));
|
||||
@@ -393,6 +420,40 @@ export const ActivePickListScreen = () => {
|
||||
flexWrap="wrap"
|
||||
rowGap={1}
|
||||
>
|
||||
<FormControl component="fieldset" sx={{ ml: { xs: 0, sm: 2 } }}>
|
||||
<FormLabel component="legend" sx={{ fontSize: '0.875rem' }}>
|
||||
Packaging
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-label="packaging-filter"
|
||||
name="packaging-filter"
|
||||
value={packagingFilter}
|
||||
onChange={(_, value) => setPackagingFilter(value as 'all' | 'units' | 'cartons')}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="all"
|
||||
control={<Radio size="small" />}
|
||||
label="All"
|
||||
data-testid="packaging-all"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="units"
|
||||
control={<Radio size="small" />}
|
||||
label="Units"
|
||||
disabled={packagingInfo.visibleCount === 0 || packagingInfo.uniquePackagingCount === 1}
|
||||
data-testid="packaging-units"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="cartons"
|
||||
control={<Radio size="small" />}
|
||||
label="Cartons"
|
||||
disabled={packagingInfo.visibleCount === 0 || packagingInfo.uniquePackagingCount === 1}
|
||||
data-testid="packaging-cartons"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ ml: { xs: 0, sm: 2 } }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
|
||||
Reference in New Issue
Block a user