Merge pull request #60 from beatz174-bit/codex/sort-product-list-alphabetically-z5figo

Deduplicate pick list products by name
This commit is contained in:
beatz174-bit
2025-11-23 15:15:41 +10:00
committed by GitHub
2 changed files with 73 additions and 26 deletions
+60
View File
@@ -174,6 +174,66 @@ describe('ActivePickListScreen product search', () => {
]);
});
it('deduplicates product options with the same id', async () => {
const duplicateProducts = [...defaultProducts, { ...defaultProducts[0] }];
productsMock.mockReturnValue(duplicateProducts);
const user = userEvent.setup();
render(
<MemoryRouter initialEntries={['/pick-lists/1']}>
<Routes>
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
</Routes>
</MemoryRouter>,
);
const combobox = screen.getByRole('combobox');
await user.click(combobox);
const listbox = await screen.findByRole('listbox');
const options = within(listbox).getAllByRole('option');
expect(options).toHaveLength(3);
expect(options.map((option) => option.textContent)).toEqual([
'Apple Juice (Drinks)',
'Chips (Snacks)',
'Cola (Drinks)',
]);
});
it('deduplicates product options with the same name', async () => {
const duplicateNameProducts: Product[] = [
...defaultProducts,
{ ...defaultProducts[0], id: 'prod-duplicate', barcode: '999', updated_at: 5 },
];
productsMock.mockReturnValue(duplicateNameProducts);
const user = userEvent.setup();
render(
<MemoryRouter initialEntries={['/pick-lists/1']}>
<Routes>
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
</Routes>
</MemoryRouter>,
);
const combobox = screen.getByRole('combobox');
await user.click(combobox);
const listbox = await screen.findByRole('listbox');
const options = within(listbox).getAllByRole('option');
expect(options).toHaveLength(3);
expect(options.map((option) => option.textContent)).toEqual([
'Apple Juice (Drinks)',
'Chips (Snacks)',
'Cola (Drinks)',
]);
});
it('updates an existing pick item when the same packaging is selected', async () => {
pickItemsMock.mockReturnValue([
{
+13 -26
View File
@@ -48,35 +48,22 @@ export const ActivePickListScreen = () => {
[areas, pickList?.area_id],
);
const productMap = useMemo(
() => new Map(products.map((product) => [product.id, product])),
[products],
);
const sortedProducts = useMemo(() => {
const uniqueProducts = new Map<string, Product>();
const sortedProducts = useMemo(
() =>
[...products].sort((a, b) =>
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }),
),
[products],
);
products.forEach((product) => {
const normalizedName = product.name.trim().toLowerCase();
const existing = uniqueProducts.get(normalizedName);
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => {
const productA = productMap.get(a.product_id);
const productB = productMap.get(b.product_id);
const nameA = productA?.name.toLowerCase() ?? '';
const nameB = productB?.name.toLowerCase() ?? '';
const nameComparison = nameA.localeCompare(nameB);
if (nameComparison !== 0) return nameComparison;
if (a.is_carton !== b.is_carton) return Number(a.is_carton) - Number(b.is_carton);
return (a.created_at ?? 0) - (b.created_at ?? 0);
if (!existing || product.updated_at > existing.updated_at) {
uniqueProducts.set(normalizedName, product);
}
});
}, [items, productMap]);
return Array.from(uniqueProducts.values()).sort((a, b) =>
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }),
);
}, [products]);
const filteredProducts = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();