Merge remote-tracking branch 'origin/codex/refactor-add-product-control-on-pick-list'

This commit is contained in:
beatzaplenty committed 2025-12-01 16:13:52 +10:00
commit e4c51d69b6
2 files changed
+40 -7

No files matched your search

+8 -2
View File
@@ -8,7 +8,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import { Link as RouterLink } from 'react-router-dom';
import { useMemo, useState, useEffect } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { useCategories } from '../hooks/dataHooks'; import { useCategories } from '../hooks/dataHooks';
import { Product } from '../models/Product'; import { Product } from '../models/Product';
@@ -18,6 +17,7 @@ interface ProductAutocompleteProps {
onSelect: (product: Product) => void; onSelect: (product: Product) => void;
placeholder?: string; placeholder?: string;
onQueryChange?: (q: string) => void; onQueryChange?: (q: string) => void;
onAddProduct?: () => void;
} }
export const ProductAutocomplete = ({ export const ProductAutocomplete = ({
@@ -25,6 +25,7 @@ export const ProductAutocomplete = ({
onSelect, onSelect,
placeholder = 'Search products', placeholder = 'Search products',
onQueryChange, onQueryChange,
onAddProduct,
}: ProductAutocompleteProps) => { }: ProductAutocompleteProps) => {
const categories = useCategories(); const categories = useCategories();
const categoriesById = useMemo(() => new Map(categories.map((c) => [c.id, c.name])), [categories]); const categoriesById = useMemo(() => new Map(categories.map((c) => [c.id, c.name])), [categories]);
@@ -98,7 +99,12 @@ export const ProductAutocomplete = ({
{params.InputProps?.endAdornment} {params.InputProps?.endAdornment}
<InputAdornment position="end"> <InputAdornment position="end">
<Tooltip title="Add a new product"> <Tooltip title="Add a new product">
<IconButton aria-label="Add product" component={RouterLink} to="/products" size="small"> <IconButton
aria-label="Add product"
size="small"
onClick={onAddProduct}
disabled={!onAddProduct}
>
<AddCircleOutlineIcon /> <AddCircleOutlineIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
+32 -5
View File
@@ -1,22 +1,23 @@
// src/screens/ActivePickListScreen.tsx // src/screens/ActivePickListScreen.tsx
import { import {
Alert,
AlertColor,
Button, Button,
Checkbox, Checkbox,
Container, Container,
FormControlLabel, FormControlLabel,
IconButton,
InputAdornment, InputAdornment,
Stack, Stack,
TextField, TextField,
Tooltip,
Typography, Typography,
FormControl, FormControl,
FormLabel, FormLabel,
RadioGroup, RadioGroup,
Radio, Radio,
Snackbar,
} from '@mui/material'; } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import { Link as RouterLink, useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { import {
@@ -31,7 +32,9 @@ import { PickItemRow } from '../components/PickItemRow';
import { PickItem } from '../models/PickItem'; import { PickItem } from '../models/PickItem';
import { Product } from '../models/Product'; import { Product } from '../models/Product';
import { ProductAutocomplete } from '../components/ProductAutocomplete'; import { ProductAutocomplete } from '../components/ProductAutocomplete';
import { normalizeName } from '../utils/stringUtils'; import { AddProductDialog } from '../components/AddProductDialog';
const normalizeName = (name: string) => name.trim().toLowerCase();
const ActivePickListScreen = () => { const ActivePickListScreen = () => {
const { id } = useParams(); const { id } = useParams();
@@ -48,6 +51,8 @@ const ActivePickListScreen = () => {
const [itemState, setItemState] = useState<PickItem[]>(items ?? []); const [itemState, setItemState] = useState<PickItem[]>(items ?? []);
const [isBatchUpdating, setIsBatchUpdating] = useState(false); const [isBatchUpdating, setIsBatchUpdating] = useState(false);
const [packagingFilter, setPackagingFilter] = useState<'all' | 'units' | 'cartons'>('all'); const [packagingFilter, setPackagingFilter] = useState<'all' | 'units' | 'cartons'>('all');
const [addProductDialogOpen, setAddProductDialogOpen] = useState(false);
const [feedback, setFeedback] = useState<{ text: string; severity: AlertColor } | null>(null);
// NEW: search within the list and category filter for the visible list // NEW: search within the list and category filter for the visible list
const [listSearch, setListSearch] = useState(''); const [listSearch, setListSearch] = useState('');
@@ -153,6 +158,14 @@ const ActivePickListScreen = () => {
const categoriesById = useMemo(() => new Map(categoriesList.map((c) => [c.id, c.name])), [categoriesList]); const categoriesById = useMemo(() => new Map(categoriesList.map((c) => [c.id, c.name])), [categoriesList]);
const categoryNameToId = useMemo(() => new Map(categoriesList.map((c) => [c.name.trim().toLowerCase(), c.id])), [categoriesList]); const categoryNameToId = useMemo(() => new Map(categoriesList.map((c) => [c.name.trim().toLowerCase(), c.id])), [categoriesList]);
const addProductCategoryOptions = useMemo(() => {
const categoryNames = categoriesList.map((item) => item.name);
const productCategories = products.map(
(product) => categoriesById.get(product.category) ?? product.category ?? '',
);
return Array.from(new Set([...categoryNames, ...productCategories].filter(Boolean)));
}, [categoriesList, products, categoriesById]);
// Filter products by pickList.categories (now stored as ids). Fallback: resolve name -> id // Filter products by pickList.categories (now stored as ids). Fallback: resolve name -> id
const categoryFilteredProducts = useMemo(() => { const categoryFilteredProducts = useMemo(() => {
if (!pickList?.categories || pickList.categories.length === 0) { if (!pickList?.categories || pickList.categories.length === 0) {
@@ -310,6 +323,10 @@ const ActivePickListScreen = () => {
navigate('/pick-lists'); navigate('/pick-lists');
}; };
const handleAddProductClose = () => {
setAddProductDialogOpen(false);
};
// Sort the items that are actually visible (after showPicked and packaging filter) // Sort the items that are actually visible (after showPicked and packaging filter)
const visibleItems = useMemo(() => { const visibleItems = useMemo(() => {
let arr = [...itemsAfterShowPicked]; let arr = [...itemsAfterShowPicked];
@@ -403,6 +420,7 @@ const ActivePickListScreen = () => {
void addOrUpdateItem(product); void addOrUpdateItem(product);
}} }}
onQueryChange={(q: string) => setQuery(q)} onQueryChange={(q: string) => setQuery(q)}
onAddProduct={() => setAddProductDialogOpen(true)}
/> />
{filteredProducts.length === 0 ? ( {filteredProducts.length === 0 ? (
@@ -555,8 +573,17 @@ const ActivePickListScreen = () => {
<Button fullWidth sx={{ mt: 3 }} variant="outlined" onClick={returnToLists}> <Button fullWidth sx={{ mt: 3 }} variant="outlined" onClick={returnToLists}>
Save and Return Save and Return
</Button> </Button>
<AddProductDialog
open={addProductDialogOpen}
onClose={handleAddProductClose}
categoryOptions={addProductCategoryOptions}
onFeedback={setFeedback}
/>
<Snackbar open={!!feedback} autoHideDuration={3000} onClose={() => setFeedback(null)}>
{feedback ? <Alert severity={feedback.severity}>{feedback.text}</Alert> : undefined}
</Snackbar>
</Container> </Container>
); );
}; };
export default ActivePickListScreen export default ActivePickListScreen;