diff --git a/src/actions/KIInventoryActions.js b/src/actions/KIInventoryActions.js new file mode 100644 index 0000000000..b93738befd --- /dev/null +++ b/src/actions/KIInventoryActions.js @@ -0,0 +1,62 @@ +import axios from 'axios'; +import { ENDPOINTS } from '~/utils/URL'; +import { + KI_INVENTORY_FETCH_REQUEST, + KI_INVENTORY_FETCH_SUCCESS, + KI_INVENTORY_FETCH_FAILURE, + KI_INVENTORY_STATS_REQUEST, + KI_INVENTORY_STATS_SUCCESS, + KI_INVENTORY_STATS_FAILURE, + KI_PRESERVED_ITEMS_REQUEST, + KI_PRESERVED_ITEMS_SUCCESS, + KI_PRESERVED_ITEMS_FAILURE, +} from '../constants/KIInventoryConstants'; + +const createFetchAction = (requestType, successType, failureType, endpoint, defaultErrorMsg) => async dispatch => { + dispatch({ type: requestType }); + try { + const res = await axios.get(endpoint); + dispatch({ type: successType, payload: res.data.data }); + } catch (err) { + dispatch({ + type: failureType, + payload: err.response?.data?.message || defaultErrorMsg, + }); + } +}; + +/** + * Fetch all inventory items across all categories. + * GET /api/kitchenandinventory/inventory/items + */ +export const fetchInventoryItems = () => createFetchAction( + KI_INVENTORY_FETCH_REQUEST, + KI_INVENTORY_FETCH_SUCCESS, + KI_INVENTORY_FETCH_FAILURE, + ENDPOINTS.KI_INVENTORY_ITEMS, + 'Failed to fetch inventory items.' +); + +/** + * Fetch inventory stats — total items, critical stock count, low stock count. + * GET /api/kitchenandinventory/inventory/items/stats + */ +export const fetchInventoryStats = () => createFetchAction( + KI_INVENTORY_STATS_REQUEST, + KI_INVENTORY_STATS_SUCCESS, + KI_INVENTORY_STATS_FAILURE, + ENDPOINTS.KI_INVENTORY_STATS, + 'Failed to fetch inventory stats.' +); + +/** + * Fetch preserved ingredient items (expiry >= 1 year from now). + * GET /api/kitchenandinventory/inventory/items/ingredients/preserved + */ +export const fetchPreservedItems = () => createFetchAction( + KI_PRESERVED_ITEMS_REQUEST, + KI_PRESERVED_ITEMS_SUCCESS, + KI_PRESERVED_ITEMS_FAILURE, + ENDPOINTS.KI_INVENTORY_PRESERVED, + 'Failed to fetch preserved items.' +); diff --git a/src/components/KitchenandInventory/KIInventory/KIInventory.jsx b/src/components/KitchenandInventory/KIInventory/KIInventory.jsx index 395586d968..433ec34663 100644 --- a/src/components/KitchenandInventory/KIInventory/KIInventory.jsx +++ b/src/components/KitchenandInventory/KIInventory/KIInventory.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap'; import styles from './KIInventory.module.css'; import MetricCard from '../MetricCards/MetricCard'; @@ -20,20 +20,25 @@ import { import { RiLeafLine } from 'react-icons/ri'; import KIItemCard from './KIItemCard'; import { - ingredients, - preservedItems, - lowStock, - totalItems, - criticalStock, - onsiteGrown, - equipmentAndSupplies, - seeds, - canningSupplies, - animalSupplies, -} from './KIInventorySampleItems.js'; + fetchInventoryItems, + fetchInventoryStats, + fetchPreservedItems, +} from '../../../actions/KIInventoryActions'; + +// Category enum values — must match backend model enum exactly +const CATEGORY_MAP = { + ingredients: 'INGREDIENT', + 'equipment & supplies': 'EQUIPEMENTANDSUPPLIES', + seeds: 'SEEDS', + 'canning supplies': 'CANNINGSUPPLIES', + 'animal supplies': 'ANIMALSUPPLIES', +}; const KIInventory = () => { + const dispatch = useDispatch(); const darkMode = useSelector(state => state.theme.darkMode); + const { items, preservedItems, stats, loading } = useSelector(state => state.kiInventory); + const tabs = [ 'ingredients', 'equipment & supplies', @@ -43,19 +48,55 @@ const KIInventory = () => { ]; const [activeTab, setActiveTab] = useState(tabs[0]); const [searchTerm, setSearchTerm] = useState(''); + const toggleTab = tab => { - if (activeTab !== tabs[tab]) setActiveTab(tabs[tab]); + if (activeTab !== tabs[tab]) { + setActiveTab(tabs[tab]); + setSearchTerm(''); + } }; + + // Fetch all data on mount useEffect(() => { - // This is where you would fetch real data from an API or database - // For this example, we're using static sample data from KIInventorySampleItems.js - }, []); - let preservedDesc = []; - if (preservedItems.length > 0) { - preservedDesc = preservedItems.map( - item => `${item.presentQuantity} ${item.unit} of ${item.name}`, - ); - } + dispatch(fetchInventoryItems()); + dispatch(fetchInventoryStats()); + dispatch(fetchPreservedItems()); + }, [dispatch]); + + // Onsite grown — computed from all items + const onsiteGrown = items.filter(i => i.onsite).length; + + // Items for active tab filtered by category and search term + const activeCategory = CATEGORY_MAP[activeTab]; + const tabItems = items + .filter(i => i.category === activeCategory) + .filter(i => !searchTerm || i.name.toLowerCase().includes(searchTerm.toLowerCase())); + + // Preserved items description for notification banner + const preservedDesc = + preservedItems.length > 0 + ? preservedItems.map(item => `${item.presentQuantity} ${item.unit} of ${item.name}`) + : []; + + const renderItems = tabName => { + if (loading) { + return
Loading...
; + } + if (tabItems.length > 0) { + return tabItems.map(item => ( +No results for "{searchTerm}"
+ ); + } + returnNo items in {tabName} yet.
; + }; + return (Track ingredients, equipment, and supplies across all kitchen operations