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 name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
<link rel="icon" href="./favicon.png" />
|
<link rel="icon" href="./favicon.png" />
|
||||||
<title>Dreams Pos admin template</title>
|
<title>APSKEL</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -60,13 +60,13 @@ const AddCategoryList = () => {
|
|||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="page-wrapper-new p-0">
|
<div className="page-wrapper-new p-0">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="modal-header border-0 custom-modal-header">
|
<div className="modal-header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Create Category</h4>
|
<h4>Create Category</h4>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-close btn-close-danger"
|
className="btn-close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
></button>
|
></button>
|
||||||
@ -77,7 +77,7 @@ const AddCategoryList = () => {
|
|||||||
<label className="form-label">Category</label>
|
<label className="form-label">Category</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
name="name"
|
name="name"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -88,7 +88,7 @@ const AddCategoryList = () => {
|
|||||||
<label className="form-label">Description</label>
|
<label className="form-label">Description</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
name="description"
|
name="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -109,7 +109,7 @@ const AddCategoryList = () => {
|
|||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary me-2"
|
className="btn btn-outline-dark me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@ -72,7 +72,7 @@ const EditCategoryList = () => {
|
|||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="page-wrapper-new p-0">
|
<div className="page-wrapper-new p-0">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="modal-header border-0 custom-modal-header">
|
<div className="modal-header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Edit Category</h4>
|
<h4>Edit Category</h4>
|
||||||
</div>
|
</div>
|
||||||
@ -89,7 +89,7 @@ const EditCategoryList = () => {
|
|||||||
<label className="form-label">Category</label>
|
<label className="form-label">Category</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
name="name"
|
name="name"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -100,7 +100,7 @@ const EditCategoryList = () => {
|
|||||||
<label className="form-label">Description</label>
|
<label className="form-label">Description</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
name="description"
|
name="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -121,7 +121,7 @@ const EditCategoryList = () => {
|
|||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary me-2"
|
className="btn btn-outline-dark me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@ -1,26 +1,149 @@
|
|||||||
import React from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Select from "react-select";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import ImageWithBasePath from "../../img/imagewithbasebath";
|
import AsyncSelect from "react-select/async";
|
||||||
import { Link } from "react-router-dom";
|
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 ManageStockModal = () => {
|
||||||
const options1 = [
|
const dispatch = useDispatch();
|
||||||
{ value: "choose", label: "Choose" },
|
const { creating, updating, currentInventory } = useSelector(
|
||||||
{ value: "lobarHandy", label: "Lobar Handy" },
|
(state) => state.inventories
|
||||||
{ value: "quaintWarehouse", label: "Quaint Warehouse" },
|
);
|
||||||
];
|
|
||||||
|
|
||||||
const options2 = [
|
const initializeFormData = () => {
|
||||||
{ value: "choose", label: "Choose" },
|
return {
|
||||||
{ value: "selosy", label: "Selosy" },
|
outlet_id: "",
|
||||||
{ value: "logerro", label: "Logerro" },
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Add Stock */}
|
{/* Add Stock */}
|
||||||
@ -29,65 +152,109 @@ const ManageStockModal = () => {
|
|||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="page-wrapper-new p-0">
|
<div className="page-wrapper-new p-0">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="modal-header border-0 custom-modal-header">
|
<div className="modal-header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Add Stock</h4>
|
<h4>Create Stock</h4>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="close"
|
className="btn-close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
></button>
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body custom-modal-body">
|
<div className="modal-body custom-modal-body">
|
||||||
<form>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="row">
|
<div className="mb-3">
|
||||||
<div className="col-lg-6">
|
<label className="form-label">Outlet</label>
|
||||||
<div className="input-blocks">
|
<AsyncSelect
|
||||||
<label>Warehouse</label>
|
className="select"
|
||||||
<Select className="select" options={options1} />
|
loadOptions={loadOutlets}
|
||||||
|
placeholder="Choose"
|
||||||
|
isClearable
|
||||||
|
cacheOptions={true}
|
||||||
|
defaultOptions={true}
|
||||||
|
value={selectedOutlet}
|
||||||
|
onChange={(selectedOption) => {
|
||||||
|
handleSelectChange("outlet_id", selectedOption);
|
||||||
|
setSelectedOutlet(selectedOption);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</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>
|
||||||
<div className="col-lg-6">
|
<div className="mb-3">
|
||||||
<div className="input-blocks">
|
<label className="form-label">Quantity</label>
|
||||||
<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
|
<input
|
||||||
type="text"
|
type="number"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
placeholder="Select Product"
|
name="quantity"
|
||||||
|
value={formData.quantity}
|
||||||
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
<i
|
</div>
|
||||||
data-feather="search"
|
<div className="row">
|
||||||
className="feather-search custom-search"
|
<div className="col">
|
||||||
|
<label className="form-label">Min Stock</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control border"
|
||||||
|
name="min_stock_level"
|
||||||
|
value={formData.min_stock_level}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<label className="form-label">Max Stock</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control border"
|
||||||
|
name="max_stock_level"
|
||||||
|
value={formData.max_stock_level}
|
||||||
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-cancel me-2"
|
className="btn btn-outline-dark me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="btn btn-submit">
|
<button
|
||||||
Create
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -100,167 +267,79 @@ const ManageStockModal = () => {
|
|||||||
{/* /Add Stock */}
|
{/* /Add Stock */}
|
||||||
{/* Edit Stock */}
|
{/* Edit Stock */}
|
||||||
<div className="modal fade" id="edit-units">
|
<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="modal-content">
|
||||||
<div className="page-wrapper-new p-0">
|
<div className="page-wrapper-new p-0">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="modal-header border-0 custom-modal-header">
|
<div className="modal-header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Edit Stock</h4>
|
<h4>Edit Stock</h4>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="close"
|
className="btn-close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
></button>
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body custom-modal-body">
|
<div className="modal-body custom-modal-body">
|
||||||
<form>
|
<form onSubmit={handleUpdate}>
|
||||||
<div className="input-blocks search-form">
|
<div className="mb-3">
|
||||||
<label>Product</label>
|
<label className="form-label">Quantity</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="number"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
defaultValue="Nike Jordan"
|
name="quantity"
|
||||||
/>
|
value={formData.quantity}
|
||||||
<i
|
onChange={handleInputChange}
|
||||||
data-feather="search"
|
|
||||||
className="feather-search custom-search"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="mb-3">
|
||||||
<div className="col-lg-6">
|
<label className="form-label">Min Stock</label>
|
||||||
<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
|
<input
|
||||||
type="text"
|
type="number"
|
||||||
className="form-control"
|
className="form-control border"
|
||||||
placeholder="Select Product"
|
name="min_stock_level"
|
||||||
defaultValue="Nike Jordan"
|
value={formData.min_stock_level}
|
||||||
/>
|
onChange={handleInputChange}
|
||||||
<i
|
|
||||||
data-feather="search"
|
|
||||||
className="feather-search custom-search"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="mb-3">
|
||||||
<div className="col-lg-12">
|
<label className="form-label">Max Stock</label>
|
||||||
<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
|
<input
|
||||||
type="text"
|
type="number"
|
||||||
className="quntity-input"
|
className="form-control border"
|
||||||
defaultValue={2}
|
name="max_stock_level"
|
||||||
|
value={formData.max_stock_level}
|
||||||
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
<span className="quantity-btn">
|
|
||||||
+
|
|
||||||
<i
|
|
||||||
data-feather="plus-circle"
|
|
||||||
className="plus-circle"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="action-table-data">
|
|
||||||
<div className="edit-delete-action">
|
|
||||||
<Link
|
|
||||||
className="me-2 p-2"
|
|
||||||
to="#"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#edit-units"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
data-feather="edit"
|
|
||||||
className="feather-edit"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="confirm-text p-2"
|
|
||||||
to="#"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
data-feather="trash-2"
|
|
||||||
className="feather-trash-2"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-cancel me-2"
|
className="btn btn-outline-dark me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="btn btn-submit">
|
<button
|
||||||
Save Changes
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</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 orderReducer from './reducers/orderReducer';
|
||||||
import paymentMethodReducer from './reducers/paymentMethodReducer';
|
import paymentMethodReducer from './reducers/paymentMethodReducer';
|
||||||
import organizationReducer from './reducers/organizationReducer';
|
import organizationReducer from './reducers/organizationReducer';
|
||||||
|
import inventoryReducer from './reducers/inventoryReducer';
|
||||||
|
import outletReducer from './reducers/outletReducer';
|
||||||
|
|
||||||
// Legacy reducer for existing functionality
|
// Legacy reducer for existing functionality
|
||||||
const legacyReducer = (state = initialState, action) => {
|
const legacyReducer = (state = initialState, action) => {
|
||||||
@ -82,7 +84,9 @@ const rootReducer = combineReducers({
|
|||||||
categories: categoryReducer,
|
categories: categoryReducer,
|
||||||
orders: orderReducer,
|
orders: orderReducer,
|
||||||
paymentMethods: paymentMethodReducer,
|
paymentMethods: paymentMethodReducer,
|
||||||
organizations: organizationReducer
|
organizations: organizationReducer,
|
||||||
|
inventories: inventoryReducer,
|
||||||
|
outlets: outletReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|||||||
@ -18,17 +18,17 @@ const authSlice = createSlice({
|
|||||||
},
|
},
|
||||||
loginSuccess: (state, action) => {
|
loginSuccess: (state, action) => {
|
||||||
state.isAuthenticated = true;
|
state.isAuthenticated = true;
|
||||||
state.user = action.payload.user;
|
state.user = action.payload.data.user;
|
||||||
state.token = action.payload.token;
|
state.token = action.payload.data.token;
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
// Store token in localStorage
|
// Store token in localStorage
|
||||||
localStorage.setItem('authToken', action.payload.token);
|
localStorage.setItem('authToken', action.payload.data.token);
|
||||||
localStorage.setItem('user', JSON.stringify(action.payload.user));
|
localStorage.setItem('user', JSON.stringify(action.payload.data.user));
|
||||||
},
|
},
|
||||||
loginFailure: (state, action) => {
|
loginFailure: (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error = action.payload;
|
state.error = action.payload.error;
|
||||||
state.isAuthenticated = false;
|
state.isAuthenticated = false;
|
||||||
state.user = null;
|
state.user = null;
|
||||||
state.token = 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 Select from "react-select";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
import AddBrand from "../../core/modals/addbrand";
|
|
||||||
import AddCategory from "../../core/modals/inventory/addcategory";
|
import AddCategory from "../../core/modals/inventory/addcategory";
|
||||||
import Addunits from "../../core/modals/inventory/addunits";
|
|
||||||
import { setToogleHeader } from "../../core/redux/action";
|
import { setToogleHeader } from "../../core/redux/action";
|
||||||
import { createProduct } from "../../core/redux/actions/productActions";
|
import { createProduct } from "../../core/redux/actions/productActions";
|
||||||
import { all_routes } from "../../Router/all_routes";
|
import { all_routes } from "../../Router/all_routes";
|
||||||
import { categoriesApi } from "../../services/categoriesApi";
|
import { categoriesApi } from "../../services/categoriesApi";
|
||||||
|
import { filesApi } from "../../services/filesApi";
|
||||||
|
|
||||||
const AddProduct = () => {
|
const AddProduct = () => {
|
||||||
const route = all_routes;
|
const route = all_routes;
|
||||||
@ -48,14 +47,19 @@ const AddProduct = () => {
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
sku: "",
|
sku: "",
|
||||||
|
barcode: "",
|
||||||
description: "",
|
description: "",
|
||||||
price: 0,
|
price: 0,
|
||||||
cost: 0,
|
cost: 0,
|
||||||
category_id: "",
|
category_id: "",
|
||||||
|
printer_type: "",
|
||||||
|
image_url: "",
|
||||||
});
|
});
|
||||||
|
const [tempImage, setTempImage] = useState(null);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
const [variants, setVariants] = useState([
|
const [variants, setVariants] = useState([
|
||||||
{ name: "", price_modifier: 0, cost: 0 },
|
{ name: "", price_modifier: '', cost: '' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
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(() => {
|
useEffect(() => {
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
try {
|
try {
|
||||||
@ -90,32 +126,11 @@ const AddProduct = () => {
|
|||||||
{ value: "rasmussen", label: "Rasmussen" },
|
{ value: "rasmussen", label: "Rasmussen" },
|
||||||
{ value: "fredJohn", label: "Fred John" },
|
{ value: "fredJohn", label: "Fred John" },
|
||||||
];
|
];
|
||||||
const warehouse = [
|
const printerTypes = [
|
||||||
{ value: "choose", label: "Choose" },
|
{ value: "Kitchen", label: "Kitchen" },
|
||||||
{ value: "legendary", label: "Legendary" },
|
|
||||||
{ value: "determined", label: "Determined" },
|
{ value: "determined", label: "Determined" },
|
||||||
{ value: "sincere", label: "Sincere" },
|
{ 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 = [
|
const barcodesymbol = [
|
||||||
{ value: "choose", label: "Choose" },
|
{ value: "choose", label: "Choose" },
|
||||||
{ value: "code34", label: "Code34" },
|
{ value: "code34", label: "Code34" },
|
||||||
@ -136,15 +151,9 @@ const AddProduct = () => {
|
|||||||
{ value: "percentage", label: "Percentage" },
|
{ value: "percentage", label: "Percentage" },
|
||||||
{ value: "cash", label: "Cash" },
|
{ value: "cash", label: "Cash" },
|
||||||
];
|
];
|
||||||
const [isImageVisible, setIsImageVisible] = useState(true);
|
|
||||||
|
|
||||||
const handleRemoveProduct = () => {
|
|
||||||
setIsImageVisible(false);
|
|
||||||
};
|
|
||||||
const [isImageVisible1, setIsImageVisible1] = useState(true);
|
|
||||||
|
|
||||||
const handleRemoveProduct1 = () => {
|
const handleRemoveProduct1 = () => {
|
||||||
setIsImageVisible1(false);
|
setFormData({ ...formData, image_url: "" });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
@ -229,11 +238,17 @@ const AddProduct = () => {
|
|||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
title: "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
|
// Handle select changes
|
||||||
const handleSelectChange = (field, selectedOption) => {
|
const handleSelectChange = (field, selectedOption) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
@ -350,29 +365,6 @@ const AddProduct = () => {
|
|||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<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="row">
|
||||||
<div className="col-sm-6 col-12">
|
<div className="col-sm-6 col-12">
|
||||||
<div className="mb-3 add-product">
|
<div className="mb-3 add-product">
|
||||||
@ -386,12 +378,58 @@ const AddProduct = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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="col-sm-6 col-12">
|
||||||
<div className="mb-3 add-product">
|
<div className="mb-3 add-product">
|
||||||
<label className="form-label">Slug</label>
|
<label className="form-label">Store</label>
|
||||||
<input
|
<Select
|
||||||
type="text"
|
className="select"
|
||||||
className="form-control border"
|
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>
|
||||||
</div>
|
</div>
|
||||||
@ -427,20 +465,6 @@ const AddProduct = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 col-12">
|
|
||||||
<div className="mb-3 add-product">
|
|
||||||
<label className="form-label">Selling Type</label>
|
|
||||||
<Select
|
|
||||||
className="select"
|
|
||||||
options={sellingtype}
|
|
||||||
placeholder="Choose"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="addservice-info">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-6 col-12">
|
<div className="col-sm-6 col-12">
|
||||||
<div className="mb-3 add-product">
|
<div className="mb-3 add-product">
|
||||||
<div className="add-newplus">
|
<div className="add-newplus">
|
||||||
@ -473,120 +497,8 @@ const AddProduct = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<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 */}
|
{/* Editor */}
|
||||||
<div className="col-lg-12">
|
<div className="col-lg-12">
|
||||||
<div className="input-blocks summer-description-box transfer mb-3">
|
<div className="input-blocks summer-description-box transfer mb-3">
|
||||||
@ -935,17 +847,36 @@ const AddProduct = () => {
|
|||||||
<div className="add-choosen">
|
<div className="add-choosen">
|
||||||
<div className="input-blocks">
|
<div className="input-blocks">
|
||||||
<div className="image-upload">
|
<div className="image-upload">
|
||||||
<input type="file" />
|
<input
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
disabled={uploading}
|
||||||
|
/>
|
||||||
<div className="image-uploads">
|
<div className="image-uploads">
|
||||||
|
{uploading ? (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className="spinner-border spinner-border-sm me-2"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
></span>
|
||||||
|
<h4>Uploading...</h4>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<PlusCircle className="plus-down-add me-0" />
|
<PlusCircle className="plus-down-add me-0" />
|
||||||
<h4>Add Images</h4>
|
<h4>
|
||||||
|
{tempImage ? "Edit Image" : "Add Image"}
|
||||||
|
</h4>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isImageVisible1 && (
|
{formData.image_url && (
|
||||||
<div className="phone-img">
|
<div className="phone-img">
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath
|
||||||
src="assets/img/products/phone-add-2.png"
|
src={formData.image_url}
|
||||||
alt="image"
|
alt="image"
|
||||||
/>
|
/>
|
||||||
<Link to="#">
|
<Link to="#">
|
||||||
@ -956,20 +887,6 @@ const AddProduct = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1131,9 +1048,7 @@ const AddProduct = () => {
|
|||||||
</form>
|
</form>
|
||||||
{/* /add */}
|
{/* /add */}
|
||||||
</div>
|
</div>
|
||||||
<Addunits />
|
|
||||||
<AddCategory />
|
<AddCategory />
|
||||||
<AddBrand />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -236,23 +236,6 @@ const CategoryList = () => {
|
|||||||
];
|
];
|
||||||
const MySwal = withReactContent(Swal);
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
|
|||||||
@ -20,9 +20,7 @@ import { Link, useNavigate, useParams } from "react-router-dom";
|
|||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
import AddBrand from "../../core/modals/addbrand";
|
|
||||||
import AddCategory from "../../core/modals/inventory/addcategory";
|
import AddCategory from "../../core/modals/inventory/addcategory";
|
||||||
import Addunits from "../../core/modals/inventory/addunits";
|
|
||||||
import { setToogleHeader } from "../../core/redux/action";
|
import { setToogleHeader } from "../../core/redux/action";
|
||||||
import {
|
import {
|
||||||
fetchProduct,
|
fetchProduct,
|
||||||
@ -30,6 +28,7 @@ import {
|
|||||||
} from "../../core/redux/actions/productActions";
|
} from "../../core/redux/actions/productActions";
|
||||||
import { all_routes } from "../../Router/all_routes";
|
import { all_routes } from "../../Router/all_routes";
|
||||||
import categoriesApi from "../../services/categoriesApi";
|
import categoriesApi from "../../services/categoriesApi";
|
||||||
|
import { filesApi } from "../../services/filesApi";
|
||||||
|
|
||||||
const EditProduct = () => {
|
const EditProduct = () => {
|
||||||
const route = all_routes;
|
const route = all_routes;
|
||||||
@ -52,16 +51,53 @@ const EditProduct = () => {
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
sku: "",
|
sku: "",
|
||||||
|
barcode: "",
|
||||||
description: "",
|
description: "",
|
||||||
price: 0,
|
price: 0,
|
||||||
category_id: null,
|
|
||||||
cost: 0,
|
cost: 0,
|
||||||
|
category_id: "",
|
||||||
|
printer_type: "",
|
||||||
|
image_url: "",
|
||||||
});
|
});
|
||||||
|
const [tempImage, setTempImage] = useState(null);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
const [variants, setVariants] = useState([
|
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(() => {
|
useEffect(() => {
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
try {
|
try {
|
||||||
@ -98,6 +134,9 @@ const EditProduct = () => {
|
|||||||
price: currentProduct.price || 0,
|
price: currentProduct.price || 0,
|
||||||
cost: currentProduct.cost || 0,
|
cost: currentProduct.cost || 0,
|
||||||
category_id: currentProduct.category_id,
|
category_id: currentProduct.category_id,
|
||||||
|
printer_type: currentProduct.printer_type,
|
||||||
|
image_url: currentProduct.image_url,
|
||||||
|
barcode: currentProduct.barcode,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentProduct.variants) {
|
if (currentProduct.variants) {
|
||||||
@ -134,7 +173,7 @@ const EditProduct = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addVariant = () => {
|
const addVariant = () => {
|
||||||
setVariants([...variants, { name: "", price_modifier: 0, cost: 0 }]);
|
setVariants([...variants, { name: "", price_modifier: '', cost: '' }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeVariant = (index) => {
|
const removeVariant = (index) => {
|
||||||
@ -221,7 +260,7 @@ const EditProduct = () => {
|
|||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: "success",
|
icon: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
text: "Product created successfully!",
|
text: "Product updated successfully!",
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
timer: 1500,
|
timer: 1500,
|
||||||
});
|
});
|
||||||
@ -237,7 +276,7 @@ const EditProduct = () => {
|
|||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
title: "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: "rasmussen", label: "Rasmussen" },
|
||||||
{ value: "fredJohn", label: "Fred John" },
|
{ 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 = [
|
const barcodesymbol = [
|
||||||
{ value: "choose", label: "Choose" },
|
|
||||||
{ value: "code34", label: "Code34" },
|
{ value: "code34", label: "Code34" },
|
||||||
{ value: "code35", label: "Code35" },
|
{ value: "code35", label: "Code35" },
|
||||||
{ value: "code36", label: "Code36" },
|
{ value: "code36", label: "Code36" },
|
||||||
];
|
];
|
||||||
|
const printerTypes = [
|
||||||
|
{ value: "Kitchen", label: "Kitchen" },
|
||||||
|
{ value: "determined", label: "Determined" },
|
||||||
|
{ value: "sincere", label: "Sincere" },
|
||||||
|
];
|
||||||
const taxtype = [
|
const taxtype = [
|
||||||
{ value: "exclusive", label: "Exclusive" },
|
{ value: "exclusive", label: "Exclusive" },
|
||||||
{ value: "salesTax", label: "Sales Tax" },
|
{ value: "salesTax", label: "Sales Tax" },
|
||||||
@ -299,15 +311,9 @@ const EditProduct = () => {
|
|||||||
{ value: "percentage", label: "Percentage" },
|
{ value: "percentage", label: "Percentage" },
|
||||||
{ value: "cash", label: "Cash" },
|
{ value: "cash", label: "Cash" },
|
||||||
];
|
];
|
||||||
const [isImageVisible, setIsImageVisible] = useState(true);
|
|
||||||
|
|
||||||
const handleRemoveProduct = () => {
|
|
||||||
setIsImageVisible(false);
|
|
||||||
};
|
|
||||||
const [isImageVisible1, setIsImageVisible1] = useState(true);
|
|
||||||
|
|
||||||
const handleRemoveProduct1 = () => {
|
const handleRemoveProduct1 = () => {
|
||||||
setIsImageVisible1(false);
|
setFormData({ ...formData, image_url: "" });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
@ -413,7 +419,7 @@ const EditProduct = () => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{/* /add */}
|
{/* /edit */}
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body add-product">
|
<div className="card-body add-product">
|
||||||
@ -448,29 +454,6 @@ const EditProduct = () => {
|
|||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
<div className="accordion-body">
|
<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="row">
|
||||||
<div className="col-sm-6 col-12">
|
<div className="col-sm-6 col-12">
|
||||||
<div className="mb-3 add-product">
|
<div className="mb-3 add-product">
|
||||||
@ -484,12 +467,58 @@ const EditProduct = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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="col-sm-6 col-12">
|
||||||
<div className="mb-3 add-product">
|
<div className="mb-3 add-product">
|
||||||
<label className="form-label">Slug</label>
|
<label className="form-label">Store</label>
|
||||||
<input
|
<Select
|
||||||
type="text"
|
className="select"
|
||||||
className="form-control border"
|
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>
|
||||||
</div>
|
</div>
|
||||||
@ -525,20 +554,6 @@ const EditProduct = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 col-12">
|
|
||||||
<div className="mb-3 add-product">
|
|
||||||
<label className="form-label">Selling Type</label>
|
|
||||||
<Select
|
|
||||||
className="select"
|
|
||||||
options={sellingtype}
|
|
||||||
placeholder="Choose"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="addservice-info">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-6 col-12">
|
<div className="col-sm-6 col-12">
|
||||||
<div className="mb-3 add-product">
|
<div className="mb-3 add-product">
|
||||||
<div className="add-newplus">
|
<div className="add-newplus">
|
||||||
@ -557,12 +572,10 @@ const EditProduct = () => {
|
|||||||
options={category}
|
options={category}
|
||||||
placeholder="Choose"
|
placeholder="Choose"
|
||||||
value={
|
value={
|
||||||
formData.category_id
|
category.find(
|
||||||
? category.find(
|
|
||||||
(option) =>
|
(option) =>
|
||||||
option.value === formData.category_id
|
option.value === formData.category_id
|
||||||
)
|
) || null
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
onChange={(selectedOption) =>
|
onChange={(selectedOption) =>
|
||||||
handleSelectChange(
|
handleSelectChange(
|
||||||
@ -573,104 +586,8 @@ const EditProduct = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<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 */}
|
{/* Editor */}
|
||||||
<div className="col-lg-12">
|
<div className="col-lg-12">
|
||||||
<div className="input-blocks summer-description-box transfer mb-3">
|
<div className="input-blocks summer-description-box transfer mb-3">
|
||||||
@ -774,6 +691,7 @@ const EditProduct = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tab-content" id="pills-tabContent">
|
<div className="tab-content" id="pills-tabContent">
|
||||||
<div
|
<div
|
||||||
className="tab-pane fade show active"
|
className="tab-pane fade show active"
|
||||||
@ -786,7 +704,7 @@ const EditProduct = () => {
|
|||||||
<div className="add-product">
|
<div className="add-product">
|
||||||
<label className="form-label">Quantity</label>
|
<label className="form-label">Quantity</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="number"
|
||||||
className="form-control border"
|
className="form-control border"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -1018,17 +936,36 @@ const EditProduct = () => {
|
|||||||
<div className="add-choosen">
|
<div className="add-choosen">
|
||||||
<div className="input-blocks">
|
<div className="input-blocks">
|
||||||
<div className="image-upload">
|
<div className="image-upload">
|
||||||
<input type="file" />
|
<input
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
disabled={uploading}
|
||||||
|
/>
|
||||||
<div className="image-uploads">
|
<div className="image-uploads">
|
||||||
|
{uploading ? (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className="spinner-border spinner-border-sm me-2"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
></span>
|
||||||
|
<h4>Uploading...</h4>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<PlusCircle className="plus-down-add me-0" />
|
<PlusCircle className="plus-down-add me-0" />
|
||||||
<h4>Add Images</h4>
|
<h4>
|
||||||
|
{tempImage ? "Edit Image" : "Add Image"}
|
||||||
|
</h4>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isImageVisible1 && (
|
{formData.image_url && (
|
||||||
<div className="phone-img">
|
<div className="phone-img">
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath
|
||||||
src="assets/img/products/phone-add-2.png"
|
src={formData.image_url}
|
||||||
alt="image"
|
alt="image"
|
||||||
/>
|
/>
|
||||||
<Link to="#">
|
<Link to="#">
|
||||||
@ -1039,20 +976,6 @@ const EditProduct = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1214,9 +1137,7 @@ const EditProduct = () => {
|
|||||||
</form>
|
</form>
|
||||||
{/* /add */}
|
{/* /add */}
|
||||||
</div>
|
</div>
|
||||||
<Addunits />
|
|
||||||
<AddCategory />
|
<AddCategory />
|
||||||
<AddBrand />
|
|
||||||
</div>
|
</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>
|
<h4>Category</h4>
|
||||||
<h6>{currentProduct?.category ?? "-"}</h6>
|
<h6>{currentProduct?.category ?? "-"}</h6>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<h4>Business</h4>
|
|
||||||
<h6>{currentProduct?.business_type ?? "-"}</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>Unit</h4>
|
|
||||||
<h6>Piece</h6>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<h4>SKU</h4>
|
<h4>SKU</h4>
|
||||||
<h6>{currentProduct?.sku ?? "-"}</h6>
|
<h6>{currentProduct?.sku ?? "-"}</h6>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Business</h4>
|
||||||
|
<h6>{currentProduct?.business_type ?? "-"}</h6>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Printer</h4>
|
||||||
|
<h6>{currentProduct?.printer_type ?? "-"}</h6>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Price</h4>
|
<h4>Price</h4>
|
||||||
<h6>{formatRupiah(Number(currentProduct?.price))}</h6>
|
<h6>{formatRupiah(Number(currentProduct?.price))}</h6>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Cost</h4>
|
||||||
|
<h6>{formatRupiah(Number(currentProduct?.cost))}</h6>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Status</h4>
|
<h4>Status</h4>
|
||||||
<h6>
|
<h6>
|
||||||
@ -141,11 +145,10 @@ const ProductDetail = () => {
|
|||||||
<div className="owl-carousel owl-theme product-slide">
|
<div className="owl-carousel owl-theme product-slide">
|
||||||
<div className="slider-product">
|
<div className="slider-product">
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath
|
||||||
src="assets/img/products/product69.jpg"
|
src={currentProduct?.image_url}
|
||||||
alt="img"
|
alt="img"
|
||||||
/>
|
/>
|
||||||
<h4>{currentProduct?.name}</h4>
|
<h4>{currentProduct?.name}</h4>
|
||||||
<h6>581kb</h6>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -594,7 +594,17 @@ const ProductList = () => {
|
|||||||
{
|
{
|
||||||
title: "Product",
|
title: "Product",
|
||||||
dataIndex: "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,
|
sorter: (a, b) => a.product.length - b.product.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -610,7 +620,6 @@ const ProductList = () => {
|
|||||||
return catA.length - catB.length;
|
return catA.length - catB.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Business",
|
title: "Business",
|
||||||
dataIndex: "business",
|
dataIndex: "business",
|
||||||
@ -624,6 +633,19 @@ const ProductList = () => {
|
|||||||
return brandA.length - brandB.length;
|
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",
|
title: "Price",
|
||||||
dataIndex: "price",
|
dataIndex: "price",
|
||||||
@ -638,53 +660,18 @@ const ProductList = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Unit",
|
title: "Cost",
|
||||||
dataIndex: "unit",
|
dataIndex: "cost",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const unit = record.unit || record.unitOfMeasure || "-";
|
const cost = record.cost || 0;
|
||||||
return <span>{unit}</span>;
|
return <span>{formatRupiah(Number(cost))}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const unitA = a.unit || a.unitOfMeasure || "";
|
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
|
||||||
const unitB = b.unit || b.unitOfMeasure || "";
|
const priceB = Number(b.price || b.salePrice || b.unitPrice || 0);
|
||||||
return unitA.length - unitB.length;
|
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",
|
title: "Action",
|
||||||
dataIndex: "action",
|
dataIndex: "action",
|
||||||
|
|||||||
@ -12,26 +12,28 @@ const Signin = () => {
|
|||||||
const authState = useSelector((state) => state.auth);
|
const authState = useSelector((state) => state.auth);
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
email: "",
|
email: '',
|
||||||
password: "",
|
password: ''
|
||||||
});
|
});
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
[e.target.name]: e.target.value,
|
[e.target.name]: e.target.value
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError("");
|
setError('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
authApi.login(formData).then(() => navigate(route.dashboard));
|
await authApi.login(formData)
|
||||||
|
|
||||||
|
navigate(route.dashboard);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error.message || "Login failed");
|
setError(error.message || 'Login failed');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ const Signin = () => {
|
|||||||
className="btn btn-login"
|
className="btn btn-login"
|
||||||
disabled={authState.loading}
|
disabled={authState.loading}
|
||||||
>
|
>
|
||||||
{authState.loading ? "Signing In..." : "Sign In"}
|
{authState.loading ? 'Signing In...' : 'Sign In'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="signinform">
|
<div className="signinform">
|
||||||
|
|||||||
@ -84,6 +84,7 @@ const SalesList = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setDebouncedSearchTerm(searchTerm);
|
setDebouncedSearchTerm(searchTerm);
|
||||||
|
handleSetParams("page", 1);
|
||||||
}, 500); // 500ms delay
|
}, 500); // 500ms delay
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
@ -93,8 +94,6 @@ const SalesList = () => {
|
|||||||
const handleSearch = (e) => {
|
const handleSearch = (e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setSearchTerm(value);
|
setSearchTerm(value);
|
||||||
// Reset to first page when searching
|
|
||||||
handleSetParams("page", 1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle pagination
|
// Handle pagination
|
||||||
@ -824,19 +823,27 @@ const SalesList = () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Order Tax</td>
|
<td>Order Tax</td>
|
||||||
<td className="text-end">{formatRupiah(selectedOrder?.tax_amount)}</td>
|
<td className="text-end">
|
||||||
|
{formatRupiah(selectedOrder?.tax_amount)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Discount</td>
|
<td>Discount</td>
|
||||||
<td className="text-end">{formatRupiah(selectedOrder?.discount_amount)}</td>
|
<td className="text-end">
|
||||||
|
{formatRupiah(selectedOrder?.discount_amount)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="fw-bold">
|
<tr className="fw-bold">
|
||||||
<td>Grand Total</td>
|
<td>Grand Total</td>
|
||||||
<td className="text-end">{formatRupiah(selectedOrder?.total_amount)}</td>
|
<td className="text-end">
|
||||||
|
{formatRupiah(selectedOrder?.total_amount)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Sub Total</td>
|
<td>Sub Total</td>
|
||||||
<td className="text-end">{formatRupiah(selectedOrder?.subtotal)}</td>
|
<td className="text-end">
|
||||||
|
{formatRupiah(selectedOrder?.subtotal)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -1,115 +1,181 @@
|
|||||||
import React, { useState } from "react";
|
import { Tag } from "antd";
|
||||||
import Breadcrumbs from "../../core/breadcrumbs";
|
import { useEffect, useState } from "react";
|
||||||
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 "react-datepicker/dist/react-datepicker.css";
|
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 { Edit, Trash2 } from "react-feather";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
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 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 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 dispatch = useDispatch();
|
||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const dataSource = apiInventories?.length > 0 ? apiInventories : [];
|
||||||
|
|
||||||
const toggleFilterVisibility = () => {
|
const [params, setParams] = useState({
|
||||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
page: reduxCurrentPage || 1,
|
||||||
};
|
limit: reduxPageSize || 10,
|
||||||
const handleDateChange = (date) => {
|
});
|
||||||
setSelectedDate(date);
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadInventories = async () => {
|
||||||
|
try {
|
||||||
|
const receivedParams = {
|
||||||
|
page: params.page,
|
||||||
|
limit: params.limit,
|
||||||
|
search: debouncedSearchTerm || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = [
|
// Remove empty parameters
|
||||||
{ value: "sortByDate", label: "Sort by Date" },
|
const cleanParams = Object.fromEntries(
|
||||||
{ value: "140923", label: "14 09 23" },
|
Object.entries(receivedParams).filter(([, value]) => value !== "")
|
||||||
{ value: "110923", label: "11 09 23" },
|
);
|
||||||
];
|
|
||||||
|
|
||||||
const warehouseOptions = [
|
await dispatch(fetchInventories(cleanParams));
|
||||||
{ value: "Choose Warehouse", label: "Choose Warehouse" },
|
} catch (error) {
|
||||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
console.error("Failed to fetch orders", error);
|
||||||
{ value: "Quaint Warehouse", label: "Quaint Warehouse" },
|
}
|
||||||
{ value: "Traditional Warehouse", label: "Traditional Warehouse" },
|
};
|
||||||
{ value: "Cool Warehouse", label: "Cool Warehouse" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const productOptions = [
|
loadInventories();
|
||||||
{ value: "Choose Product", label: "Choose Product" },
|
}, [dispatch, params, debouncedSearchTerm]);
|
||||||
{ 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" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const personOptions = [
|
// Debounce search term
|
||||||
{ value: "Choose Person", label: "Choose Person" },
|
useEffect(() => {
|
||||||
{ value: "Steven", label: "Steven" },
|
const timer = setTimeout(() => {
|
||||||
{ value: "Gravely", label: "Gravely" },
|
setDebouncedSearchTerm(searchTerm);
|
||||||
];
|
handleSetParams("page", 1);
|
||||||
|
}, 500); // 500ms delay
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
const handleSetParams = (key, value) => {
|
||||||
|
setParams({
|
||||||
|
...params,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchTerm(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle pagination
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
handleSetParams("page", page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle page size change
|
||||||
|
const handlePageSizeChange = (newPageSize) => {
|
||||||
|
handleSetParams("limit", newPageSize);
|
||||||
|
handleSetParams("page", 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
try {
|
||||||
|
await dispatch(deleteInventory(id));
|
||||||
|
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Deleted!",
|
||||||
|
text: "Your stock has been deleted.",
|
||||||
|
className: "btn btn-success",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
confirmButton: "btn btn-success",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete inventory", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate pagination info
|
||||||
|
const totalRecords = totalInventories || dataSource.length;
|
||||||
|
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
||||||
|
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Warehouse",
|
title: "Outlet",
|
||||||
dataIndex: "Warehouse",
|
dataIndex: "outlet",
|
||||||
|
render: (_, record) => <span>{record.outlet || "-"}</span>,
|
||||||
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Shop",
|
|
||||||
dataIndex: "Shop",
|
|
||||||
sorter: (a, b) => a.Shop.length - b.Shop.length,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Product",
|
title: "Product",
|
||||||
dataIndex: "Product",
|
dataIndex: "Product",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span className="userimgname">
|
<span className="userimgname">
|
||||||
<Link to="#" className="product-img">
|
<Link to="#" className="product-img">
|
||||||
<ImageWithBasePath alt="img" src={record.Product.Image} />
|
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="#">{record.Product.Name}</Link>
|
<Link to="#">{record.product_name || ""}</Link>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Date",
|
title: "Qty",
|
||||||
dataIndex: "Date",
|
dataIndex: "qty",
|
||||||
|
render: (_, record) => <span>{record.quantity || 0}</span>,
|
||||||
sorter: (a, b) => a.Email.length - b.Email.length,
|
sorter: (a, b) => a.Email.length - b.Email.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Person",
|
title: "Status",
|
||||||
dataIndex: "Person",
|
dataIndex: "status",
|
||||||
render: (text, record) => (
|
render: (_, record) => (
|
||||||
<span className="userimgname">
|
<Tag color={record.is_low_stock ? "red" : "green"}>
|
||||||
<Link to="#" className="product-img">
|
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
|
||||||
<ImageWithBasePath alt="img" src={record.Person.Image} />
|
</Tag>
|
||||||
</Link>
|
|
||||||
<Link to="#">{record.Person.Name}</Link>
|
|
||||||
</span>
|
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Quantity",
|
title: "Record Level",
|
||||||
dataIndex: "Quantity",
|
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,
|
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Action",
|
title: "Action",
|
||||||
dataIndex: "action",
|
dataIndex: "action",
|
||||||
render: () => (
|
render: (_, record) => (
|
||||||
<td className="action-table-data">
|
<td className="action-table-data">
|
||||||
<div className="edit-delete-action">
|
<div className="edit-delete-action">
|
||||||
<div className="input-block add-lists"></div>
|
<div className="input-block add-lists"></div>
|
||||||
@ -119,6 +185,12 @@ const Managestock = () => {
|
|||||||
to="#"
|
to="#"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#edit-units"
|
data-bs-target="#edit-units"
|
||||||
|
onClick={() =>
|
||||||
|
dispatch({
|
||||||
|
type: INVENTORY_ACTIONS.FETCH_INVENTORY_SUCCESS,
|
||||||
|
payload: { data: record },
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Edit className="feather-edit" />
|
<Edit className="feather-edit" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -126,7 +198,7 @@ const Managestock = () => {
|
|||||||
<Link
|
<Link
|
||||||
className="confirm-text p-2"
|
className="confirm-text p-2"
|
||||||
to="#"
|
to="#"
|
||||||
onClick={showConfirmationAlert}
|
onClick={() => showConfirmationAlert(record.id)}
|
||||||
>
|
>
|
||||||
<Trash2 className="feather-trash-2" />
|
<Trash2 className="feather-trash-2" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -139,7 +211,7 @@ const Managestock = () => {
|
|||||||
|
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
const showConfirmationAlert = () => {
|
const showConfirmationAlert = (id) => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Are you sure?",
|
title: "Are you sure?",
|
||||||
text: "You won't be able to revert this!",
|
text: "You won't be able to revert this!",
|
||||||
@ -150,20 +222,13 @@ const Managestock = () => {
|
|||||||
cancelButtonText: "Cancel",
|
cancelButtonText: "Cancel",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
MySwal.fire({
|
handleDelete(id);
|
||||||
title: "Deleted!",
|
|
||||||
text: "Your file has been deleted.",
|
|
||||||
className: "btn btn-success",
|
|
||||||
confirmButtonText: "OK",
|
|
||||||
customClass: {
|
|
||||||
confirmButton: "btn btn-success",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
MySwal.close();
|
MySwal.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -182,117 +247,58 @@ const Managestock = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="form-control form-control-sm formsearch"
|
className="form-control form-control-sm formsearch"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
<Link to className="btn btn-searchset">
|
<Link to className="btn btn-searchset">
|
||||||
<i data-feather="search" className="feather-search" />
|
<i data-feather="search" className="feather-search" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<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">
|
<div className="table-responsive">
|
||||||
|
{loading ? (
|
||||||
|
<div className="text-center p-4">
|
||||||
|
<div className="spinner-border text-primary" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2">Loading products...</p>
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="alert alert-danger" role="alert">
|
||||||
|
<strong>Error:</strong> {error}
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline-danger ms-2"
|
||||||
|
onClick={() => dispatch(fetchInventories())}
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Table
|
<Table
|
||||||
className="table datanew"
|
className="table datanew"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data}
|
dataSource={dataSource}
|
||||||
rowKey={(record) => record.id}
|
rowKey={(record) => record.id}
|
||||||
// pagination={true}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CustomPagination
|
||||||
|
currentPage={params.page}
|
||||||
|
pageSize={params.limit}
|
||||||
|
totalCount={totalRecords}
|
||||||
|
totalPages={actualTotalPages}
|
||||||
|
loading={loading}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
onPageSizeChange={handlePageSizeChange}
|
||||||
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
|
showInfo={true}
|
||||||
|
showPageSizeSelector={true}
|
||||||
|
compact={false}
|
||||||
|
className="product-list-pagination"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,118 +1,153 @@
|
|||||||
import React, { useState } from "react";
|
import { useEffect, 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 "react-datepicker/dist/react-datepicker.css";
|
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 Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
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 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 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 dispatch = useDispatch();
|
||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const dataSource = apiInventories?.length > 0 ? apiInventories : [];
|
||||||
|
|
||||||
const toggleFilterVisibility = () => {
|
const [params, setParams] = useState({
|
||||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
page: reduxCurrentPage || 1,
|
||||||
|
limit: reduxPageSize || 10,
|
||||||
|
});
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadInventories = async () => {
|
||||||
|
try {
|
||||||
|
const receivedParams = {
|
||||||
|
page: params.page,
|
||||||
|
limit: params.limit,
|
||||||
|
search: debouncedSearchTerm || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = (date) => {
|
// Remove empty parameters
|
||||||
setSelectedDate(date);
|
const cleanParams = Object.fromEntries(
|
||||||
|
Object.entries(receivedParams).filter(([, value]) => value !== "")
|
||||||
|
);
|
||||||
|
|
||||||
|
await dispatch(fetchInventories(cleanParams));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch orders", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const options = [
|
|
||||||
{ value: "sortByDate", label: "Sort by Date" },
|
|
||||||
{ value: "140923", label: "14 09 23" },
|
|
||||||
{ value: "110923", label: "11 09 23" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const warehouseOptions = [
|
loadInventories();
|
||||||
{ value: "Choose Warehouse", label: "Choose Warehouse" },
|
}, [dispatch, params, debouncedSearchTerm]);
|
||||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
|
||||||
{ value: "Quaint Warehouse", label: "Quaint Warehouse" },
|
|
||||||
{ value: "Traditional Warehouse", label: "Traditional Warehouse" },
|
|
||||||
{ value: "Cool Warehouse", label: "Cool Warehouse" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const productOptions = [
|
// Debounce search term
|
||||||
{ value: "Choose Product", label: "Choose Product" },
|
useEffect(() => {
|
||||||
{ value: "Nike Jordan", label: "Nike Jordan" },
|
const timer = setTimeout(() => {
|
||||||
{ value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" },
|
setDebouncedSearchTerm(searchTerm);
|
||||||
{ value: "Amazon Echo Dot", label: "Amazon Echo Dot" },
|
handleSetParams("page", 1);
|
||||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
}, 500); // 500ms delay
|
||||||
];
|
|
||||||
|
|
||||||
const personOptions = [
|
return () => clearTimeout(timer);
|
||||||
{ value: "Choose Person", label: "Choose Person" },
|
}, [searchTerm]);
|
||||||
{ value: "Steven", label: "Steven" },
|
|
||||||
{ value: "Gravely", label: "Gravely" },
|
const handleSetParams = (key, value) => {
|
||||||
];
|
setParams({
|
||||||
|
...params,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchTerm(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle pagination
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
handleSetParams("page", page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle page size change
|
||||||
|
const handlePageSizeChange = (newPageSize) => {
|
||||||
|
handleSetParams("limit", newPageSize);
|
||||||
|
handleSetParams("page", 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate pagination info
|
||||||
|
const totalRecords = totalInventories || dataSource.length;
|
||||||
|
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
||||||
|
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Warehouse",
|
title: "Outlet",
|
||||||
dataIndex: "Warehouse",
|
dataIndex: "outlet",
|
||||||
|
render: (_, record) => <span>{record.outlet || "-"}</span>,
|
||||||
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Shop",
|
|
||||||
dataIndex: "Shop",
|
|
||||||
sorter: (a, b) => a.Shop.length - b.Shop.length,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Product",
|
title: "Product",
|
||||||
dataIndex: "Product",
|
dataIndex: "Product",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span className="userimgname">
|
<span className="userimgname">
|
||||||
<Link to="#" className="product-img">
|
<Link to="#" className="product-img">
|
||||||
<ImageWithBasePath alt="img" src={record.Product.Image} />
|
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="#">{record.Product.Name}</Link>
|
<Link to="#">{record.product_name || ""}</Link>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Date",
|
title: "Qty",
|
||||||
dataIndex: "Date",
|
dataIndex: "qty",
|
||||||
|
render: (_, record) => <span>{record.quantity || 0}</span>,
|
||||||
sorter: (a, b) => a.Email.length - b.Email.length,
|
sorter: (a, b) => a.Email.length - b.Email.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Person",
|
title: "Status",
|
||||||
dataIndex: "Person",
|
dataIndex: "status",
|
||||||
render: (text, record) => (
|
render: (_, record) => (
|
||||||
<span className="userimgname">
|
<Tag color={record.is_low_stock ? "red" : "green"}>
|
||||||
<Link to="#" className="product-img">
|
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
|
||||||
<ImageWithBasePath alt="img" src={record.Person.Image} />
|
</Tag>
|
||||||
</Link>
|
|
||||||
<Link to="#">{record.Person.Name}</Link>
|
|
||||||
</span>
|
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Notes",
|
title: "Record Level",
|
||||||
// dataIndex: "Quantity",
|
dataIndex: "recordlevel",
|
||||||
render: () => (
|
render: (_, record) => <span>{record.record_level || "-"}</span>,
|
||||||
<Link
|
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||||
to="#"
|
},
|
||||||
className="view-note"
|
{
|
||||||
data-bs-toggle="modal"
|
title: "Updated Date",
|
||||||
data-bs-target="#view-notes"
|
dataIndex: "updateddate",
|
||||||
>
|
render: (_, record) => (
|
||||||
View Note
|
<span>{formatDate(record.updated_at) || "-"}</span>
|
||||||
</Link>
|
|
||||||
),
|
),
|
||||||
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"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="form-control form-control-sm formsearch"
|
className="form-control form-control-sm formsearch"
|
||||||
|
name="search"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
<Link to className="btn btn-searchset">
|
<Link to className="btn btn-searchset">
|
||||||
<i data-feather="search" className="feather-search" />
|
<i data-feather="search" className="feather-search" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<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">
|
<div className="table-responsive">
|
||||||
|
{loading ? (
|
||||||
|
<div className="text-center p-4">
|
||||||
|
<div className="spinner-border text-primary" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2">Loading products...</p>
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="alert alert-danger" role="alert">
|
||||||
|
<strong>Error:</strong> {error}
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline-danger ms-2"
|
||||||
|
onClick={() => dispatch(fetchInventories())}
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Table
|
<Table
|
||||||
className="table datanew"
|
className="table datanew"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data}
|
dataSource={dataSource}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CustomPagination
|
||||||
|
currentPage={params.page}
|
||||||
|
pageSize={params.limit}
|
||||||
|
totalCount={totalRecords}
|
||||||
|
totalPages={actualTotalPages}
|
||||||
|
loading={loading}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
onPageSizeChange={handlePageSizeChange}
|
||||||
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
|
showInfo={true}
|
||||||
|
showPageSizeSelector={true}
|
||||||
|
compact={false}
|
||||||
|
className="product-list-pagination"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 {
|
.custom-table .ant-table-thead .ant-table-cell {
|
||||||
background-color: #fafbfe;
|
background-color: #fafbfe;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-table .ant-table-tbody > tr > td {
|
|
||||||
padding-top: 1px;
|
|
||||||
padding-bottom: 1px;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -256,13 +256,12 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.custom-modal-header {
|
.custom-modal-header {
|
||||||
background: $body-bg;
|
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
.page-title {
|
.page-title {
|
||||||
h4 {
|
h4 {
|
||||||
font-size: $font-size-18;
|
font-size: $font-size-18;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
color: $secondary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user