From 86a4a192093c35a076ea60c8ce2d26a281f15092 Mon Sep 17 00:00:00 2001 From: ferdiansyah783 Date: Sun, 3 Aug 2025 20:55:11 +0700 Subject: [PATCH] refactor and conneting api --- public/favicon.png | Bin 3960 -> 31965 bytes public/index.html | 2 +- src/core/modals/inventory/addcategorylist.jsx | 10 +- .../modals/inventory/editcategorylist.jsx | 8 +- src/core/modals/stocks/managestockModal.jsx | 473 ++++++++++-------- src/core/redux/actions/inventoryActions.js | 143 ++++++ src/core/redux/actions/outletActions.js | 138 +++++ src/core/redux/reducer.jsx | 6 +- src/core/redux/reducers/authReducer.js | 10 +- src/core/redux/reducers/inventoryReducer.js | 189 +++++++ src/core/redux/reducers/outletReducer.js | 212 ++++++++ src/feature-module/inventory/addproduct.jsx | 379 ++++++-------- src/feature-module/inventory/categorylist.jsx | 19 +- src/feature-module/inventory/editproduct.jsx | 385 ++++++-------- src/feature-module/inventory/outletlist.jsx | 388 ++++++++++++++ .../inventory/productdetail.jsx | 23 +- src/feature-module/inventory/productlist.jsx | 75 ++- src/feature-module/pages/login/signin.jsx | 34 +- src/feature-module/sales/saleslist.jsx | 19 +- src/feature-module/stock/managestock.jsx | 364 +++++++------- src/feature-module/stock/stockAdjustment.jsx | 326 ++++++------ src/services/filesApi.js | 35 ++ src/services/inventoriesApi.js | 60 +++ src/services/outletsApi.js | 102 ++++ src/style/scss/components/_tables.scss | 5 - src/style/scss/pages/_stocks.scss | 3 +- 26 files changed, 2277 insertions(+), 1131 deletions(-) create mode 100644 src/core/redux/actions/inventoryActions.js create mode 100644 src/core/redux/actions/outletActions.js create mode 100644 src/core/redux/reducers/inventoryReducer.js create mode 100644 src/core/redux/reducers/outletReducer.js create mode 100644 src/feature-module/inventory/outletlist.jsx create mode 100644 src/services/filesApi.js create mode 100644 src/services/inventoriesApi.js create mode 100644 src/services/outletsApi.js diff --git a/public/favicon.png b/public/favicon.png index e35e3e10b2a6ca21a987a13f03dbfdbb1df4ea60..36b59bff2328d9b2535418cebda28a09465ff75d 100644 GIT binary patch literal 31965 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 Tjt!_+4kInDAXX}B;QxOCbW(>@ literal 3960 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 - Dreams Pos admin template + APSKEL diff --git a/src/core/modals/inventory/addcategorylist.jsx b/src/core/modals/inventory/addcategorylist.jsx index 9a026b7..9ab84c8 100644 --- a/src/core/modals/inventory/addcategorylist.jsx +++ b/src/core/modals/inventory/addcategorylist.jsx @@ -60,13 +60,13 @@ const AddCategoryList = () => {
-
+

Create Category

@@ -77,7 +77,7 @@ const AddCategoryList = () => { { {
+ >
-
+ +
+ + { + handleSelectChange("outlet_id", selectedOption); + setSelectedOutlet(selectedOption); + }} + /> +
+
+ + { + handleSelectChange("product_id", selectedOption); + setSelectedProduct(selectedOption); + }} + /> +
+
+ + +
-
-
- -
-
-
- - -
-
-
-
- - - -
+
+ +
+
-
@@ -100,167 +267,79 @@ const ManageStockModal = () => { {/* /Add Stock */} {/* Edit Stock */}
-
+
-
+

Edit Stock

+ >
-
-
- + +
+ -
-
-
-
- - -
-
-
-
- - - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - -
ProductSKUCategoryQtyAction
-
- - - - - Nike Jordan - -
-
PT002Nike -
- - - - - - + - - -
-
-
- - - - - - -
-
-
-
-
+
+ +
+
+ + +
+
-
diff --git a/src/core/redux/actions/inventoryActions.js b/src/core/redux/actions/inventoryActions.js new file mode 100644 index 0000000..bd6f214 --- /dev/null +++ b/src/core/redux/actions/inventoryActions.js @@ -0,0 +1,143 @@ +import { inventoryApi } from '../../../services/inventoriesApi'; + +// Action Types +export const INVENTORY_ACTIONS = { + // Fetch Inventories + FETCH_INVENTORIES_REQUEST: 'FETCH_INVENTORIES_REQUEST', + FETCH_INVENTORIES_SUCCESS: 'FETCH_INVENTORIES_SUCCESS', + FETCH_INVENTORIES_FAILURE: 'FETCH_INVENTORIES_FAILURE', + + // Fetch Single Inventory + FETCH_INVENTORY_REQUEST: 'FETCH_INVENTORY_REQUEST', + FETCH_INVENTORY_SUCCESS: 'FETCH_INVENTORY_SUCCESS', + FETCH_INVENTORY_FAILURE: 'FETCH_INVENTORY_FAILURE', + + // Create Inventory + CREATE_INVENTORY_REQUEST: 'CREATE_INVENTORY_REQUEST', + CREATE_INVENTORY_SUCCESS: 'CREATE_INVENTORY_SUCCESS', + CREATE_INVENTORY_FAILURE: 'CREATE_INVENTORY_FAILURE', + + // Update Inventory + UPDATE_INVENTORY_REQUEST: 'UPDATE_INVENTORY_REQUEST', + UPDATE_INVENTORY_SUCCESS: 'UPDATE_INVENTORY_SUCCESS', + UPDATE_INVENTORY_FAILURE: 'UPDATE_INVENTORY_FAILURE', + + // Delete Inventory + DELETE_INVENTORY_REQUEST: 'DELETE_INVENTORY_REQUEST', + DELETE_INVENTORY_SUCCESS: 'DELETE_INVENTORY_SUCCESS', + DELETE_INVENTORY_FAILURE: 'DELETE_INVENTORY_FAILURE', + + // Search Inventories + SEARCH_INVENTORIES_REQUEST: 'SEARCH_INVENTORIES_REQUEST', + SEARCH_INVENTORIES_SUCCESS: 'SEARCH_INVENTORIES_SUCCESS', + SEARCH_INVENTORIES_FAILURE: 'SEARCH_INVENTORIES_FAILURE', + + // Clear States + CLEAR_INVENTORY_ERROR: 'CLEAR_INVENTORY_ERROR', + CLEAR_CURRENT_INVENTORY: 'CLEAR_CURRENT_INVENTORY', +}; + +// Action Creators + +export const fetchInventories = (params = {}) => async (dispatch) => { + dispatch({ type: INVENTORY_ACTIONS.FETCH_INVENTORIES_REQUEST }); + + try { + const data = await inventoryApi.getAllInventories(params); + dispatch({ + type: INVENTORY_ACTIONS.FETCH_INVENTORIES_SUCCESS, + payload: data, + }); + return data; + } catch (error) { + dispatch({ + type: INVENTORY_ACTIONS.FETCH_INVENTORIES_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to fetch inventories', + }); + throw error; + } +}; + +export const fetchInventory = (id) => async (dispatch) => { + dispatch({ type: INVENTORY_ACTIONS.FETCH_INVENTORY_REQUEST }); + + try { + const data = await inventoryApi.getInventoryById(id); + dispatch({ + type: INVENTORY_ACTIONS.FETCH_INVENTORY_SUCCESS, + payload: data, + }); + return data; + } catch (error) { + dispatch({ + type: INVENTORY_ACTIONS.FETCH_INVENTORY_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to fetch inventory', + }); + throw error; + } +}; + +export const createInventory = (inventoryData) => async (dispatch) => { + dispatch({ type: INVENTORY_ACTIONS.CREATE_INVENTORY_REQUEST }); + + try { + const data = await inventoryApi.createInventory(inventoryData); + dispatch({ + type: INVENTORY_ACTIONS.CREATE_INVENTORY_SUCCESS, + payload: data, + }); + return data; + } catch (error) { + dispatch({ + type: INVENTORY_ACTIONS.CREATE_INVENTORY_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to create inventory', + }); + throw error; + } +}; + +export const updateInventory = (id, inventoryData) => async (dispatch) => { + dispatch({ type: INVENTORY_ACTIONS.UPDATE_INVENTORY_REQUEST }); + + try { + const data = await inventoryApi.updateInventory(id, inventoryData); + dispatch({ + type: INVENTORY_ACTIONS.UPDATE_INVENTORY_SUCCESS, + payload: { id, data }, + }); + return data; + } catch (error) { + dispatch({ + type: INVENTORY_ACTIONS.UPDATE_INVENTORY_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to update inventory', + }); + throw error; + } +}; + +export const deleteInventory = (id) => async (dispatch) => { + dispatch({ type: INVENTORY_ACTIONS.DELETE_INVENTORY_REQUEST }); + + try { + await inventoryApi.deleteInventory(id); + dispatch({ + type: INVENTORY_ACTIONS.DELETE_INVENTORY_SUCCESS, + payload: id, + }); + return id; + } catch (error) { + dispatch({ + type: INVENTORY_ACTIONS.DELETE_INVENTORY_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to delete inventory', + }); + throw error; + } +}; + +export const clearInventoryError = () => ({ + type: INVENTORY_ACTIONS.CLEAR_INVENTORY_ERROR, +}); + +export const clearCurrentInventory = () => ({ + type: INVENTORY_ACTIONS.CLEAR_CURRENT_INVENTORY, +}); diff --git a/src/core/redux/actions/outletActions.js b/src/core/redux/actions/outletActions.js new file mode 100644 index 0000000..3a6185e --- /dev/null +++ b/src/core/redux/actions/outletActions.js @@ -0,0 +1,138 @@ +import { outletsApi } from '../../../services/outletsApi'; + +// Action Types +export const OUTLET_ACTIONS = { + // Fetch Outlets + FETCH_OUTLETS_REQUEST: 'FETCH_OUTLETS_REQUEST', + FETCH_OUTLETS_SUCCESS: 'FETCH_OUTLETS_SUCCESS', + FETCH_OUTLETS_FAILURE: 'FETCH_OUTLETS_FAILURE', + + // Fetch Single Outlet + FETCH_OUTLET_REQUEST: 'FETCH_OUTLET_REQUEST', + FETCH_OUTLET_SUCCESS: 'FETCH_OUTLET_SUCCESS', + FETCH_OUTLET_FAILURE: 'FETCH_OUTLET_FAILURE', + + // Create Outlet + CREATE_OUTLET_REQUEST: 'CREATE_OUTLET_REQUEST', + CREATE_OUTLET_SUCCESS: 'CREATE_OUTLET_SUCCESS', + CREATE_OUTLET_FAILURE: 'CREATE_OUTLET_FAILURE', + + // Update Outlet + UPDATE_OUTLET_REQUEST: 'UPDATE_OUTLET_REQUEST', + UPDATE_OUTLET_SUCCESS: 'UPDATE_OUTLET_SUCCESS', + UPDATE_OUTLET_FAILURE: 'UPDATE_OUTLET_FAILURE', + + // Delete Outlet + DELETE_OUTLET_REQUEST: 'DELETE_OUTLET_REQUEST', + DELETE_OUTLET_SUCCESS: 'DELETE_OUTLET_SUCCESS', + DELETE_OUTLET_FAILURE: 'DELETE_OUTLET_FAILURE', + + // Clear States + CLEAR_OUTLET_ERROR: 'CLEAR_OUTLET_ERROR', + CLEAR_CURRENT_OUTLET: 'CLEAR_CURRENT_OUTLET', +}; + +// Action Creators + +export const fetchOutlets = (params = {}) => async (dispatch) => { + dispatch({ type: OUTLET_ACTIONS.FETCH_OUTLETS_REQUEST }); + + try { + const data = await outletsApi.getAllOutlets(params); + dispatch({ + type: OUTLET_ACTIONS.FETCH_OUTLETS_SUCCESS, + payload: data, + }); + return data; + } catch (error) { + dispatch({ + type: OUTLET_ACTIONS.FETCH_OUTLETS_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to fetch outlets', + }); + throw error; + } +}; + +export const fetchOutlet = (id) => async (dispatch) => { + dispatch({ type: OUTLET_ACTIONS.FETCH_OUTLET_REQUEST }); + + try { + const data = await outletsApi.getOutletById(id); + dispatch({ + type: OUTLET_ACTIONS.FETCH_OUTLET_SUCCESS, + payload: data, + }); + return data; + } catch (error) { + dispatch({ + type: OUTLET_ACTIONS.FETCH_OUTLET_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to fetch outlet', + }); + throw error; + } +}; + +export const createOutlet = (outletData) => async (dispatch) => { + dispatch({ type: OUTLET_ACTIONS.CREATE_OUTLET_REQUEST }); + + try { + const data = await outletsApi.createOutlet(outletData); + dispatch({ + type: OUTLET_ACTIONS.CREATE_OUTLET_SUCCESS, + payload: data, + }); + return data; + } catch (error) { + dispatch({ + type: OUTLET_ACTIONS.CREATE_OUTLET_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to create outlet', + }); + throw error; + } +}; + +export const updateOutlet = (id, outletData) => async (dispatch) => { + dispatch({ type: OUTLET_ACTIONS.UPDATE_OUTLET_REQUEST }); + + try { + const data = await outletsApi.updateOutlet(id, outletData); + dispatch({ + type: OUTLET_ACTIONS.UPDATE_OUTLET_SUCCESS, + payload: { id, data }, + }); + return data; + } catch (error) { + dispatch({ + type: OUTLET_ACTIONS.UPDATE_OUTLET_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to update outlet', + }); + throw error; + } +}; + +export const deleteOutlet = (id) => async (dispatch) => { + dispatch({ type: OUTLET_ACTIONS.DELETE_OUTLET_REQUEST }); + + try { + await outletsApi.deleteOutlet(id); + dispatch({ + type: OUTLET_ACTIONS.DELETE_OUTLET_SUCCESS, + payload: id, + }); + return id; + } catch (error) { + dispatch({ + type: OUTLET_ACTIONS.DELETE_OUTLET_FAILURE, + payload: error.response?.data?.message || error.message || 'Failed to delete outlet', + }); + throw error; + } +}; + +export const clearOutletError = () => ({ + type: OUTLET_ACTIONS.CLEAR_OUTLET_ERROR, +}); + +export const clearCurrentOutlet = () => ({ + type: OUTLET_ACTIONS.CLEAR_CURRENT_OUTLET, +}); diff --git a/src/core/redux/reducer.jsx b/src/core/redux/reducer.jsx index e3f10a9..9633017 100644 --- a/src/core/redux/reducer.jsx +++ b/src/core/redux/reducer.jsx @@ -6,6 +6,8 @@ import categoryReducer from './reducers/categoryReducer'; import orderReducer from './reducers/orderReducer'; import paymentMethodReducer from './reducers/paymentMethodReducer'; import organizationReducer from './reducers/organizationReducer'; +import inventoryReducer from './reducers/inventoryReducer'; +import outletReducer from './reducers/outletReducer'; // Legacy reducer for existing functionality const legacyReducer = (state = initialState, action) => { @@ -82,7 +84,9 @@ const rootReducer = combineReducers({ categories: categoryReducer, orders: orderReducer, paymentMethods: paymentMethodReducer, - organizations: organizationReducer + organizations: organizationReducer, + inventories: inventoryReducer, + outlets: outletReducer }); export default rootReducer; diff --git a/src/core/redux/reducers/authReducer.js b/src/core/redux/reducers/authReducer.js index 71d8d05..20cf6da 100644 --- a/src/core/redux/reducers/authReducer.js +++ b/src/core/redux/reducers/authReducer.js @@ -18,17 +18,17 @@ const authSlice = createSlice({ }, loginSuccess: (state, action) => { state.isAuthenticated = true; - state.user = action.payload.user; - state.token = action.payload.token; + state.user = action.payload.data.user; + state.token = action.payload.data.token; state.loading = false; state.error = null; // Store token in localStorage - localStorage.setItem('authToken', action.payload.token); - localStorage.setItem('user', JSON.stringify(action.payload.user)); + localStorage.setItem('authToken', action.payload.data.token); + localStorage.setItem('user', JSON.stringify(action.payload.data.user)); }, loginFailure: (state, action) => { state.loading = false; - state.error = action.payload; + state.error = action.payload.error; state.isAuthenticated = false; state.user = null; state.token = null; diff --git a/src/core/redux/reducers/inventoryReducer.js b/src/core/redux/reducers/inventoryReducer.js new file mode 100644 index 0000000..f41ef42 --- /dev/null +++ b/src/core/redux/reducers/inventoryReducer.js @@ -0,0 +1,189 @@ +import { INVENTORY_ACTIONS } from '../actions/inventoryActions'; + +const initialState = { + // Inventory list + inventories: [], + totalInventories: 0, + currentPage: 1, + totalPages: 1, + pageSize: 10, + hasPrevious: false, + hasNext: false, + + // Current inventory (for edit/view) + currentInventory: null, + + // Search results + searchResults: [], + searchQuery: '', + + // Loading states + loading: false, + inventoryLoading: false, + searchLoading: false, + + // Error states + error: null, + inventoryError: null, + searchError: null, + + // Operation states + creating: false, + updating: false, + deleting: false, +}; + +const inventoryReducer = (state = initialState, action) => { + switch (action.type) { + // Fetch Inventories + case INVENTORY_ACTIONS.FETCH_INVENTORIES_REQUEST: + return { + ...state, + loading: true, + error: null, + }; + + case INVENTORY_ACTIONS.FETCH_INVENTORIES_SUCCESS: { + const { inventory, total_count, page, total_pages, limit } = + action.payload.data; + + return { + ...state, + loading: false, + inventories: inventory, + totalInventories: total_count || inventory.length, + currentPage: page || 1, + totalPages: total_pages || 1, + pageSize: limit || 10, + hasPrevious: false, + hasNext: false, + error: null, + }; + } + + case INVENTORY_ACTIONS.FETCH_INVENTORIES_FAILURE: + return { + ...state, + loading: false, + error: action.payload, + }; + + // Fetch Single Inventory + case INVENTORY_ACTIONS.FETCH_INVENTORY_REQUEST: + return { + ...state, + inventoryLoading: true, + inventoryError: null, + }; + + case INVENTORY_ACTIONS.FETCH_INVENTORY_SUCCESS: + return { + ...state, + inventoryLoading: false, + currentInventory: action.payload.data, + inventoryError: null, + }; + + case INVENTORY_ACTIONS.FETCH_INVENTORY_FAILURE: + return { + ...state, + inventoryLoading: false, + inventoryError: action.payload, + }; + + // Create Inventory + case INVENTORY_ACTIONS.CREATE_INVENTORY_REQUEST: + return { + ...state, + creating: true, + error: null, + }; + + case INVENTORY_ACTIONS.CREATE_INVENTORY_SUCCESS: + return { + ...state, + creating: false, + inventories: [action.payload.data, ...state.inventories], + totalInventories: state.totalInventories + 1, + error: null, + }; + + case INVENTORY_ACTIONS.CREATE_INVENTORY_FAILURE: + return { + ...state, + creating: false, + error: action.payload, + }; + + // Update Inventory + case INVENTORY_ACTIONS.UPDATE_INVENTORY_REQUEST: + return { + ...state, + updating: true, + error: null, + }; + + case INVENTORY_ACTIONS.UPDATE_INVENTORY_SUCCESS: + return { + ...state, + updating: false, + inventories: state.inventories.map(inv => + inv.id === action.payload.data.id ? action.payload.data : inv + ), + currentInventory: action.payload.data, + error: null, + }; + + case INVENTORY_ACTIONS.UPDATE_INVENTORY_FAILURE: + return { + ...state, + updating: false, + error: action.payload, + }; + + // Delete Inventory + case INVENTORY_ACTIONS.DELETE_INVENTORY_REQUEST: + return { + ...state, + deleting: true, + error: null, + }; + + case INVENTORY_ACTIONS.DELETE_INVENTORY_SUCCESS: + return { + ...state, + deleting: false, + inventories: state.inventories.filter(inv => inv.id !== action.payload), + totalInventories: state.totalInventories - 1, + error: null, + }; + + case INVENTORY_ACTIONS.DELETE_INVENTORY_FAILURE: + return { + ...state, + deleting: false, + error: action.payload, + }; + + // Clear states + case INVENTORY_ACTIONS.CLEAR_INVENTORY_ERROR: + return { + ...state, + error: null, + inventoryError: null, + searchError: null, + }; + + case INVENTORY_ACTIONS.CLEAR_CURRENT_INVENTORY: + return { + ...state, + currentInventory: null, + inventoryError: null, + }; + + default: + return state; + } +}; + +export default inventoryReducer; diff --git a/src/core/redux/reducers/outletReducer.js b/src/core/redux/reducers/outletReducer.js new file mode 100644 index 0000000..fec3365 --- /dev/null +++ b/src/core/redux/reducers/outletReducer.js @@ -0,0 +1,212 @@ +import { OUTLET_ACTIONS } from '../actions/outletActions'; + +const initialState = { + // Outlets list + outlets: [], + totalOutlets: 0, + currentPage: 1, + totalPages: 1, + pageSize: 10, + hasPrevious: false, + hasNext: false, + + // Current outlet (for edit/view) + currentOutlet: null, + + // Search results + searchResults: [], + searchQuery: '', + + // Loading states + loading: false, + outletLoading: false, + searchLoading: false, + + // Error states + error: null, + outletError: null, + searchError: null, + + // Operation states + creating: false, + updating: false, + deleting: false, +}; + +const outletReducer = (state = initialState, action) => { + switch (action.type) { + // Fetch Outlets + case OUTLET_ACTIONS.FETCH_OUTLETS_REQUEST: + return { + ...state, + loading: true, + error: null, + }; + + case OUTLET_ACTIONS.FETCH_OUTLETS_SUCCESS: { + const { outlets, total_count, page, total_pages, limit } = action.payload.data; + + return { + ...state, + loading: false, + outlets: outlets, + totalOutlets: total_count || outlets.length, + currentPage: page || 1, + totalPages: total_pages || 1, + pageSize: limit || 10, + hasPrevious: false, + hasNext: false, + error: null, + }; + } + + case OUTLET_ACTIONS.FETCH_OUTLETS_FAILURE: + return { + ...state, + loading: false, + error: action.payload, + }; + + // Fetch Single Outlet + case OUTLET_ACTIONS.FETCH_OUTLET_REQUEST: + return { + ...state, + outletLoading: true, + outletError: null, + }; + + case OUTLET_ACTIONS.FETCH_OUTLET_SUCCESS: + return { + ...state, + outletLoading: false, + currentOutlet: action.payload.data, + outletError: null, + }; + + case OUTLET_ACTIONS.FETCH_OUTLET_FAILURE: + return { + ...state, + outletLoading: false, + outletError: action.payload, + }; + + // Create Outlet + case OUTLET_ACTIONS.CREATE_OUTLET_REQUEST: + return { + ...state, + creating: true, + error: null, + }; + + case OUTLET_ACTIONS.CREATE_OUTLET_SUCCESS: + return { + ...state, + creating: false, + outlets: [action.payload.data, ...state.outlets], + totalOutlets: state.totalOutlets + 1, + error: null, + }; + + case OUTLET_ACTIONS.CREATE_OUTLET_FAILURE: + return { + ...state, + creating: false, + error: action.payload, + }; + + // Update Outlet + case OUTLET_ACTIONS.UPDATE_OUTLET_REQUEST: + return { + ...state, + updating: true, + error: null, + }; + + case OUTLET_ACTIONS.UPDATE_OUTLET_SUCCESS: + return { + ...state, + updating: false, + outlets: state.outlets.map((outlet) => + outlet.id === action.payload.data.id ? action.payload.data : outlet + ), + currentOutlet: action.payload.data, + error: null, + }; + + case OUTLET_ACTIONS.UPDATE_OUTLET_FAILURE: + return { + ...state, + updating: false, + error: action.payload, + }; + + // Delete Outlet + case OUTLET_ACTIONS.DELETE_OUTLET_REQUEST: + return { + ...state, + deleting: true, + error: null, + }; + + case OUTLET_ACTIONS.DELETE_OUTLET_SUCCESS: + return { + ...state, + deleting: false, + outlets: state.outlets.filter((outlet) => outlet.id !== action.payload), + totalOutlets: state.totalOutlets - 1, + error: null, + }; + + case OUTLET_ACTIONS.DELETE_OUTLET_FAILURE: + return { + ...state, + deleting: false, + error: action.payload, + }; + + // Search Outlets + case OUTLET_ACTIONS.SEARCH_OUTLETS_REQUEST: + return { + ...state, + searchLoading: true, + searchError: null, + }; + + case OUTLET_ACTIONS.SEARCH_OUTLETS_SUCCESS: + return { + ...state, + searchLoading: false, + searchResults: action.payload.data || action.payload, + searchQuery: action.payload.query || '', + searchError: null, + }; + + case OUTLET_ACTIONS.SEARCH_OUTLETS_FAILURE: + return { + ...state, + searchLoading: false, + searchError: action.payload, + }; + + // Clear States + case OUTLET_ACTIONS.CLEAR_OUTLET_ERROR: + return { + ...state, + error: null, + outletError: null, + searchError: null, + }; + + case OUTLET_ACTIONS.CLEAR_CURRENT_OUTLET: + return { + ...state, + currentOutlet: null, + outletError: null, + }; + + default: + return state; + } +}; + +export default outletReducer; diff --git a/src/feature-module/inventory/addproduct.jsx b/src/feature-module/inventory/addproduct.jsx index be9b0b9..65fb835 100644 --- a/src/feature-module/inventory/addproduct.jsx +++ b/src/feature-module/inventory/addproduct.jsx @@ -20,13 +20,12 @@ import { Link, useNavigate } from "react-router-dom"; import Select from "react-select"; import Swal from "sweetalert2"; import ImageWithBasePath from "../../core/img/imagewithbasebath"; -import AddBrand from "../../core/modals/addbrand"; import AddCategory from "../../core/modals/inventory/addcategory"; -import Addunits from "../../core/modals/inventory/addunits"; import { setToogleHeader } from "../../core/redux/action"; import { createProduct } from "../../core/redux/actions/productActions"; import { all_routes } from "../../Router/all_routes"; import { categoriesApi } from "../../services/categoriesApi"; +import { filesApi } from "../../services/filesApi"; const AddProduct = () => { const route = all_routes; @@ -48,14 +47,19 @@ const AddProduct = () => { const [formData, setFormData] = useState({ name: "", sku: "", + barcode: "", description: "", price: 0, cost: 0, category_id: "", + printer_type: "", + image_url: "", }); + const [tempImage, setTempImage] = useState(null); + const [uploading, setUploading] = useState(false); const [variants, setVariants] = useState([ - { name: "", price_modifier: 0, cost: 0 }, + { name: "", price_modifier: '', cost: '' }, ]); const handleInputChange = (e) => { @@ -65,6 +69,38 @@ const AddProduct = () => { }); }; + const handleFileChange = async (e) => { + setTempImage(e.target.files[0]); + }; + + useEffect(() => { + const loadImage = async () => { + setUploading(true); + + try { + const uploads = new FormData(); + + uploads.append("file", tempImage); + uploads.append("file_type", "image"); + uploads.append("description", "Product image"); + + const response = await filesApi.uploadFile(uploads); + + setFormData({ + ...formData, + image_url: response?.data?.file_url, + }); + + setUploading(false); + } catch (error) { + console.error("Error fetching image:", error); + setUploading(false); + } + }; + + loadImage(); + }, [tempImage]); + useEffect(() => { const loadCategories = async () => { try { @@ -90,32 +126,11 @@ const AddProduct = () => { { value: "rasmussen", label: "Rasmussen" }, { value: "fredJohn", label: "Fred John" }, ]; - const warehouse = [ - { value: "choose", label: "Choose" }, - { value: "legendary", label: "Legendary" }, + const printerTypes = [ + { value: "Kitchen", label: "Kitchen" }, { value: "determined", label: "Determined" }, { value: "sincere", label: "Sincere" }, ]; - const subcategory = [ - { value: "choose", label: "Choose" }, - { value: "lenovo", label: "Lenovo" }, - { value: "electronics", label: "Electronics" }, - ]; - const brand = [ - { value: "choose", label: "Choose" }, - { value: "nike", label: "Nike" }, - { value: "bolt", label: "Bolt" }, - ]; - const unit = [ - { value: "choose", label: "Choose" }, - { value: "kg", label: "Kg" }, - { value: "pc", label: "Pc" }, - ]; - const sellingtype = [ - { value: "choose", label: "Choose" }, - { value: "transactionalSelling", label: "Transactional selling" }, - { value: "solutionSelling", label: "Solution selling" }, - ]; const barcodesymbol = [ { value: "choose", label: "Choose" }, { value: "code34", label: "Code34" }, @@ -136,15 +151,9 @@ const AddProduct = () => { { value: "percentage", label: "Percentage" }, { value: "cash", label: "Cash" }, ]; - const [isImageVisible, setIsImageVisible] = useState(true); - - const handleRemoveProduct = () => { - setIsImageVisible(false); - }; - const [isImageVisible1, setIsImageVisible1] = useState(true); const handleRemoveProduct1 = () => { - setIsImageVisible1(false); + setFormData({ ...formData, image_url: "" }); }; // Handle form submission @@ -229,11 +238,17 @@ const AddProduct = () => { Swal.fire({ icon: "error", title: "Error!", - text: error?.response?.data?.errors[0].cause || "Failed to create product. Please try again.", + text: + error?.response?.data?.errors[0].cause || + "Failed to create product. Please try again.", }); } }; + // const handleUploadImage = () => { + + // } + // Handle select changes const handleSelectChange = (field, selectedOption) => { setFormData({ @@ -350,29 +365,6 @@ const AddProduct = () => { data-bs-parent="#accordionExample" >
-
-
-
- - -
-
-
-
@@ -386,12 +378,58 @@ const AddProduct = () => { />
+
+
+ + Store + + option.value === formData.printer_type + ) || null + } + onChange={(selectedOption) => + handleSelectChange( + "printer_type", + selectedOption + ) + } />
@@ -429,164 +467,38 @@ const AddProduct = () => {
- +
+ + + + Add New + +
- option.value === formData.category_id - ) || null - } - onChange={(selectedOption) => - handleSelectChange( - "category_id", - selectedOption - ) - } - /> -
-
-
-
- - -
-
-
-
-
- - - - Add New - -
- -
-
-
- -
- - -
-
- {/*
-
- - - - Generate Code - -
-
*/} -
{/* Editor */}
@@ -935,17 +847,36 @@ const AddProduct = () => {
- +
- -

Add Images

+ {uploading ? ( + <> + +

Uploading...

+ + ) : ( + <> + +

+ {tempImage ? "Edit Image" : "Add Image"} +

+ + )}
- {isImageVisible1 && ( + {formData.image_url && (
@@ -956,20 +887,6 @@ const AddProduct = () => {
)} - {isImageVisible && ( -
- - - - -
- )}
@@ -1131,9 +1048,7 @@ const AddProduct = () => { {/* /add */}
- -
); }; diff --git a/src/feature-module/inventory/categorylist.jsx b/src/feature-module/inventory/categorylist.jsx index b5a1b17..21212d5 100644 --- a/src/feature-module/inventory/categorylist.jsx +++ b/src/feature-module/inventory/categorylist.jsx @@ -235,24 +235,7 @@ const CategoryList = () => { }, ]; const MySwal = withReactContent(Swal); - - // const showConfirmationAlert = () => { - // MySwal.fire({ - // title: "Are you sure?", - // text: "You won't be able to revert this!", - // showCancelButton: true, - // confirmButtonColor: "#00ff00", - // confirmButtonText: "Yes, delete it!", - // cancelButtonColor: "#ff0000", - // cancelButtonText: "Cancel", - // }).then((result) => { - // if (result.isConfirmed) { - // handleDeleteCategory(); - // } else { - // MySwal.close(); - // } - // }); - // }; + return (
diff --git a/src/feature-module/inventory/editproduct.jsx b/src/feature-module/inventory/editproduct.jsx index ac896aa..f6c7652 100644 --- a/src/feature-module/inventory/editproduct.jsx +++ b/src/feature-module/inventory/editproduct.jsx @@ -20,9 +20,7 @@ import { Link, useNavigate, useParams } from "react-router-dom"; import Select from "react-select"; import Swal from "sweetalert2"; import ImageWithBasePath from "../../core/img/imagewithbasebath"; -import AddBrand from "../../core/modals/addbrand"; import AddCategory from "../../core/modals/inventory/addcategory"; -import Addunits from "../../core/modals/inventory/addunits"; import { setToogleHeader } from "../../core/redux/action"; import { fetchProduct, @@ -30,6 +28,7 @@ import { } from "../../core/redux/actions/productActions"; import { all_routes } from "../../Router/all_routes"; import categoriesApi from "../../services/categoriesApi"; +import { filesApi } from "../../services/filesApi"; const EditProduct = () => { const route = all_routes; @@ -52,16 +51,53 @@ const EditProduct = () => { const [formData, setFormData] = useState({ name: "", sku: "", + barcode: "", description: "", price: 0, - category_id: null, cost: 0, + category_id: "", + printer_type: "", + image_url: "", }); + const [tempImage, setTempImage] = useState(null); + const [uploading, setUploading] = useState(false); const [variants, setVariants] = useState([ - { name: "", price_modifier: 0, cost: 0 }, + { name: "", price_modifier: '', cost: '' }, ]); + const handleFileChange = async (e) => { + setTempImage(e.target.files[0]); + }; + + useEffect(() => { + const loadImage = async () => { + setUploading(true); + + try { + const uploads = new FormData(); + + uploads.append("file", tempImage); + uploads.append("file_type", "image"); + uploads.append("description", "Product image"); + + const response = await filesApi.uploadFile(uploads); + + setFormData({ + ...formData, + image_url: response?.data?.file_url, + }); + + setUploading(false); + } catch (error) { + console.error("Error fetching image:", error); + setUploading(false); + } + }; + + loadImage(); + }, [tempImage]); + useEffect(() => { const loadCategories = async () => { try { @@ -98,6 +134,9 @@ const EditProduct = () => { price: currentProduct.price || 0, cost: currentProduct.cost || 0, category_id: currentProduct.category_id, + printer_type: currentProduct.printer_type, + image_url: currentProduct.image_url, + barcode: currentProduct.barcode, }); if (currentProduct.variants) { @@ -134,7 +173,7 @@ const EditProduct = () => { }; const addVariant = () => { - setVariants([...variants, { name: "", price_modifier: 0, cost: 0 }]); + setVariants([...variants, { name: "", price_modifier: '', cost: '' }]); }; const removeVariant = (index) => { @@ -221,7 +260,7 @@ const EditProduct = () => { Swal.fire({ icon: "success", title: "Success!", - text: "Product created successfully!", + text: "Product updated successfully!", showConfirmButton: false, timer: 1500, }); @@ -237,7 +276,7 @@ const EditProduct = () => { Swal.fire({ icon: "error", title: "Error!", - text: error.message || "Failed to create product. Please try again.", + text: error.message || "Failed to update product. Please try again.", }); } }; @@ -248,43 +287,16 @@ const EditProduct = () => { { value: "rasmussen", label: "Rasmussen" }, { value: "fredJohn", label: "Fred John" }, ]; - const warehouse = [ - { value: "choose", label: "Choose" }, - { value: "legendary", label: "Legendary" }, - { value: "determined", label: "Determined" }, - { value: "sincere", label: "Sincere" }, - ]; - const subcategory = [ - { value: "choose", label: "Choose" }, - { value: "lenovo", label: "Lenovo" }, - { value: "electronics", label: "Electronics" }, - ]; - const brand = [ - { value: "choose", label: "Choose" }, - { value: "nike", label: "Nike" }, - { value: "bolt", label: "Bolt" }, - ]; - const unit = [ - { value: "choose", label: "Choose" }, - { value: "kg", label: "Kg" }, - { value: "pc", label: "Pc" }, - { value: "pcs", label: "Pcs" }, - { value: "piece", label: "Piece" }, - { value: "gram", label: "Gram" }, - { value: "liter", label: "Liter" }, - { value: "meter", label: "Meter" }, - ]; - const sellingtype = [ - { value: "choose", label: "Choose" }, - { value: "transactionalSelling", label: "Transactional selling" }, - { value: "solutionSelling", label: "Solution selling" }, - ]; const barcodesymbol = [ - { value: "choose", label: "Choose" }, { value: "code34", label: "Code34" }, { value: "code35", label: "Code35" }, { value: "code36", label: "Code36" }, ]; + const printerTypes = [ + { value: "Kitchen", label: "Kitchen" }, + { value: "determined", label: "Determined" }, + { value: "sincere", label: "Sincere" }, + ]; const taxtype = [ { value: "exclusive", label: "Exclusive" }, { value: "salesTax", label: "Sales Tax" }, @@ -299,15 +311,9 @@ const EditProduct = () => { { value: "percentage", label: "Percentage" }, { value: "cash", label: "Cash" }, ]; - const [isImageVisible, setIsImageVisible] = useState(true); - - const handleRemoveProduct = () => { - setIsImageVisible(false); - }; - const [isImageVisible1, setIsImageVisible1] = useState(true); const handleRemoveProduct1 = () => { - setIsImageVisible1(false); + setFormData({ ...formData, image_url: "" }); }; // Show loading state @@ -413,7 +419,7 @@ const EditProduct = () => {
- {/* /add */} + {/* /edit */}
@@ -448,29 +454,6 @@ const EditProduct = () => { data-bs-parent="#accordionExample" >
-
-
-
- - -
-
-
-
@@ -484,12 +467,58 @@ const EditProduct = () => { />
+
+
+ + Store + + option.value === formData.printer_type + ) || null + } + onChange={(selectedOption) => + handleSelectChange( + "printer_type", + selectedOption + ) + } />
@@ -527,150 +556,38 @@ const EditProduct = () => {
- +
+ + + + Add New + +
- option.value === formData.category_id - ) - : null - } - onChange={(selectedOption) => - handleSelectChange( - "category_id", - selectedOption - ) - } - /> -
-
-
-
- - -
-
-
-
-
- - - - Add New - -
- -
-
-
- -
- - -
-
-
{/* Editor */}
@@ -774,6 +691,7 @@ const EditProduct = () => {
+
{
@@ -1018,17 +936,36 @@ const EditProduct = () => {
- +
- -

Add Images

+ {uploading ? ( + <> + +

Uploading...

+ + ) : ( + <> + +

+ {tempImage ? "Edit Image" : "Add Image"} +

+ + )}
- {isImageVisible1 && ( + {formData.image_url && (
@@ -1039,20 +976,6 @@ const EditProduct = () => {
)} - {isImageVisible && ( -
- - - - -
- )}
@@ -1214,9 +1137,7 @@ const EditProduct = () => { {/* /add */}
- -
); }; diff --git a/src/feature-module/inventory/outletlist.jsx b/src/feature-module/inventory/outletlist.jsx new file mode 100644 index 0000000..a0b4d50 --- /dev/null +++ b/src/feature-module/inventory/outletlist.jsx @@ -0,0 +1,388 @@ +import { Select, Tag } from "antd"; +import { + ChevronUp, + PlusCircle, + RotateCcw, + Trash2, +} from "feather-icons-react/build/IconComponents"; +import { useEffect, useState } from "react"; +import { OverlayTrigger, Tooltip } from "react-bootstrap"; +import { useDispatch, useSelector } from "react-redux"; +import { Link } from "react-router-dom"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import CustomPagination from "../../components/CustomPagination"; +import ImageWithBasePath from "../../core/img/imagewithbasebath"; +import AddCategoryList from "../../core/modals/inventory/addcategorylist"; +import EditCategoryList from "../../core/modals/inventory/editcategorylist"; +import Table from "../../core/pagination/datatable"; +import { setToogleHeader } from "../../core/redux/action"; +import { + deleteCategory, + fetchCategories, + fetchCategory, +} from "../../core/redux/actions/categoryActions"; +import { formatDate } from "../../utils/date"; + +const OutletList = () => { + const { + categories: apiCategories, + loading, + error, + totalCategories, + totalPages, + pageSize: reduxPageSize, + currentPage: reduxCurrentPage, + } = useSelector((state) => state.categories); + + const dispatch = useDispatch(); + const data = useSelector((state) => state.toggle_header); + const dataSource = apiCategories?.length > 0 ? apiCategories : []; + + const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1); + const [pageSize, setPageSize] = useState(reduxPageSize || 10); + const [searchTerm, setSearchTerm] = useState(""); + + const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); + + useEffect(() => { + const loadCategories = async () => { + try { + const searchParams = { + page: currentPage, + limit: pageSize, + search: debouncedSearchTerm || "", + }; + + // Remove empty parameters + const cleanParams = Object.fromEntries( + Object.entries(searchParams).filter(([, value]) => value !== "") + ); + + await dispatch(fetchCategories(cleanParams)); + } catch (error) { + console.error("Failed to load categories", error); + } + }; + + loadCategories(); + }, [dispatch, currentPage, pageSize, debouncedSearchTerm]); + + // Debounce search term + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchTerm(searchTerm); + }, 500); // 500ms delay + + return () => clearTimeout(timer); + }, [searchTerm]); + + // Handle pagination + const handlePageChange = (page) => { + setCurrentPage(page); + }; + + // Handle page size change + const handlePageSizeChange = (newPageSize) => { + setPageSize(newPageSize); + setCurrentPage(1); // Reset to first page when changing page size + }; + + const handleSearch = (e) => { + const value = e.target.value; + setSearchTerm(value); + // Reset to first page when searching + setCurrentPage(1); + }; + + // Calculate pagination info + const totalRecords = totalCategories || dataSource.length; + const calculatedTotalPages = Math.ceil(totalRecords / pageSize); + const actualTotalPages = totalPages || calculatedTotalPages; + + const handleDeleteCategory = async (categoryId) => { + try { + await dispatch(deleteCategory(categoryId)); + // Show success message + MySwal.fire({ + title: "Deleted!", + text: "Category has been deleted successfully.", + icon: "success", + className: "btn btn-success", + customClass: { + confirmButton: "btn btn-success", + }, + }); + } catch (error) { + console.error("Failed to delete category:", error); + MySwal.fire({ + title: "Error!", + text: "Failed to delete category. Please try again.", + icon: "error", + className: "btn btn-danger", + customClass: { + confirmButton: "btn btn-danger", + }, + }); + } + }; + + const renderTooltip = (props) => ( + + Pdf + + ); + const renderExcelTooltip = (props) => ( + + Excel + + ); + const renderPrinterTooltip = (props) => ( + + Printer + + ); + const renderRefreshTooltip = (props) => ( + + Refresh + + ); + const renderCollapseTooltip = (props) => ( + + Collapse + + ); + + const dateOptions = [ + { label: "Sort By: Last 7 Days", value: "last7days" }, + { label: "Sort By: Last Month", value: "lastmonth" }, + { label: "Sort By: Ascending", value: "ascending" }, + { label: "Sort By: Descending", value: "descending" }, + ]; + + const columns = [ + { + title: "Category", + dataIndex: "category", + render: (_, record) => { + return {record.name}; + }, + sorter: (a, b) => a.category.length - b.category.length, + }, + { + title: "Category Slug", + dataIndex: "categoryslug", + render: (_, record) => { + return {record?.name?.toLowerCase()}; + }, + sorter: (a, b) => a.categoryslug.length - b.categoryslug.length, + }, + { + title: "Created On", + dataIndex: "createdon", + render: (_, record) => { + return {formatDate(record.created_at)}; + }, + sorter: (a, b) => a.createdon.length - b.createdon.length, + }, + { + title: "Status", + dataIndex: "status", + render: () => active, + sorter: (a, b) => a.status.length - b.status.length, + }, + { + title: "Actions", + dataIndex: "actions", + key: "actions", + render: (_, record) => ( + +
+ dispatch(fetchCategory(record.id))} + > + + + { + e.preventDefault(); + MySwal.fire({ + title: "Are you sure?", + text: "You won't be able to revert this!", + showCancelButton: true, + confirmButtonColor: "#00ff00", + confirmButtonText: "Yes, delete it!", + cancelButtonColor: "#ff0000", + cancelButtonText: "Cancel", + }).then((result) => { + if (result.isConfirmed) { + handleDeleteCategory(record.id || record.key); + } + }); + }} + > + + +
+ + ), + }, + ]; + const MySwal = withReactContent(Swal); + + return ( +
+
+
+
+
+
+

Category

+
Manage your categories
+
+
+
    +
  • + + + + + +
  • +
  • + + + + + +
  • +
  • + + + + + +
  • +
  • + + + + + +
  • +
  • + + { + dispatch(setToogleHeader(!data)); + }} + > + + + +
  • +
+
+ + + Add New Category + +
+
+ {/* /product list */} +
+
+
+
+
+ + + + +
+
+ + {
-
diff --git a/src/feature-module/sales/saleslist.jsx b/src/feature-module/sales/saleslist.jsx index 753584e..f58ec60 100644 --- a/src/feature-module/sales/saleslist.jsx +++ b/src/feature-module/sales/saleslist.jsx @@ -84,6 +84,7 @@ const SalesList = () => { useEffect(() => { const timer = setTimeout(() => { setDebouncedSearchTerm(searchTerm); + handleSetParams("page", 1); }, 500); // 500ms delay return () => clearTimeout(timer); @@ -93,8 +94,6 @@ const SalesList = () => { const handleSearch = (e) => { const value = e.target.value; setSearchTerm(value); - // Reset to first page when searching - handleSetParams("page", 1); }; // Handle pagination @@ -824,19 +823,27 @@ const SalesList = () => { Order Tax - {formatRupiah(selectedOrder?.tax_amount)} + + {formatRupiah(selectedOrder?.tax_amount)} + Discount - {formatRupiah(selectedOrder?.discount_amount)} + + {formatRupiah(selectedOrder?.discount_amount)} + Grand Total - {formatRupiah(selectedOrder?.total_amount)} + + {formatRupiah(selectedOrder?.total_amount)} + Sub Total - {formatRupiah(selectedOrder?.subtotal)} + + {formatRupiah(selectedOrder?.subtotal)} + diff --git a/src/feature-module/stock/managestock.jsx b/src/feature-module/stock/managestock.jsx index 6f8e092..46fe0f3 100644 --- a/src/feature-module/stock/managestock.jsx +++ b/src/feature-module/stock/managestock.jsx @@ -1,115 +1,181 @@ -import React, { useState } from "react"; -import Breadcrumbs from "../../core/breadcrumbs"; -import { Filter, Sliders } from "react-feather"; -import ImageWithBasePath from "../../core/img/imagewithbasebath"; -import Select from "react-select"; -import { Link } from "react-router-dom"; -import DatePicker from "react-datepicker"; +import { Tag } from "antd"; +import { useEffect, useState } from "react"; import "react-datepicker/dist/react-datepicker.css"; -import { Archive, Box, Calendar, User } from "react-feather"; -import ManageStockModal from "../../core/modals/stocks/managestockModal"; import { Edit, Trash2 } from "react-feather"; +import { useDispatch, useSelector } from "react-redux"; +import { Link } from "react-router-dom"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; +import CustomPagination from "../../components/CustomPagination"; +import Breadcrumbs from "../../core/breadcrumbs"; +import ImageWithBasePath from "../../core/img/imagewithbasebath"; +import ManageStockModal from "../../core/modals/stocks/managestockModal"; import Table from "../../core/pagination/datatable"; -import { useSelector } from "react-redux"; +import { + deleteInventory, + fetchInventories, + INVENTORY_ACTIONS, +} from "../../core/redux/actions/inventoryActions"; +import { formatDate } from "../../utils/date"; const Managestock = () => { - const data = useSelector((state) => state.managestockdata); + const { + inventories: apiInventories, + loading, + error, + totalInventories, + totalPages, + pageSize: reduxPageSize, + currentPage: reduxCurrentPage, + } = useSelector((state) => state.inventories); - const [isFilterVisible, setIsFilterVisible] = useState(false); - const [selectedDate, setSelectedDate] = useState(null); + const dispatch = useDispatch(); + const dataSource = apiInventories?.length > 0 ? apiInventories : []; - const toggleFilterVisibility = () => { - setIsFilterVisible((prevVisibility) => !prevVisibility); - }; - const handleDateChange = (date) => { - setSelectedDate(date); + const [params, setParams] = useState({ + page: reduxCurrentPage || 1, + limit: reduxPageSize || 10, + }); + const [searchTerm, setSearchTerm] = useState(""); + const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); + + useEffect(() => { + const loadInventories = async () => { + try { + const receivedParams = { + page: params.page, + limit: params.limit, + search: debouncedSearchTerm || "", + }; + + // Remove empty parameters + const cleanParams = Object.fromEntries( + Object.entries(receivedParams).filter(([, value]) => value !== "") + ); + + await dispatch(fetchInventories(cleanParams)); + } catch (error) { + console.error("Failed to fetch orders", error); + } + }; + + loadInventories(); + }, [dispatch, params, debouncedSearchTerm]); + + // Debounce search term + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchTerm(searchTerm); + handleSetParams("page", 1); + }, 500); // 500ms delay + + return () => clearTimeout(timer); + }, [searchTerm]); + + const handleSetParams = (key, value) => { + setParams({ + ...params, + [key]: value, + }); }; - const options = [ - { value: "sortByDate", label: "Sort by Date" }, - { value: "140923", label: "14 09 23" }, - { value: "110923", label: "11 09 23" }, - ]; + const handleSearch = (e) => { + const value = e.target.value; + setSearchTerm(value); + }; - const warehouseOptions = [ - { value: "Choose Warehouse", label: "Choose Warehouse" }, - { value: "Lobar Handy", label: "Lobar Handy" }, - { value: "Quaint Warehouse", label: "Quaint Warehouse" }, - { value: "Traditional Warehouse", label: "Traditional Warehouse" }, - { value: "Cool Warehouse", label: "Cool Warehouse" }, - ]; + // Handle pagination + const handlePageChange = (page) => { + handleSetParams("page", page); + }; - const productOptions = [ - { value: "Choose Product", label: "Choose Product" }, - { value: "Nike Jordan", label: "Nike Jordan" }, - { value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" }, - { value: "Amazon Echo Dot", label: "Amazon Echo Dot" }, - { value: "Lobar Handy", label: "Lobar Handy" }, - ]; + // Handle page size change + const handlePageSizeChange = (newPageSize) => { + handleSetParams("limit", newPageSize); + handleSetParams("page", 1); + }; - const personOptions = [ - { value: "Choose Person", label: "Choose Person" }, - { value: "Steven", label: "Steven" }, - { value: "Gravely", label: "Gravely" }, - ]; + const handleDelete = async (id) => { + try { + await dispatch(deleteInventory(id)); + + MySwal.fire({ + title: "Deleted!", + text: "Your stock has been deleted.", + className: "btn btn-success", + confirmButtonText: "OK", + customClass: { + confirmButton: "btn btn-success", + }, + }); + } catch (error) { + console.error("Failed to delete inventory", error); + } + }; + + // Calculate pagination info + const totalRecords = totalInventories || dataSource.length; + const calculatedTotalPages = Math.ceil(totalRecords / params.limit); + const actualTotalPages = totalPages || calculatedTotalPages; const columns = [ { - title: "Warehouse", - dataIndex: "Warehouse", + title: "Outlet", + dataIndex: "outlet", + render: (_, record) => {record.outlet || "-"}, sorter: (a, b) => a.Warehouse.length - b.Warehouse.length, }, - { - title: "Shop", - dataIndex: "Shop", - sorter: (a, b) => a.Shop.length - b.Shop.length, - }, { title: "Product", dataIndex: "Product", render: (text, record) => ( - + - {record.Product.Name} + {record.product_name || ""} ), sorter: (a, b) => a.Product.Name.length - b.Product.Name.length, }, { - title: "Date", - dataIndex: "Date", + title: "Qty", + dataIndex: "qty", + render: (_, record) => {record.quantity || 0}, sorter: (a, b) => a.Email.length - b.Email.length, }, { - title: "Person", - dataIndex: "Person", - render: (text, record) => ( - - - - - {record.Person.Name} - + title: "Status", + dataIndex: "status", + render: (_, record) => ( + + {record.is_low_stock ? "Low Stock" : "Normal Stock"} + ), sorter: (a, b) => a.Person.Name.length - b.Person.Name.length, }, { - title: "Quantity", - dataIndex: "Quantity", + title: "Record Level", + dataIndex: "recordlevel", + render: (_, record) => {record.record_level || "-"}, + sorter: (a, b) => a.Quantity.length - b.Quantity.length, + }, + { + title: "Updated Date", + dataIndex: "updateddate", + render: (_, record) => ( + {formatDate(record.updated_at) || "-"} + ), sorter: (a, b) => a.Quantity.length - b.Quantity.length, }, { title: "Action", dataIndex: "action", - render: () => ( + render: (_, record) => (
@@ -119,6 +185,12 @@ const Managestock = () => { to="#" data-bs-toggle="modal" data-bs-target="#edit-units" + onClick={() => + dispatch({ + type: INVENTORY_ACTIONS.FETCH_INVENTORY_SUCCESS, + payload: { data: record }, + }) + } > @@ -126,7 +198,7 @@ const Managestock = () => { showConfirmationAlert(record.id)} > @@ -139,7 +211,7 @@ const Managestock = () => { const MySwal = withReactContent(Swal); - const showConfirmationAlert = () => { + const showConfirmationAlert = (id) => { MySwal.fire({ title: "Are you sure?", text: "You won't be able to revert this!", @@ -150,20 +222,13 @@ const Managestock = () => { cancelButtonText: "Cancel", }).then((result) => { if (result.isConfirmed) { - MySwal.fire({ - title: "Deleted!", - text: "Your file has been deleted.", - className: "btn btn-success", - confirmButtonText: "OK", - customClass: { - confirmButton: "btn btn-success", - }, - }); + handleDelete(id); } else { MySwal.close(); } }); }; + return (
@@ -182,117 +247,58 @@ const Managestock = () => { type="text" placeholder="Search" className="form-control form-control-sm formsearch" + value={searchTerm} + onChange={handleSearch} />
-
- - - - - - -
-
- - - -
-
-
-
- - -
-
- -
-
-
- {/* /Filter */}
- record.id} - // pagination={true} - /> + {loading ? ( +
+
+ Loading... +
+

Loading products...

+
+ ) : error ? ( +
+ Error: {error} + +
+ ) : ( + <> +
record.id} + /> + + + + )} diff --git a/src/feature-module/stock/stockAdjustment.jsx b/src/feature-module/stock/stockAdjustment.jsx index 17ed541..a022839 100644 --- a/src/feature-module/stock/stockAdjustment.jsx +++ b/src/feature-module/stock/stockAdjustment.jsx @@ -1,118 +1,153 @@ -import React, { useState } from "react"; -import Breadcrumbs from "../../core/breadcrumbs"; -import { Filter, Sliders } from "react-feather"; -import ImageWithBasePath from "../../core/img/imagewithbasebath"; -import Select from "react-select"; -import { Link } from "react-router-dom"; -import DatePicker from "react-datepicker"; +import { useEffect, useState } from "react"; import "react-datepicker/dist/react-datepicker.css"; -import { Archive, Box, Calendar, User, Edit, Trash2 } from "react-feather"; +import { Edit, Trash2 } from "react-feather"; +import { useDispatch, useSelector } from "react-redux"; +import { Link } from "react-router-dom"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; -import Table from "../../core/pagination/datatable"; +import CustomPagination from "../../components/CustomPagination"; +import Breadcrumbs from "../../core/breadcrumbs"; +import ImageWithBasePath from "../../core/img/imagewithbasebath"; import StockadjustmentModal from "../../core/modals/stocks/stockadjustmentModal"; -import { useSelector } from "react-redux"; +import Table from "../../core/pagination/datatable"; +import { fetchInventories } from "../../core/redux/actions/inventoryActions"; +import { formatDate } from "../../utils/date"; +import { Tag } from "antd"; const StockAdjustment = () => { - const data = useSelector((state) => state.managestockdata); + const { + inventories: apiInventories, + loading, + error, + totalInventories, + totalPages, + pageSize: reduxPageSize, + currentPage: reduxCurrentPage, + } = useSelector((state) => state.inventories); - const [isFilterVisible, setIsFilterVisible] = useState(false); - const [selectedDate, setSelectedDate] = useState(null); + const dispatch = useDispatch(); + const dataSource = apiInventories?.length > 0 ? apiInventories : []; - const toggleFilterVisibility = () => { - setIsFilterVisible((prevVisibility) => !prevVisibility); + const [params, setParams] = useState({ + page: reduxCurrentPage || 1, + limit: reduxPageSize || 10, + }); + const [searchTerm, setSearchTerm] = useState(""); + const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); + + useEffect(() => { + const loadInventories = async () => { + try { + const receivedParams = { + page: params.page, + limit: params.limit, + search: debouncedSearchTerm || "", + }; + + // Remove empty parameters + const cleanParams = Object.fromEntries( + Object.entries(receivedParams).filter(([, value]) => value !== "") + ); + + await dispatch(fetchInventories(cleanParams)); + } catch (error) { + console.error("Failed to fetch orders", error); + } + }; + + loadInventories(); + }, [dispatch, params, debouncedSearchTerm]); + + // Debounce search term + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchTerm(searchTerm); + handleSetParams("page", 1); + }, 500); // 500ms delay + + return () => clearTimeout(timer); + }, [searchTerm]); + + const handleSetParams = (key, value) => { + setParams({ + ...params, + [key]: value, + }); }; - const handleDateChange = (date) => { - setSelectedDate(date); + const handleSearch = (e) => { + const value = e.target.value; + setSearchTerm(value); }; - const options = [ - { value: "sortByDate", label: "Sort by Date" }, - { value: "140923", label: "14 09 23" }, - { value: "110923", label: "11 09 23" }, - ]; - const warehouseOptions = [ - { value: "Choose Warehouse", label: "Choose Warehouse" }, - { value: "Lobar Handy", label: "Lobar Handy" }, - { value: "Quaint Warehouse", label: "Quaint Warehouse" }, - { value: "Traditional Warehouse", label: "Traditional Warehouse" }, - { value: "Cool Warehouse", label: "Cool Warehouse" }, - ]; + // Handle pagination + const handlePageChange = (page) => { + handleSetParams("page", page); + }; - const productOptions = [ - { value: "Choose Product", label: "Choose Product" }, - { value: "Nike Jordan", label: "Nike Jordan" }, - { value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" }, - { value: "Amazon Echo Dot", label: "Amazon Echo Dot" }, - { value: "Lobar Handy", label: "Lobar Handy" }, - ]; + // Handle page size change + const handlePageSizeChange = (newPageSize) => { + handleSetParams("limit", newPageSize); + handleSetParams("page", 1); + }; - const personOptions = [ - { value: "Choose Person", label: "Choose Person" }, - { value: "Steven", label: "Steven" }, - { value: "Gravely", label: "Gravely" }, - ]; + // Calculate pagination info + const totalRecords = totalInventories || dataSource.length; + const calculatedTotalPages = Math.ceil(totalRecords / params.limit); + const actualTotalPages = totalPages || calculatedTotalPages; const columns = [ { - title: "Warehouse", - dataIndex: "Warehouse", + title: "Outlet", + dataIndex: "outlet", + render: (_, record) => {record.outlet || "-"}, sorter: (a, b) => a.Warehouse.length - b.Warehouse.length, }, - { - title: "Shop", - dataIndex: "Shop", - sorter: (a, b) => a.Shop.length - b.Shop.length, - }, { title: "Product", dataIndex: "Product", render: (text, record) => ( - + - {record.Product.Name} + {record.product_name || ""} ), sorter: (a, b) => a.Product.Name.length - b.Product.Name.length, }, { - title: "Date", - dataIndex: "Date", + title: "Qty", + dataIndex: "qty", + render: (_, record) => {record.quantity || 0}, sorter: (a, b) => a.Email.length - b.Email.length, }, { - title: "Person", - dataIndex: "Person", - render: (text, record) => ( - - - - - {record.Person.Name} - + title: "Status", + dataIndex: "status", + render: (_, record) => ( + + {record.is_low_stock ? "Low Stock" : "Normal Stock"} + ), sorter: (a, b) => a.Person.Name.length - b.Person.Name.length, }, { - title: "Notes", - // dataIndex: "Quantity", - render: () => ( - - View Note - + title: "Record Level", + dataIndex: "recordlevel", + render: (_, record) => {record.record_level || "-"}, + sorter: (a, b) => a.Quantity.length - b.Quantity.length, + }, + { + title: "Updated Date", + dataIndex: "updateddate", + render: (_, record) => ( + {formatDate(record.updated_at) || "-"} ), - sorter: (a, b) => a.Notes.length - b.Notes.length, + sorter: (a, b) => a.Quantity.length - b.Quantity.length, }, { @@ -191,115 +226,58 @@ const StockAdjustment = () => { type="text" placeholder="Search" className="form-control form-control-sm formsearch" + name="search" + value={searchTerm} + onChange={handleSearch} /> -
- - - - - - -
-
- - - -
- -
-
- - -
-
- - - - - {/* /Filter */}
-
+ {loading ? ( +
+
+ Loading... +
+

Loading products...

+
+ ) : error ? ( +
+ Error: {error} + +
+ ) : ( + <> +
+ + + + )} diff --git a/src/services/filesApi.js b/src/services/filesApi.js new file mode 100644 index 0000000..c92f975 --- /dev/null +++ b/src/services/filesApi.js @@ -0,0 +1,35 @@ +import api from "./api"; + +// Files API endpoints +const ENDPOINTS = { + FILE_UPLOAD: "files/upload", + FILE_BY_ID: (id) => `files/${id}`, +}; + +// Files API service +export const filesApi = { + // Get all categories + uploadFile: async (fileData) => { + try { + const response = await api.post(ENDPOINTS.FILE_UPLOAD, fileData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + return response.data; + } catch (error) { + console.error("Error fetching categories:", error); + throw error; + } + }, + + getFileById: async (id) => { + try { + const response = await api.get(ENDPOINTS.FILE_BY_ID(id)); + return response.data; + } catch (error) { + console.error(`Error fetching file ${id}:`, error); + throw error; + } + }, +}; diff --git a/src/services/inventoriesApi.js b/src/services/inventoriesApi.js new file mode 100644 index 0000000..03e1ea6 --- /dev/null +++ b/src/services/inventoriesApi.js @@ -0,0 +1,60 @@ +import api from './api'; + +const ENDPOINTS = { + INVENTORIES: 'inventory', + INVENTORY_BY_ID: (id) => `inventory/${id}`, +}; + +export const inventoryApi = { + getAllInventories: async (params = {}) => { + try { + const response = await api.get(ENDPOINTS.INVENTORIES, { params }); + return response.data; + } catch (error) { + console.error('Error fetching inventories:', error); + throw error; + } + }, + + getInventoryById: async (id) => { + try { + const response = await api.get(ENDPOINTS.INVENTORY_BY_ID(id)); + return response.data; + } catch (error) { + console.error(`Error fetching inventory ${id}:`, error); + throw error; + } + }, + + createInventory: async (inventoryData) => { + try { + const response = await api.post(ENDPOINTS.INVENTORIES, inventoryData); + return response.data; + } catch (error) { + console.error('Error creating inventory:', error); + throw error; + } + }, + + updateInventory: async (id, inventoryData) => { + try { + const response = await api.put(ENDPOINTS.INVENTORY_BY_ID(id), inventoryData); + return response.data; + } catch (error) { + console.error(`Error updating inventory ${id}:`, error); + throw error; + } + }, + + deleteInventory: async (id) => { + try { + const response = await api.delete(ENDPOINTS.INVENTORY_BY_ID(id)); + return response.data; + } catch (error) { + console.error(`Error deleting inventory ${id}:`, error); + throw error; + } + }, +}; + +export default inventoryApi; diff --git a/src/services/outletsApi.js b/src/services/outletsApi.js new file mode 100644 index 0000000..c4c0cbf --- /dev/null +++ b/src/services/outletsApi.js @@ -0,0 +1,102 @@ +import api from './api'; + +// Outlets API endpoints +const ENDPOINTS = { + OUTLETS: 'outlets/list', + OUTLET_BY_ID: (id) => `outlets/${id}`, + OUTLET_PRODUCTS: (id) => `outlets/${id}/products`, +}; + +// Outlets API service +export const outletsApi = { + // Get all outlets + getAllOutlets: async (params = {}) => { + try { + const response = await api.get(ENDPOINTS.OUTLETS, { params }); + return response.data; + } catch (error) { + console.error('Error fetching outlets:', error); + throw error; + } + }, + + // Get outlet by ID + getOutletById: async (id) => { + try { + const response = await api.get(ENDPOINTS.OUTLET_BY_ID(id)); + return response.data; + } catch (error) { + console.error(`Error fetching outlet ${id}:`, error); + throw error; + } + }, + + // Create new outlet + createOutlet: async (outletData) => { + try { + const response = await api.post(ENDPOINTS.OUTLETS, outletData); + return response.data; + } catch (error) { + console.error('Error creating outlet:', error); + throw error; + } + }, + + // Update outlet + updateOutlet: async (id, outletData) => { + try { + const response = await api.put(ENDPOINTS.OUTLET_BY_ID(id), outletData); + return response.data; + } catch (error) { + console.error(`Error updating outlet ${id}:`, error); + throw error; + } + }, + + // Delete outlet + deleteOutlet: async (id) => { + try { + const response = await api.delete(ENDPOINTS.OUTLET_BY_ID(id)); + return response.data; + } catch (error) { + console.error(`Error deleting outlet ${id}:`, error); + throw error; + } + }, + + // Get products by outlet + getOutletProducts: async (id, params = {}) => { + try { + const response = await api.get(ENDPOINTS.OUTLET_PRODUCTS(id), { params }); + return response.data; + } catch (error) { + console.error(`Error fetching products for outlet ${id}:`, error); + throw error; + } + }, + + // Bulk operations + bulkUpdateOutlets: async (outlets) => { + try { + const response = await api.put(`${ENDPOINTS.OUTLETS}/bulk`, { outlets }); + return response.data; + } catch (error) { + console.error('Error bulk updating outlets:', error); + throw error; + } + }, + + bulkDeleteOutlets: async (outletIds) => { + try { + const response = await api.delete(`${ENDPOINTS.OUTLETS}/bulk`, { + data: { ids: outletIds } + }); + return response.data; + } catch (error) { + console.error('Error bulk deleting outlets:', error); + throw error; + } + }, +}; + +export default outletsApi; diff --git a/src/style/scss/components/_tables.scss b/src/style/scss/components/_tables.scss index 41b2e9e..c84c449 100644 --- a/src/style/scss/components/_tables.scss +++ b/src/style/scss/components/_tables.scss @@ -683,8 +683,3 @@ table { .custom-table .ant-table-thead .ant-table-cell { background-color: #fafbfe; } - -.custom-table .ant-table-tbody > tr > td { - padding-top: 1px; - padding-bottom: 1px; -} diff --git a/src/style/scss/pages/_stocks.scss b/src/style/scss/pages/_stocks.scss index b9a8a43..b92c32e 100644 --- a/src/style/scss/pages/_stocks.scss +++ b/src/style/scss/pages/_stocks.scss @@ -256,13 +256,12 @@ table { } } .custom-modal-header { - background: $body-bg; padding: 24px; .page-title { h4 { font-size: $font-size-18; font-weight: $font-weight-bold; - color: $secondary; + color: $primary; } } }