refactor and conneting api
This commit is contained in:
parent
0656cda2ec
commit
86a4a19209
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 31 KiB |
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
<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="mb-3">
|
||||
<label className="form-label">Quantity</label>
|
||||
<input
|
||||
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 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-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>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Select Product"
|
||||
/>
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search custom-search"
|
||||
/>
|
||||
</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 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>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Select Product"
|
||||
defaultValue="Nike Jordan"
|
||||
/>
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search custom-search"
|
||||
/>
|
||||
</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>
|
||||
<input
|
||||
type="text"
|
||||
className="quntity-input"
|
||||
defaultValue={2}
|
||||
/>
|
||||
<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 className="mb-3">
|
||||
<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="mb-3">
|
||||
<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 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>
|
||||
|
||||
143
src/core/redux/actions/inventoryActions.js
Normal file
143
src/core/redux/actions/inventoryActions.js
Normal 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,
|
||||
});
|
||||
138
src/core/redux/actions/outletActions.js
Normal file
138
src/core/redux/actions/outletActions.js
Normal 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,
|
||||
});
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
189
src/core/redux/reducers/inventoryReducer.js
Normal file
189
src/core/redux/reducers/inventoryReducer.js
Normal 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;
|
||||
212
src/core/redux/reducers/outletReducer.js
Normal file
212
src/core/redux/reducers/outletReducer.js
Normal 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;
|
||||
@ -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>
|
||||
@ -429,164 +467,38 @@ const AddProduct = () => {
|
||||
</div>
|
||||
<div className="col-sm-6 col-12">
|
||||
<div className="mb-3 add-product">
|
||||
<label className="form-label">Selling Type</label>
|
||||
<div className="add-newplus">
|
||||
<label className="form-label">Category</label>
|
||||
<Link
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-units-category"
|
||||
>
|
||||
<PlusCircle className="plus-down-add" />
|
||||
<span>Add New</span>
|
||||
</Link>
|
||||
</div>
|
||||
<Select
|
||||
className="select"
|
||||
options={sellingtype}
|
||||
options={category}
|
||||
placeholder="Choose"
|
||||
value={
|
||||
category.find(
|
||||
(option) =>
|
||||
option.value === formData.category_id
|
||||
) || null
|
||||
}
|
||||
onChange={(selectedOption) =>
|
||||
handleSelectChange(
|
||||
"category_id",
|
||||
selectedOption
|
||||
)
|
||||
}
|
||||
/>
|
||||
</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">
|
||||
<label className="form-label">Category</label>
|
||||
<Link
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-units-category"
|
||||
>
|
||||
<PlusCircle className="plus-down-add" />
|
||||
<span>Add New</span>
|
||||
</Link>
|
||||
</div>
|
||||
<Select
|
||||
className="select"
|
||||
options={category}
|
||||
placeholder="Choose"
|
||||
value={
|
||||
category.find(
|
||||
(option) =>
|
||||
option.value === formData.category_id
|
||||
) || null
|
||||
}
|
||||
onChange={(selectedOption) =>
|
||||
handleSelectChange(
|
||||
"category_id",
|
||||
selectedOption
|
||||
)
|
||||
}
|
||||
/>
|
||||
</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">
|
||||
<PlusCircle className="plus-down-add me-0" />
|
||||
<h4>Add Images</h4>
|
||||
{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>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
|
||||
@ -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>
|
||||
@ -527,150 +556,38 @@ const EditProduct = () => {
|
||||
</div>
|
||||
<div className="col-sm-6 col-12">
|
||||
<div className="mb-3 add-product">
|
||||
<label className="form-label">Selling Type</label>
|
||||
<div className="add-newplus">
|
||||
<label className="form-label">Category</label>
|
||||
<Link
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-units-category"
|
||||
>
|
||||
<PlusCircle className="plus-down-add" />
|
||||
<span>Add New</span>
|
||||
</Link>
|
||||
</div>
|
||||
<Select
|
||||
className="select"
|
||||
options={sellingtype}
|
||||
options={category}
|
||||
placeholder="Choose"
|
||||
value={
|
||||
category.find(
|
||||
(option) =>
|
||||
option.value === formData.category_id
|
||||
) || null
|
||||
}
|
||||
onChange={(selectedOption) =>
|
||||
handleSelectChange(
|
||||
"category_id",
|
||||
selectedOption
|
||||
)
|
||||
}
|
||||
/>
|
||||
</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">
|
||||
<label className="form-label">Category</label>
|
||||
<Link
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-units-category"
|
||||
>
|
||||
<PlusCircle className="plus-down-add" />
|
||||
<span>Add New</span>
|
||||
</Link>
|
||||
</div>
|
||||
<Select
|
||||
className="select"
|
||||
options={category}
|
||||
placeholder="Choose"
|
||||
value={
|
||||
formData.category_id
|
||||
? category.find(
|
||||
(option) =>
|
||||
option.value === formData.category_id
|
||||
)
|
||||
: null
|
||||
}
|
||||
onChange={(selectedOption) =>
|
||||
handleSelectChange(
|
||||
"category_id",
|
||||
selectedOption
|
||||
)
|
||||
}
|
||||
/>
|
||||
</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">
|
||||
<PlusCircle className="plus-down-add me-0" />
|
||||
<h4>Add Images</h4>
|
||||
{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>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
388
src/feature-module/inventory/outletlist.jsx
Normal file
388
src/feature-module/inventory/outletlist.jsx
Normal 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;
|
||||
@ -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>
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -10,28 +10,30 @@ const Signin = () => {
|
||||
const navigate = useNavigate();
|
||||
// const dispatch = useDispatch();
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
@ -62,12 +64,12 @@ const Signin = () => {
|
||||
<div className="form-login mb-3">
|
||||
<label className="form-label">Email Address</label>
|
||||
<div className="form-addons">
|
||||
<input
|
||||
type="email"
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
className="form-control"
|
||||
className="form-control"
|
||||
required
|
||||
/>
|
||||
<ImageWithBasePath
|
||||
@ -109,12 +111,12 @@ const Signin = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-login">
|
||||
<button
|
||||
type="submit"
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-login"
|
||||
disabled={authState.loading}
|
||||
>
|
||||
{authState.loading ? "Signing In..." : "Sign In"}
|
||||
{authState.loading ? 'Signing In...' : 'Sign In'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="signinform">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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) => <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">
|
||||
<Table
|
||||
className="table datanew"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey={(record) => record.id}
|
||||
// pagination={true}
|
||||
/>
|
||||
{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={dataSource}
|
||||
rowKey={(record) => record.id}
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
@ -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) => <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">
|
||||
<Table
|
||||
className="table datanew"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
/>
|
||||
{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={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
35
src/services/filesApi.js
Normal 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;
|
||||
}
|
||||
},
|
||||
};
|
||||
60
src/services/inventoriesApi.js
Normal file
60
src/services/inventoriesApi.js
Normal 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
102
src/services/outletsApi.js
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user