diff --git a/inventory-manager/src/components/page-content/segment1-search_product/input-bars/InputSearch.tsx b/inventory-manager/src/components/page-content/segment1-search_product/input-bars/InputSearch.tsx index b50783f..8a008bc 100644 --- a/inventory-manager/src/components/page-content/segment1-search_product/input-bars/InputSearch.tsx +++ b/inventory-manager/src/components/page-content/segment1-search_product/input-bars/InputSearch.tsx @@ -8,18 +8,21 @@ interface Props { } const InputSearch: React.FC = ({parameter}) => { - - const {setParams} = useSearchContext(); + const {name, category, setParams} = useSearchContext(); + const timerRef = React.useRef(null); function onChange(e: React.ChangeEvent) { const next = e.target.value; - if (!e.target.value) { - if (parameter === 'name') setParams({name: null}); - if (parameter === 'category') setParams({category: null}); - } else { - if (parameter === 'name') setParams({name: next}); - if (parameter === 'category') setParams({category: next}); - } + if (timerRef.current) window.clearTimeout(timerRef.current); + timerRef.current = window.setTimeout(() => { + if (!e.target.value) { + if (parameter === 'name') setParams({name: null}); + if (parameter === 'category') setParams({category: null}); + } else { + if (parameter === 'name' && next !== name) setParams({name: next}); + if (parameter === 'category' && next !== category) setParams({category: next}); + } + }, 400); } const getPlaceholder = (): string => { @@ -34,6 +37,6 @@ const InputSearch: React.FC = ({parameter}) => { onChange={onChange} /> ); -} +}; export default InputSearch; \ No newline at end of file diff --git a/inventory-manager/src/components/page-content/segment2-new_product/ProductForm.tsx b/inventory-manager/src/components/page-content/segment2-new_product/ProductForm.tsx index f6435f4..f5c0c69 100644 --- a/inventory-manager/src/components/page-content/segment2-new_product/ProductForm.tsx +++ b/inventory-manager/src/components/page-content/segment2-new_product/ProductForm.tsx @@ -4,7 +4,7 @@ import type {Product} from "../../../types/Product"; import {createProduct, updateProduct} from "../../../services/Requests"; import {useSearchContext} from "../../../context/SearchContext"; import dayjs from "dayjs"; -import {useProductsData} from "../../../context/DataContext"; +import {useDataContext} from "../../../context/DataContext"; interface ProductFormProps { @@ -17,7 +17,7 @@ interface ProductFormProps { const ProductForm: React.FC = ({initialValues, mode = "create", onClose}) => { const [form] = Form.useForm(); const {stockQuantity, setParams} = useSearchContext(); - const {categories} = useProductsData(); + const {categories} = useDataContext(); const handleSave = async (values: Product) => { diff --git a/inventory-manager/src/components/page-content/segment3-table/InventoryTable.tsx b/inventory-manager/src/components/page-content/segment3-table/InventoryTable.tsx index 0b1e457..c104406 100644 --- a/inventory-manager/src/components/page-content/segment3-table/InventoryTable.tsx +++ b/inventory-manager/src/components/page-content/segment3-table/InventoryTable.tsx @@ -4,7 +4,7 @@ import type {TableColumnsType, TableProps} from 'antd'; import type {Product} from "../../../types/Product"; import {SearchOutlined} from '@ant-design/icons'; import {useSearchContext} from "../../../context/SearchContext"; -import {useProductsData} from "../../../context/DataContext"; +import {useDataContext} from "../../../context/DataContext"; import {deleteProduct, markInStock, markOutOfStock} from "../../../services/Requests"; import ProductForm from "../segment2-new_product/ProductForm"; @@ -19,21 +19,28 @@ const InventoryTable: React.FC = () => { setEditingProduct(null); } - const {products, loading} = useProductsData(); const searchInput = useRef(null); - const {stockQuantity, page, setParams} = useSearchContext(); - + const {stockQuantity, name, category, page, setParams} = useSearchContext(); + const {products, loading} = useDataContext() const handleTableChange: TableProps['onChange'] = (_pagination, filters, sorter) => { setParams({page: ((page as number))}); - if ((filters.name !== undefined) && ((filters.name as unknown as string) !== '') && (filters.name !== null)) { + if ((filters.name !== undefined) && + ((filters.name as unknown as string) !== '') && + (filters.name !== null) && + (filters.name as unknown as string !== name)) { setParams({name: filters.name?.[0] as unknown as string}); } - if (filters.category !== undefined && (filters.category as unknown as string) !== '' && filters.category !== null) { + if (filters.category !== undefined && + (filters.category as unknown as string) !== '' && + filters.category !== null && + (filters.category as unknown as string !== category)) { setParams({category: filters.category?.[0] as unknown as string}); } - if (filters.stockQuantity?.[0] && filters.stockQuantity?.[0] !== null) { + if (filters.stockQuantity?.[0] && + filters.stockQuantity?.[0] !== null && + filters.stockQuantity?.[0] as unknown as number !== stockQuantity) { setParams({stockQuantity: filters.stockQuantity?.[0] as unknown as number}); } const sortObj = Array.isArray(sorter) ? sorter : [sorter]; diff --git a/inventory-manager/src/components/page-content/segment3-table/InventoryTablePageSelector.tsx b/inventory-manager/src/components/page-content/segment3-table/InventoryTablePageSelector.tsx index 89e962e..ed0afd0 100644 --- a/inventory-manager/src/components/page-content/segment3-table/InventoryTablePageSelector.tsx +++ b/inventory-manager/src/components/page-content/segment3-table/InventoryTablePageSelector.tsx @@ -1,18 +1,18 @@ import React from 'react'; import {Pagination, type PaginationProps} from 'antd'; import {useSearchContext} from "../../../context/SearchContext"; -import {useProductsData} from "../../../context/DataContext"; +import {useDataContext} from "../../../context/DataContext"; const InventoryTablePageSelector: React.FC = () => { const {page, setParams} = useSearchContext(); - const {total} = useProductsData(); + const {total} = useDataContext() const handlePageChange: PaginationProps['onChange'] = (pagination) => { setParams({page: ((pagination as number) - 1)}); } return ( { - const {summary} = useProductsData(); + const {summary} = useDataContext(); const columns: TableColumnsType = [ { diff --git a/inventory-manager/src/context/DataContext.tsx b/inventory-manager/src/context/DataContext.tsx index 1466ac2..866cac3 100644 --- a/inventory-manager/src/context/DataContext.tsx +++ b/inventory-manager/src/context/DataContext.tsx @@ -1,4 +1,4 @@ -import React, {createContext, useEffect, useState} from "react"; +import React, {createContext, useContext, useEffect, useRef, useState} from "react"; import type {CategorySummary, Product} from "../types/Product"; import {getCategories, getFilteredProducts, getProducts, getSummary} from "../services/Requests"; import {useSearchContext} from "./SearchContext"; @@ -13,9 +13,26 @@ interface ProductDataContextProps { refreshProducts: () => Promise; } -const DataContext = createContext(undefined); +const defaultValues = { + products: undefined, + loading: true, + error: null, + total: null, + categories: [], + summary: undefined, + refreshProducts: async () => { + }, +}; + +const DataContext = createContext(defaultValues); + +export const useDataContext = () => useContext(DataContext); export const DataProvider: React.FC<{ children: React.ReactNode }> = ({children}) => { + const {name, category, stockQuantity, page, sort} = useSearchContext(); + const lastKeyRef = (useRef(null)); + const inFlightRef = (useRef(false)); + const [products, setProducts] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -23,48 +40,21 @@ export const DataProvider: React.FC<{ children: React.ReactNode }> = ({children} const [categories, setCategories] = useState([""]); const [summary, setSummary] = useState(); - - const refreshProducts = async () => { - setLoading(true); - try { - const fetched = await getProducts({page: 0}); - setProducts(fetched.products); - setTotal(fetched.totalPages); - const fetchedCategories = await getCategories(); - setCategories(fetchedCategories.data); - const fetchedSummary = await getSummary(); - setSummary(fetchedSummary.data); - - } catch (err: any) { - setError(err.message || "Unknown error"); - } finally { - setLoading(false); - } - }; - useEffect(() => { - refreshProducts().then(); - }, []); - - return ( - - {children} - - ); -}; + const paramsKey = JSON.stringify({ + name: name ?? null, + category: category ?? null, + stockQuantity: stockQuantity ?? null, + page: page ?? null, + sort: Array.isArray(sort) ? sort : (sort ?? null) + }); -export function useProductsData() { - const {name, category, stockQuantity, page, sort} = useSearchContext(); - - const [products, setProducts] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [total, setTotal] = useState(0); - const [categories, setCategories] = useState([""]); - const [summary, setSummary] = useState(); + if (lastKeyRef.current === paramsKey) return; + if (inFlightRef.current) return; + inFlightRef.current = true; + lastKeyRef.current = paramsKey; - useEffect(() => { const fetchData = async () => { setLoading(true); try { @@ -90,14 +80,48 @@ export function useProductsData() { setSummary(fetchedSummary.data); } catch (err: any) { - setError(err.message); + let msg: string; + if (typeof err === 'object' && err !== null) { + msg = + (err as any)?.response?.data?.message ?? + (err as any)?.response?.data ?? + (err as Error)?.message ?? + 'Unknown error'; + setError(String(msg)); + + } else { + msg = String(err); + } + setError(msg); } finally { setLoading(false); } }; - fetchData().then(); + fetchData().then(() => inFlightRef.current = false); }, [name, category, stockQuantity, page, sort]); - return {products, loading, error, total, categories, summary}; -} \ No newline at end of file + const refreshProducts = async () => { + setLoading(true); + try { + const fetched = await getProducts({page: 0}); + setProducts(fetched.products); + setTotal(fetched.totalPages); + const fetchedCategories = await getCategories(); + setCategories(fetchedCategories.data); + const fetchedSummary = await getSummary(); + setSummary(fetchedSummary.data); + + } catch (err: any) { + setError(err.message || "Unknown error"); + } finally { + setLoading(false); + } + }; + + return ( + + {children} + + ); +};