refactor and conneting api

This commit is contained in:
ferdiansyah783 2025-08-03 20:55:11 +07:00
parent 0656cda2ec
commit 86a4a19209
26 changed files with 2277 additions and 1131 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="icon" href="./favicon.png" />
<title>Dreams Pos admin template</title>
<title>APSKEL</title>
</head>
<body>

View File

@ -60,13 +60,13 @@ const AddCategoryList = () => {
<div className="modal-content">
<div className="page-wrapper-new p-0">
<div className="content">
<div className="modal-header border-0 custom-modal-header">
<div className="modal-header">
<div className="page-title">
<h4>Create Category</h4>
</div>
<button
type="button"
className="btn-close btn-close-danger"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
@ -77,7 +77,7 @@ const AddCategoryList = () => {
<label className="form-label">Category</label>
<input
type="text"
className="form-control"
className="form-control border"
name="name"
value={formData.name}
onChange={handleInputChange}
@ -88,7 +88,7 @@ const AddCategoryList = () => {
<label className="form-label">Description</label>
<input
type="text"
className="form-control"
className="form-control border"
name="description"
value={formData.description}
onChange={handleInputChange}
@ -109,7 +109,7 @@ const AddCategoryList = () => {
<div className="modal-footer-btn">
<button
type="button"
className="btn btn-secondary me-2"
className="btn btn-outline-dark me-2"
data-bs-dismiss="modal"
>
Cancel

View File

@ -72,7 +72,7 @@ const EditCategoryList = () => {
<div className="modal-content">
<div className="page-wrapper-new p-0">
<div className="content">
<div className="modal-header border-0 custom-modal-header">
<div className="modal-header">
<div className="page-title">
<h4>Edit Category</h4>
</div>
@ -89,7 +89,7 @@ const EditCategoryList = () => {
<label className="form-label">Category</label>
<input
type="text"
className="form-control"
className="form-control border"
name="name"
value={formData.name}
onChange={handleInputChange}
@ -100,7 +100,7 @@ const EditCategoryList = () => {
<label className="form-label">Description</label>
<input
type="text"
className="form-control"
className="form-control border"
name="description"
value={formData.description}
onChange={handleInputChange}
@ -121,7 +121,7 @@ const EditCategoryList = () => {
<div className="modal-footer-btn">
<button
type="button"
className="btn btn-secondary me-2"
className="btn btn-outline-dark me-2"
data-bs-dismiss="modal"
>
Cancel

View File

@ -1,26 +1,149 @@
import React from "react";
import Select from "react-select";
import ImageWithBasePath from "../../img/imagewithbasebath";
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import AsyncSelect from "react-select/async";
import Swal from "sweetalert2";
import outletsApi from "../../../services/outletsApi";
import productsApi from "../../../services/productsApi";
import {
createInventory,
fetchInventories,
updateInventory,
} from "../../redux/actions/inventoryActions";
const ManageStockModal = () => {
const options1 = [
{ value: "choose", label: "Choose" },
{ value: "lobarHandy", label: "Lobar Handy" },
{ value: "quaintWarehouse", label: "Quaint Warehouse" },
];
const dispatch = useDispatch();
const { creating, updating, currentInventory } = useSelector(
(state) => state.inventories
);
const options2 = [
{ value: "choose", label: "Choose" },
{ value: "selosy", label: "Selosy" },
{ value: "logerro", label: "Logerro" },
];
const initializeFormData = () => {
return {
outlet_id: "",
product_id: "",
quantity: "",
min_stock_level: "",
max_stock_level: "",
};
};
const [formData, setFormData] = useState(initializeFormData());
const [selectedOutlet, setSelectedOutlet] = useState(null);
const [selectedProduct, setSelectedProduct] = useState(null);
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: Number(e.target.value),
});
};
const handleSelectChange = (field, selectedOption) => {
setFormData({
...formData,
[field]: selectedOption ? selectedOption.value : "",
});
};
useEffect(() => {
if (currentInventory) {
setFormData(currentInventory);
}
}, [currentInventory]);
const handleSubmit = async (e) => {
e.preventDefault();
try {
await dispatch(createInventory(formData));
await dispatch(fetchInventories());
Swal.fire({
title: "Success!",
text: "Stock created successfully!",
icon: "success",
showConfirmButton: false,
timer: 1500,
}).then(() => {
const closeButton = document.querySelector("#add-units .btn-close");
closeButton.click();
setFormData(initializeFormData());
});
} catch (error) {
console.error("Error creating stock:", error);
// Show error message
Swal.fire({
icon: "error",
title: "Error!",
text: error.message || "Failed to create stock. Please try again.",
});
}
};
const handleUpdate = async (e) => {
e.preventDefault();
try {
await dispatch(updateInventory(currentInventory?.id, formData));
await dispatch(fetchInventories());
Swal.fire({
title: "Success!",
text: "Stock updated successfully!",
icon: "success",
showConfirmButton: false,
timer: 1500,
}).then(() => {
const closeButton = document.querySelector("#edit-units .btn-close");
closeButton.click();
setFormData(initializeFormData());
});
} catch (error) {
console.error("Error updating stock:", error);
// Show error message
Swal.fire({
icon: "error",
title: "Error!",
text: error.message || "Failed to update stock. Please try again.",
});
}
};
const loadOutlets = async (inputValue) => {
try {
const response = await outletsApi.getAllOutlets({ search: inputValue });
const options = response.data.outlets.map((item) => ({
label: item.name,
value: item.id,
}));
return options;
} catch (error) {
console.error("Error fetching options:", error);
return [];
}
};
const loadProducts = async (inputValue) => {
try {
const response = await productsApi.getAllProducts({ search: inputValue });
const options = response.data.products.map((item) => ({
label: item.name,
value: item.id,
}));
return options;
} catch (error) {
console.error("Error fetching options:", error);
return [];
}
};
const options3 = [
{ value: "choose", label: "Choose" },
{ value: "steven", label: "Steven" },
{ value: "gravely", label: "Gravely" },
];
return (
<>
{/* Add Stock */}
@ -29,65 +152,109 @@ const ManageStockModal = () => {
<div className="modal-content">
<div className="page-wrapper-new p-0">
<div className="content">
<div className="modal-header border-0 custom-modal-header">
<div className="modal-header">
<div className="page-title">
<h4>Add Stock</h4>
<h4>Create Stock</h4>
</div>
<button
type="button"
className="close"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
></button>
</div>
<div className="modal-body custom-modal-body">
<form>
<div className="row">
<div className="col-lg-6">
<div className="input-blocks">
<label>Warehouse</label>
<Select className="select" options={options1} />
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label className="form-label">Outlet</label>
<AsyncSelect
className="select"
loadOptions={loadOutlets}
placeholder="Choose"
isClearable
cacheOptions={true}
defaultOptions={true}
value={selectedOutlet}
onChange={(selectedOption) => {
handleSelectChange("outlet_id", selectedOption);
setSelectedOutlet(selectedOption);
}}
/>
</div>
<div className="mb-3">
<label className="form-label">Product</label>
<AsyncSelect
className="select"
loadOptions={loadProducts}
placeholder="Choose"
isClearable
cacheOptions={true}
defaultOptions={true}
value={selectedProduct}
onChange={(selectedOption) => {
handleSelectChange("product_id", selectedOption);
setSelectedProduct(selectedOption);
}}
/>
</div>
<div className="col-lg-6">
<div className="input-blocks">
<label>Shop</label>
<Select className="select" options={options2} />
</div>
</div>
<div className="col-lg-12">
<div className="input-blocks">
<label>Responsible Person</label>
<Select className="select" options={options3} />
</div>
</div>
<div className="col-lg-12">
<div className="input-blocks search-form mb-0">
<label>Product</label>
<div className="mb-3">
<label className="form-label">Quantity</label>
<input
type="text"
className="form-control"
placeholder="Select Product"
type="number"
className="form-control border"
name="quantity"
value={formData.quantity}
onChange={handleInputChange}
/>
<i
data-feather="search"
className="feather-search custom-search"
</div>
<div className="row">
<div className="col">
<label className="form-label">Min Stock</label>
<input
type="number"
className="form-control border"
name="min_stock_level"
value={formData.min_stock_level}
onChange={handleInputChange}
/>
</div>
<div className="col">
<label className="form-label">Max Stock</label>
<input
type="number"
className="form-control border"
name="max_stock_level"
value={formData.max_stock_level}
onChange={handleInputChange}
/>
</div>
</div>
</div>
<div className="modal-footer-btn">
<button
type="button"
className="btn btn-cancel me-2"
className="btn btn-outline-dark me-2"
data-bs-dismiss="modal"
>
Cancel
</button>
<button type="submit" className="btn btn-submit">
Create
<button
type="submit"
disabled={creating}
className="btn btn-submit"
>
{creating ? (
<>
<span
className="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
Creating...
</>
) : (
"Create"
)}
</button>
</div>
</form>
@ -100,167 +267,79 @@ const ManageStockModal = () => {
{/* /Add Stock */}
{/* Edit Stock */}
<div className="modal fade" id="edit-units">
<div className="modal-dialog modal-dialog-centered stock-adjust-modal">
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="page-wrapper-new p-0">
<div className="content">
<div className="modal-header border-0 custom-modal-header">
<div className="modal-header">
<div className="page-title">
<h4>Edit Stock</h4>
</div>
<button
type="button"
className="close"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
></button>
</div>
<div className="modal-body custom-modal-body">
<form>
<div className="input-blocks search-form">
<label>Product</label>
<form onSubmit={handleUpdate}>
<div className="mb-3">
<label className="form-label">Quantity</label>
<input
type="text"
className="form-control"
defaultValue="Nike Jordan"
/>
<i
data-feather="search"
className="feather-search custom-search"
type="number"
className="form-control border"
name="quantity"
value={formData.quantity}
onChange={handleInputChange}
/>
</div>
<div className="row">
<div className="col-lg-6">
<div className="input-blocks">
<label>Warehouse</label>
<Select className="select" options={options1} />
</div>
</div>
<div className="col-lg-6">
<div className="input-blocks">
<label>Shop</label>
<Select className="select" options={options2} />
</div>
</div>
<div className="col-lg-12">
<div className="input-blocks">
<label>Responsible Person</label>
<Select className="select" options={options3} />
</div>
</div>
<div className="col-lg-12">
<div className="input-blocks search-form mb-3">
<label>Product</label>
<div className="mb-3">
<label className="form-label">Min Stock</label>
<input
type="text"
className="form-control"
placeholder="Select Product"
defaultValue="Nike Jordan"
/>
<i
data-feather="search"
className="feather-search custom-search"
type="number"
className="form-control border"
name="min_stock_level"
value={formData.min_stock_level}
onChange={handleInputChange}
/>
</div>
</div>
<div className="col-lg-12">
<div className="modal-body-table">
<div className="table-responsive">
<table className="table datanew">
<thead>
<tr>
<th>Product</th>
<th>SKU</th>
<th>Category</th>
<th>Qty</th>
<th className="no-sort">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div className="productimgname">
<Link
to="#"
className="product-img stock-img"
>
<ImageWithBasePath
src="assets/img/products/stock-img-02.png"
alt="product"
/>
</Link>
<Link to="#">
Nike Jordan
</Link>
</div>
</td>
<td>PT002</td>
<td>Nike</td>
<td>
<div className="product-quantity">
<span className="quantity-btn">
<i
data-feather="minus-circle"
className="feather-search"
/>
</span>
<div className="mb-3">
<label className="form-label">Max Stock</label>
<input
type="text"
className="quntity-input"
defaultValue={2}
type="number"
className="form-control border"
name="max_stock_level"
value={formData.max_stock_level}
onChange={handleInputChange}
/>
<span className="quantity-btn">
+
<i
data-feather="plus-circle"
className="plus-circle"
/>
</span>
</div>
</td>
<td className="action-table-data">
<div className="edit-delete-action">
<Link
className="me-2 p-2"
to="#"
data-bs-toggle="modal"
data-bs-target="#edit-units"
>
<i
data-feather="edit"
className="feather-edit"
/>
</Link>
<Link
className="confirm-text p-2"
to="#"
>
<i
data-feather="trash-2"
className="feather-trash-2"
/>
</Link>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div className="modal-footer-btn">
<button
type="button"
className="btn btn-cancel me-2"
className="btn btn-outline-dark me-2"
data-bs-dismiss="modal"
>
Cancel
</button>
<button type="submit" className="btn btn-submit">
Save Changes
<button
type="submit"
disabled={updating}
className="btn btn-submit"
>
{updating ? (
<>
<span
className="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
Updating...
</>
) : (
"Update"
)}
</button>
</div>
</form>

View File

@ -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,
});

View File

@ -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,
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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"
>
<div className="accordion-body">
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Store</label>
<Select
className="select"
options={store}
placeholder="Choose"
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Warehouse</label>
<Select
className="select"
options={warehouse}
placeholder="Choose"
/>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
@ -386,12 +378,58 @@ const AddProduct = () => {
/>
</div>
</div>
<div className="col-lg-6 col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">
Barcode Symbology
</label>
<Select
className="select"
options={barcodesymbol}
placeholder="Choose"
value={
barcodesymbol.find(
(option) => option.value === formData.barcode
) || null
}
onChange={(selectedOption) =>
handleSelectChange("barcode", selectedOption)
}
/>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Slug</label>
<input
type="text"
className="form-control border"
<label className="form-label">Store</label>
<Select
className="select"
options={store}
placeholder="Choose"
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Printer Type</label>
<Select
className="select"
options={printerTypes}
placeholder="Choose"
value={
printerTypes.find(
(option) =>
option.value === formData.printer_type
) || null
}
onChange={(selectedOption) =>
handleSelectChange(
"printer_type",
selectedOption
)
}
/>
</div>
</div>
@ -427,20 +465,6 @@ const AddProduct = () => {
</button>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Selling Type</label>
<Select
className="select"
options={sellingtype}
placeholder="Choose"
/>
</div>
</div>
</div>
<div className="addservice-info">
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<div className="add-newplus">
@ -473,120 +497,8 @@ const AddProduct = () => {
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Sub Category</label>
<Select
className="select"
options={subcategory}
placeholder="Choose"
/>
</div>
</div>
</div>
</div>
<div className="add-product-new">
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<div className="add-newplus">
<label className="form-label">Brand</label>
<Link
to="#"
data-bs-toggle="modal"
data-bs-target="#add-units-brand"
>
<PlusCircle className="plus-down-add" />
<span>Add New</span>
</Link>
</div>
<Select
className="select"
options={brand}
placeholder="Choose"
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<div className="add-newplus">
<label className="form-label">Unit</label>
<Link
to="#"
data-bs-toggle="modal"
data-bs-target="#add-unit"
>
<PlusCircle className="plus-down-add" />
<span>Add New</span>
</Link>
</div>
<Select
className="select"
options={unit}
placeholder="Choose"
/>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-6 col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">
Barcode Symbology
</label>
<Select
className="select"
options={barcodesymbol}
placeholder="Choose"
/>
</div>
</div>
<div className="col-lg-6 col-sm-6 col-12">
<label className="form-label">Item Code</label>
<div className="position-relative">
<input
type="text"
className="form-control border"
placeholder="Enter item code"
aria-label="Enter item code"
aria-describedby="button-addon2"
/>
<button
className="btn btn-primary center-vertical"
style={{
fontSize: "10px",
height: "24px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
}}
type="button"
id="button-addon2"
>
Generate
</button>
</div>
</div>
{/* <div className="col-lg-6 col-sm-6 col-12">
<div className="input-blocks add-product list">
<label>Item Code</label>
<input
type="text"
className="form-control list"
placeholder="Please Enter Item Code"
/>
<Link
to={route.addproduct}
className="btn btn-primaryadd"
>
Generate Code
</Link>
</div>
</div> */}
</div>
{/* Editor */}
<div className="col-lg-12">
<div className="input-blocks summer-description-box transfer mb-3">
@ -935,17 +847,36 @@ const AddProduct = () => {
<div className="add-choosen">
<div className="input-blocks">
<div className="image-upload">
<input type="file" />
<input
type="file"
onChange={handleFileChange}
disabled={uploading}
/>
<div className="image-uploads">
{uploading ? (
<>
<span
className="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
<h4>Uploading...</h4>
</>
) : (
<>
<PlusCircle className="plus-down-add me-0" />
<h4>Add Images</h4>
<h4>
{tempImage ? "Edit Image" : "Add Image"}
</h4>
</>
)}
</div>
</div>
</div>
{isImageVisible1 && (
{formData.image_url && (
<div className="phone-img">
<ImageWithBasePath
src="assets/img/products/phone-add-2.png"
src={formData.image_url}
alt="image"
/>
<Link to="#">
@ -956,20 +887,6 @@ const AddProduct = () => {
</Link>
</div>
)}
{isImageVisible && (
<div className="phone-img">
<ImageWithBasePath
src="assets/img/products/phone-add-1.png"
alt="image"
/>
<Link to="#">
<X
className="x-square-add remove-product"
onClick={handleRemoveProduct}
/>
</Link>
</div>
)}
</div>
</div>
</div>
@ -1131,9 +1048,7 @@ const AddProduct = () => {
</form>
{/* /add */}
</div>
<Addunits />
<AddCategory />
<AddBrand />
</div>
);
};

View File

@ -236,23 +236,6 @@ 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 (
<div>
<div className="page-wrapper">

View File

@ -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 = () => {
</li>
</ul>
</div>
{/* /add */}
{/* /edit */}
<form onSubmit={handleSubmit}>
<div className="card">
<div className="card-body add-product">
@ -448,29 +454,6 @@ const EditProduct = () => {
data-bs-parent="#accordionExample"
>
<div className="accordion-body">
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Store</label>
<Select
className="select"
options={store}
placeholder="Choose"
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Warehouse</label>
<Select
className="select"
options={warehouse}
placeholder="Choose"
/>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
@ -484,12 +467,58 @@ const EditProduct = () => {
/>
</div>
</div>
<div className="col-lg-6 col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">
Barcode Symbology
</label>
<Select
className="select"
options={barcodesymbol}
placeholder="Choose"
value={
barcodesymbol.find(
(option) => option.value === formData.barcode
) || null
}
onChange={(selectedOption) =>
handleSelectChange("barcode", selectedOption)
}
/>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Slug</label>
<input
type="text"
className="form-control border"
<label className="form-label">Store</label>
<Select
className="select"
options={store}
placeholder="Choose"
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Printer Type</label>
<Select
className="select"
options={printerTypes}
placeholder="Choose"
value={
printerTypes.find(
(option) =>
option.value === formData.printer_type
) || null
}
onChange={(selectedOption) =>
handleSelectChange(
"printer_type",
selectedOption
)
}
/>
</div>
</div>
@ -525,20 +554,6 @@ const EditProduct = () => {
</button>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Selling Type</label>
<Select
className="select"
options={sellingtype}
placeholder="Choose"
/>
</div>
</div>
</div>
<div className="addservice-info">
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<div className="add-newplus">
@ -557,12 +572,10 @@ const EditProduct = () => {
options={category}
placeholder="Choose"
value={
formData.category_id
? category.find(
category.find(
(option) =>
option.value === formData.category_id
)
: null
) || null
}
onChange={(selectedOption) =>
handleSelectChange(
@ -573,104 +586,8 @@ const EditProduct = () => {
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">Sub Category</label>
<Select
className="select"
options={subcategory}
placeholder="Choose"
/>
</div>
</div>
</div>
</div>
<div className="add-product-new">
<div className="row">
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<div className="add-newplus">
<label className="form-label">Brand</label>
<Link
to="#"
data-bs-toggle="modal"
data-bs-target="#add-units-brand"
>
<PlusCircle className="plus-down-add" />
<span>Add New</span>
</Link>
</div>
<Select
className="select"
options={brand}
placeholder="Choose"
/>
</div>
</div>
<div className="col-sm-6 col-12">
<div className="mb-3 add-product">
<div className="add-newplus">
<label className="form-label">Unit</label>
<Link
to="#"
data-bs-toggle="modal"
data-bs-target="#add-unit"
>
<PlusCircle className="plus-down-add" />
<span>Add New</span>
</Link>
</div>
<Select
className="select"
options={unit}
placeholder="Choose"
/>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-6 col-sm-6 col-12">
<div className="mb-3 add-product">
<label className="form-label">
Barcode Symbology
</label>
<Select
className="select"
options={barcodesymbol}
placeholder="Choose"
/>
</div>
</div>
<div className="col-lg-6 col-sm-6 col-12">
<label className="form-label">Item Code</label>
<div className="position-relative">
<input
type="text"
className="form-control border"
placeholder="Enter item code"
aria-label="Enter item code"
aria-describedby="button-addon2"
/>
<button
className="btn btn-primary center-vertical"
style={{
fontSize: "10px",
height: "24px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
}}
type="button"
id="button-addon2"
>
Generate
</button>
</div>
</div>
</div>
{/* Editor */}
<div className="col-lg-12">
<div className="input-blocks summer-description-box transfer mb-3">
@ -774,6 +691,7 @@ const EditProduct = () => {
</ul>
</div>
</div>
<div className="tab-content" id="pills-tabContent">
<div
className="tab-pane fade show active"
@ -786,7 +704,7 @@ const EditProduct = () => {
<div className="add-product">
<label className="form-label">Quantity</label>
<input
type="text"
type="number"
className="form-control border"
/>
</div>
@ -1018,17 +936,36 @@ const EditProduct = () => {
<div className="add-choosen">
<div className="input-blocks">
<div className="image-upload">
<input type="file" />
<input
type="file"
onChange={handleFileChange}
disabled={uploading}
/>
<div className="image-uploads">
{uploading ? (
<>
<span
className="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
<h4>Uploading...</h4>
</>
) : (
<>
<PlusCircle className="plus-down-add me-0" />
<h4>Add Images</h4>
<h4>
{tempImage ? "Edit Image" : "Add Image"}
</h4>
</>
)}
</div>
</div>
</div>
{isImageVisible1 && (
{formData.image_url && (
<div className="phone-img">
<ImageWithBasePath
src="assets/img/products/phone-add-2.png"
src={formData.image_url}
alt="image"
/>
<Link to="#">
@ -1039,20 +976,6 @@ const EditProduct = () => {
</Link>
</div>
)}
{isImageVisible && (
<div className="phone-img">
<ImageWithBasePath
src="assets/img/products/phone-add-1.png"
alt="image"
/>
<Link to="#">
<X
className="x-square-add remove-product"
onClick={handleRemoveProduct}
/>
</Link>
</div>
)}
</div>
</div>
</div>
@ -1214,9 +1137,7 @@ const EditProduct = () => {
</form>
{/* /add */}
</div>
<Addunits />
<AddCategory />
<AddBrand />
</div>
);
};

View File

@ -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) => (
<Tooltip id="pdf-tooltip" {...props}>
Pdf
</Tooltip>
);
const renderExcelTooltip = (props) => (
<Tooltip id="excel-tooltip" {...props}>
Excel
</Tooltip>
);
const renderPrinterTooltip = (props) => (
<Tooltip id="printer-tooltip" {...props}>
Printer
</Tooltip>
);
const renderRefreshTooltip = (props) => (
<Tooltip id="refresh-tooltip" {...props}>
Refresh
</Tooltip>
);
const renderCollapseTooltip = (props) => (
<Tooltip id="refresh-tooltip" {...props}>
Collapse
</Tooltip>
);
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 <span>{record.name}</span>;
},
sorter: (a, b) => a.category.length - b.category.length,
},
{
title: "Category Slug",
dataIndex: "categoryslug",
render: (_, record) => {
return <span>{record?.name?.toLowerCase()}</span>;
},
sorter: (a, b) => a.categoryslug.length - b.categoryslug.length,
},
{
title: "Created On",
dataIndex: "createdon",
render: (_, record) => {
return <span>{formatDate(record.created_at)}</span>;
},
sorter: (a, b) => a.createdon.length - b.createdon.length,
},
{
title: "Status",
dataIndex: "status",
render: () => <Tag color="#87d068">active</Tag>,
sorter: (a, b) => a.status.length - b.status.length,
},
{
title: "Actions",
dataIndex: "actions",
key: "actions",
render: (_, record) => (
<td className="action-table-data">
<div className="edit-delete-action">
<Link
className="me-2 p-2"
to="#"
data-bs-toggle="modal"
data-bs-target="#edit-category"
onClick={() => dispatch(fetchCategory(record.id))}
>
<i data-feather="edit" className="feather-edit"></i>
</Link>
<Link
className="confirm-text p-2"
to="#"
onClick={(e) => {
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);
}
});
}}
>
<Trash2 className="feather-trash-2" />
</Link>
</div>
</td>
),
},
];
const MySwal = withReactContent(Swal);
return (
<div>
<div className="page-wrapper">
<div className="content">
<div className="page-header">
<div className="add-item d-flex">
<div className="page-title">
<h4>Category</h4>
<h6>Manage your categories</h6>
</div>
</div>
<ul className="table-top-head">
<li>
<OverlayTrigger placement="top" overlay={renderTooltip}>
<Link>
<ImageWithBasePath
src="assets/img/icons/pdf.svg"
alt="img"
/>
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
<Link data-bs-toggle="tooltip" data-bs-placement="top">
<ImageWithBasePath
src="assets/img/icons/excel.svg"
alt="img"
/>
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
<Link data-bs-toggle="tooltip" data-bs-placement="top">
<i data-feather="printer" className="feather-printer" />
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
<Link data-bs-toggle="tooltip" data-bs-placement="top">
<RotateCcw />
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
<Link
data-bs-toggle="tooltip"
data-bs-placement="top"
id="collapse-header"
className={data ? "active" : ""}
onClick={() => {
dispatch(setToogleHeader(!data));
}}
>
<ChevronUp />
</Link>
</OverlayTrigger>
</li>
</ul>
<div className="page-btn">
<Link
to="#"
className="btn btn-added"
data-bs-toggle="modal"
data-bs-target="#add-category"
>
<PlusCircle className="me-2" />
Add New Category
</Link>
</div>
</div>
{/* /product list */}
<div className="card table-list-card">
<div className="card-body">
<div className="table-top">
<div className="search-set">
<div className="search-input">
<input
type="text"
placeholder="Search"
className="form-control form-control-sm formsearch"
onChange={handleSearch}
/>
<Link to className="btn btn-searchset">
<i data-feather="search" className="feather-search" />
</Link>
</div>
</div>
<Select
style={{ height: 36 }}
defaultValue={dateOptions[0]?.value}
options={dateOptions}
/>
</div>
<div className="table-responsive">
{loading ? (
<div className="text-center p-4">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="mt-2">Loading categories...</p>
</div>
) : error ? (
<div className="alert alert-danger" role="alert">
<strong>Error:</strong> {error}
<button
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => dispatch(fetchCategories())}
>
Retry
</button>
</div>
) : (
<>
<Table columns={columns} dataSource={dataSource} />
<CustomPagination
currentPage={currentPage}
pageSize={pageSize}
totalCount={totalRecords}
totalPages={actualTotalPages}
loading={loading}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
pageSizeOptions={[10, 20, 50, 100]}
showInfo={true}
showPageSizeSelector={true}
compact={false}
className="product-list-pagination"
/>
</>
)}
</div>
</div>
</div>
{/* /category list */}
</div>
</div>
<AddCategoryList />
<EditCategoryList />
</div>
);
};
export default OutletList;

View File

@ -61,22 +61,26 @@ const ProductDetail = () => {
<h4>Category</h4>
<h6>{currentProduct?.category ?? "-"}</h6>
</li>
<li>
<h4>Business</h4>
<h6>{currentProduct?.business_type ?? "-"}</h6>
</li>
<li>
<h4>Unit</h4>
<h6>Piece</h6>
</li>
<li>
<h4>SKU</h4>
<h6>{currentProduct?.sku ?? "-"}</h6>
</li>
<li>
<h4>Business</h4>
<h6>{currentProduct?.business_type ?? "-"}</h6>
</li>
<li>
<h4>Printer</h4>
<h6>{currentProduct?.printer_type ?? "-"}</h6>
</li>
<li>
<h4>Price</h4>
<h6>{formatRupiah(Number(currentProduct?.price))}</h6>
</li>
<li>
<h4>Cost</h4>
<h6>{formatRupiah(Number(currentProduct?.cost))}</h6>
</li>
<li>
<h4>Status</h4>
<h6>
@ -141,11 +145,10 @@ const ProductDetail = () => {
<div className="owl-carousel owl-theme product-slide">
<div className="slider-product">
<ImageWithBasePath
src="assets/img/products/product69.jpg"
src={currentProduct?.image_url}
alt="img"
/>
<h4>{currentProduct?.name}</h4>
<h6>581kb</h6>
</div>
</div>
</div>

View File

@ -594,7 +594,17 @@ const ProductList = () => {
{
title: "Product",
dataIndex: "product",
render: (text, record) => <span className="fw-medium">{record.name}</span>,
render: (text, record) => (
<span className="productimgname">
<Link className="product-img stock-img">
<ImageWithBasePath
alt={"jpg"}
src={record.image_url}
/>
</Link>
<Link className="fw-medium text-black">{record.name}</Link>
</span>
),
sorter: (a, b) => a.product.length - b.product.length,
},
{
@ -610,7 +620,6 @@ const ProductList = () => {
return catA.length - catB.length;
},
},
{
title: "Business",
dataIndex: "business",
@ -624,6 +633,19 @@ const ProductList = () => {
return brandA.length - brandB.length;
},
},
{
title: "Printer",
dataIndex: "printer",
render: (_, record) => {
const printer = record.printer_type || "-";
return <span>{printer}</span>;
},
sorter: (a, b) => {
const brandA = a.brand || a.brandName || "";
const brandB = b.brand || b.brandName || "";
return brandA.length - brandB.length;
},
},
{
title: "Price",
dataIndex: "price",
@ -638,53 +660,18 @@ const ProductList = () => {
},
},
{
title: "Unit",
dataIndex: "unit",
title: "Cost",
dataIndex: "cost",
render: (_, record) => {
const unit = record.unit || record.unitOfMeasure || "-";
return <span>{unit}</span>;
const cost = record.cost || 0;
return <span>{formatRupiah(Number(cost))}</span>;
},
sorter: (a, b) => {
const unitA = a.unit || a.unitOfMeasure || "";
const unitB = b.unit || b.unitOfMeasure || "";
return unitA.length - unitB.length;
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
const priceB = Number(b.price || b.salePrice || b.unitPrice || 0);
return priceA - priceB;
},
},
{
title: "Qty",
dataIndex: "qty",
render: (_, record) => {
// Try multiple possible field names for quantity
const quantity =
record.qty ||
record.quantity ||
record.stock ||
record.stockQuantity ||
0;
return <span>{quantity}</span>;
},
sorter: (a, b) => {
const qtyA = a.qty || a.quantity || a.stock || a.stockQuantity || 0;
const qtyB = b.qty || b.quantity || b.stock || b.stockQuantity || 0;
return Number(qtyA) - Number(qtyB);
},
},
{
title: "Created By",
dataIndex: "createdby",
render: (text, record) => (
<span className="created-by-text">
<Link
to="/profile"
style={{ color: "#3498db", textDecoration: "none" }}
>
{record.createdBy || text || "Admin"}
</Link>
</span>
),
sorter: (a, b) => a.createdby.length - b.createdby.length,
},
{
title: "Action",
dataIndex: "action",

View File

@ -12,26 +12,28 @@ const Signin = () => {
const authState = useSelector((state) => state.auth);
const [formData, setFormData] = useState({
email: "",
password: "",
email: '',
password: ''
});
const [error, setError] = useState("");
const [error, setError] = useState('');
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
setError('');
try {
authApi.login(formData).then(() => navigate(route.dashboard));
await authApi.login(formData)
navigate(route.dashboard);
} catch (error) {
setError(error.message || "Login failed");
setError(error.message || 'Login failed');
}
};
@ -114,7 +116,7 @@ const Signin = () => {
className="btn btn-login"
disabled={authState.loading}
>
{authState.loading ? "Signing In..." : "Sign In"}
{authState.loading ? 'Signing In...' : 'Sign In'}
</button>
</div>
<div className="signinform">

View File

@ -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 = () => {
<tbody>
<tr>
<td>Order Tax</td>
<td className="text-end">{formatRupiah(selectedOrder?.tax_amount)}</td>
<td className="text-end">
{formatRupiah(selectedOrder?.tax_amount)}
</td>
</tr>
<tr>
<td>Discount</td>
<td className="text-end">{formatRupiah(selectedOrder?.discount_amount)}</td>
<td className="text-end">
{formatRupiah(selectedOrder?.discount_amount)}
</td>
</tr>
<tr className="fw-bold">
<td>Grand Total</td>
<td className="text-end">{formatRupiah(selectedOrder?.total_amount)}</td>
<td className="text-end">
{formatRupiah(selectedOrder?.total_amount)}
</td>
</tr>
<tr>
<td>Sub Total</td>
<td className="text-end">{formatRupiah(selectedOrder?.subtotal)}</td>
<td className="text-end">
{formatRupiah(selectedOrder?.subtotal)}
</td>
</tr>
</tbody>
</table>

View File

@ -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 || "",
};
const options = [
{ value: "sortByDate", label: "Sort by Date" },
{ value: "140923", label: "14 09 23" },
{ value: "110923", label: "11 09 23" },
];
// Remove empty parameters
const cleanParams = Object.fromEntries(
Object.entries(receivedParams).filter(([, value]) => 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" },
];
await dispatch(fetchInventories(cleanParams));
} catch (error) {
console.error("Failed to fetch orders", error);
}
};
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" },
];
loadInventories();
}, [dispatch, params, debouncedSearchTerm]);
const personOptions = [
{ value: "Choose Person", label: "Choose Person" },
{ value: "Steven", label: "Steven" },
{ value: "Gravely", label: "Gravely" },
];
// 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 handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value);
};
// Handle pagination
const handlePageChange = (page) => {
handleSetParams("page", page);
};
// Handle page size change
const handlePageSizeChange = (newPageSize) => {
handleSetParams("limit", newPageSize);
handleSetParams("page", 1);
};
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) => <span>{record.outlet || "-"}</span>,
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) => (
<span className="userimgname">
<Link to="#" className="product-img">
<ImageWithBasePath alt="img" src={record.Product.Image} />
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
</Link>
<Link to="#">{record.Product.Name}</Link>
<Link to="#">{record.product_name || ""}</Link>
</span>
),
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
},
{
title: "Date",
dataIndex: "Date",
title: "Qty",
dataIndex: "qty",
render: (_, record) => <span>{record.quantity || 0}</span>,
sorter: (a, b) => a.Email.length - b.Email.length,
},
{
title: "Person",
dataIndex: "Person",
render: (text, record) => (
<span className="userimgname">
<Link to="#" className="product-img">
<ImageWithBasePath alt="img" src={record.Person.Image} />
</Link>
<Link to="#">{record.Person.Name}</Link>
</span>
title: "Status",
dataIndex: "status",
render: (_, record) => (
<Tag color={record.is_low_stock ? "red" : "green"}>
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
</Tag>
),
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
},
{
title: "Quantity",
dataIndex: "Quantity",
title: "Record Level",
dataIndex: "recordlevel",
render: (_, record) => <span>{record.record_level || "-"}</span>,
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
},
{
title: "Updated Date",
dataIndex: "updateddate",
render: (_, record) => (
<span>{formatDate(record.updated_at) || "-"}</span>
),
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
},
{
title: "Action",
dataIndex: "action",
render: () => (
render: (_, record) => (
<td className="action-table-data">
<div className="edit-delete-action">
<div className="input-block add-lists"></div>
@ -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 },
})
}
>
<Edit className="feather-edit" />
</Link>
@ -126,7 +198,7 @@ const Managestock = () => {
<Link
className="confirm-text p-2"
to="#"
onClick={showConfirmationAlert}
onClick={() => showConfirmationAlert(record.id)}
>
<Trash2 className="feather-trash-2" />
</Link>
@ -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 (
<div className="page-wrapper">
<div className="content">
@ -182,117 +247,58 @@ const Managestock = () => {
type="text"
placeholder="Search"
className="form-control form-control-sm formsearch"
value={searchTerm}
onChange={handleSearch}
/>
<Link to className="btn btn-searchset">
<i data-feather="search" className="feather-search" />
</Link>
</div>
</div>
<div className="search-path">
<Link
className={`btn btn-filter ${
isFilterVisible ? "setclose" : ""
}`}
id="filter_search"
>
<Filter
className="filter-icon"
onClick={toggleFilterVisibility}
/>
<span onClick={toggleFilterVisibility}>
<ImageWithBasePath
src="assets/img/icons/closes.svg"
alt="img"
/>
</span>
</Link>
</div>
<div className="form-sort stylewidth">
<Sliders className="info-img" />
<Select
className="select "
options={options}
placeholder="Sort by Date"
/>
</div>
</div>
{/* /Filter */}
<div
className={`card${isFilterVisible ? " visible" : ""}`}
id="filter_inputs"
style={{ display: isFilterVisible ? "block" : "none" }}
>
<div className="card-body pb-0">
<div className="row">
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Archive className="info-img" />
<Select
className="select"
options={warehouseOptions}
placeholder="Choose Warehouse"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Box className="info-img" />
<Select
className="select"
options={productOptions}
placeholder="Choose Product"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Calendar className="info-img" />
<div className="input-groupicon">
<DatePicker
selected={selectedDate}
onChange={handleDateChange}
dateFormat="dd/MM/yyyy"
placeholderText="Choose Date"
className="datetimepicker"
/>
</div>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<User className="info-img" />
<Select
className="select"
options={personOptions}
placeholder="Choose Person"
/>
</div>
</div>
<div className="col-lg-4 col-sm-6 col-12 ms-auto">
<div className="input-blocks">
<a className="btn btn-filters ms-auto">
{" "}
<i
data-feather="search"
className="feather-search"
/>{" "}
Search{" "}
</a>
</div>
</div>
</div>
</div>
</div>
{/* /Filter */}
<div className="table-responsive">
{loading ? (
<div className="text-center p-4">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="mt-2">Loading products...</p>
</div>
) : error ? (
<div className="alert alert-danger" role="alert">
<strong>Error:</strong> {error}
<button
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => dispatch(fetchInventories())}
>
Retry
</button>
</div>
) : (
<>
<Table
className="table datanew"
columns={columns}
dataSource={data}
dataSource={dataSource}
rowKey={(record) => record.id}
// pagination={true}
/>
<CustomPagination
currentPage={params.page}
pageSize={params.limit}
totalCount={totalRecords}
totalPages={actualTotalPages}
loading={loading}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
pageSizeOptions={[10, 20, 50, 100]}
showInfo={true}
showPageSizeSelector={true}
compact={false}
className="product-list-pagination"
/>
</>
)}
</div>
</div>
</div>

View File

@ -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 || "",
};
const handleDateChange = (date) => {
setSelectedDate(date);
// 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);
}
};
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" },
];
loadInventories();
}, [dispatch, params, debouncedSearchTerm]);
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" },
];
// Debounce search term
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedSearchTerm(searchTerm);
handleSetParams("page", 1);
}, 500); // 500ms delay
const personOptions = [
{ value: "Choose Person", label: "Choose Person" },
{ value: "Steven", label: "Steven" },
{ value: "Gravely", label: "Gravely" },
];
return () => clearTimeout(timer);
}, [searchTerm]);
const handleSetParams = (key, value) => {
setParams({
...params,
[key]: value,
});
};
const handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value);
};
// Handle pagination
const handlePageChange = (page) => {
handleSetParams("page", page);
};
// Handle page size change
const handlePageSizeChange = (newPageSize) => {
handleSetParams("limit", newPageSize);
handleSetParams("page", 1);
};
// 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) => <span>{record.outlet || "-"}</span>,
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) => (
<span className="userimgname">
<Link to="#" className="product-img">
<ImageWithBasePath alt="img" src={record.Product.Image} />
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
</Link>
<Link to="#">{record.Product.Name}</Link>
<Link to="#">{record.product_name || ""}</Link>
</span>
),
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
},
{
title: "Date",
dataIndex: "Date",
title: "Qty",
dataIndex: "qty",
render: (_, record) => <span>{record.quantity || 0}</span>,
sorter: (a, b) => a.Email.length - b.Email.length,
},
{
title: "Person",
dataIndex: "Person",
render: (text, record) => (
<span className="userimgname">
<Link to="#" className="product-img">
<ImageWithBasePath alt="img" src={record.Person.Image} />
</Link>
<Link to="#">{record.Person.Name}</Link>
</span>
title: "Status",
dataIndex: "status",
render: (_, record) => (
<Tag color={record.is_low_stock ? "red" : "green"}>
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
</Tag>
),
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
},
{
title: "Notes",
// dataIndex: "Quantity",
render: () => (
<Link
to="#"
className="view-note"
data-bs-toggle="modal"
data-bs-target="#view-notes"
>
View Note
</Link>
title: "Record Level",
dataIndex: "recordlevel",
render: (_, record) => <span>{record.record_level || "-"}</span>,
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
},
{
title: "Updated Date",
dataIndex: "updateddate",
render: (_, record) => (
<span>{formatDate(record.updated_at) || "-"}</span>
),
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}
/>
<Link to className="btn btn-searchset">
<i data-feather="search" className="feather-search" />
</Link>
</div>
</div>
<div className="search-path">
<Link
className={`btn btn-filter ${
isFilterVisible ? "setclose" : ""
}`}
id="filter_search"
>
<Filter
className="filter-icon"
onClick={toggleFilterVisibility}
/>
<span onClick={toggleFilterVisibility}>
<ImageWithBasePath
src="assets/img/icons/closes.svg"
alt="img"
/>
</span>
</Link>
</div>
<div className="form-sort stylewidth">
<Sliders className="info-img" />
<Select
className="select "
options={options}
placeholder="Sort by Date"
/>
</div>
</div>
{/* /Filter */}
<div
className={`card${isFilterVisible ? " visible" : ""}`}
id="filter_inputs"
style={{ display: isFilterVisible ? "block" : "none" }}
>
<div className="card-body pb-0">
<div className="row">
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Archive className="info-img" />
<Select
className="select"
options={warehouseOptions}
placeholder="Choose Warehouse"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Box className="info-img" />
<Select
className="select"
options={productOptions}
placeholder="Choose Product"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Calendar className="info-img" />
<div className="input-groupicon">
<DatePicker
selected={selectedDate}
onChange={handleDateChange}
dateFormat="dd/MM/yyyy"
placeholderText="Choose Date"
className="datetimepicker"
/>
</div>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<User className="info-img" />
<Select
className="select"
options={personOptions}
placeholder="Choose Person"
/>
</div>
</div>
<div className="col-lg-4 col-sm-6 col-12 ms-auto">
<div className="input-blocks">
<a className="btn btn-filters ms-auto">
{" "}
<i
data-feather="search"
className="feather-search"
/>{" "}
Search{" "}
</a>
</div>
</div>
</div>
</div>
</div>
{/* /Filter */}
<div className="table-responsive">
{loading ? (
<div className="text-center p-4">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="mt-2">Loading products...</p>
</div>
) : error ? (
<div className="alert alert-danger" role="alert">
<strong>Error:</strong> {error}
<button
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => dispatch(fetchInventories())}
>
Retry
</button>
</div>
) : (
<>
<Table
className="table datanew"
columns={columns}
dataSource={data}
dataSource={dataSource}
/>
<CustomPagination
currentPage={params.page}
pageSize={params.limit}
totalCount={totalRecords}
totalPages={actualTotalPages}
loading={loading}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
pageSizeOptions={[10, 20, 50, 100]}
showInfo={true}
showPageSizeSelector={true}
compact={false}
className="product-list-pagination"
/>
</>
)}
</div>
</div>
</div>

35
src/services/filesApi.js Normal file
View File

@ -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;
}
},
};

View File

@ -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;

102
src/services/outletsApi.js Normal file
View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}
}