Merge remote-tracking branch 'origin/codex/refactor-add-product-control-on-pick-list'
This commit is contained in:
commit
e4c51d69b6
2 files changed
+40
-7
No files matched your search
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in new issue
Block a user