diff --git a/src/actions/KIInventoryActions.js b/src/actions/KIInventoryActions.js
new file mode 100644
index 0000000000..51b60addae
--- /dev/null
+++ b/src/actions/KIInventoryActions.js
@@ -0,0 +1,64 @@
+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';
+
+/**
+ * Fetch all inventory items across all categories.
+ * GET /api/kitchenandinventory/inventory/items
+ */
+export const fetchInventoryItems = () => async dispatch => {
+ dispatch({ type: KI_INVENTORY_FETCH_REQUEST });
+ try {
+ const res = await axios.get(ENDPOINTS.KI_INVENTORY_ITEMS);
+ dispatch({ type: KI_INVENTORY_FETCH_SUCCESS, payload: res.data.data });
+ } catch (err) {
+ dispatch({
+ type: KI_INVENTORY_FETCH_FAILURE,
+ payload: err.response?.data?.message || '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 = () => async dispatch => {
+ dispatch({ type: KI_INVENTORY_STATS_REQUEST });
+ try {
+ const res = await axios.get(ENDPOINTS.KI_INVENTORY_STATS);
+ dispatch({ type: KI_INVENTORY_STATS_SUCCESS, payload: res.data.data });
+ } catch (err) {
+ dispatch({
+ type: KI_INVENTORY_STATS_FAILURE,
+ payload: err.response?.data?.message || 'Failed to fetch inventory stats.',
+ });
+ }
+};
+
+/**
+ * Fetch preserved ingredient items (expiry >= 1 year from now).
+ * GET /api/kitchenandinventory/inventory/items/ingredients/preserved
+ */
+export const fetchPreservedItems = () => async dispatch => {
+ dispatch({ type: KI_PRESERVED_ITEMS_REQUEST });
+ try {
+ const res = await axios.get(ENDPOINTS.KI_INVENTORY_PRESERVED);
+ dispatch({ type: KI_PRESERVED_ITEMS_SUCCESS, payload: res.data.data });
+ } catch (err) {
+ dispatch({
+ type: KI_PRESERVED_ITEMS_FAILURE,
+ payload: err.response?.data?.message || 'Failed to fetch preserved items.',
+ });
+ }
+};
diff --git a/src/components/KitchenandInventory/KIInventory/KIInventory.jsx b/src/components/KitchenandInventory/KIInventory/KIInventory.jsx
index 395586d968..214181c193 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,36 @@ 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}`)
+ : [];
+
return (
-
+
-
+
@@ -105,7 +131,7 @@ const KIInventory = () => {
onClick={() => toggleTab(1)}
>
- Equipment & Supplies
+ Equipment & Supplies
@@ -161,9 +187,7 @@ const KIInventory = () => {
type="text"
placeholder={`Search ${activeTab}...`}
value={searchTerm}
- onChange={e => {
- setSearchTerm(e.target.value);
- }}
+ onChange={e => setSearchTerm(e.target.value)}
/>