2025-05-25 17:15:22 +07:00

525 lines
17 KiB
JavaScript

import {
Box,
ChevronUp,
Edit,
Eye,
Filter,
GitMerge,
PlusCircle,
RotateCcw,
Sliders,
StopCircle,
Trash2,
} from "feather-icons-react/build/IconComponents";
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import Select from "react-select";
import ImageWithBasePath from "../../core/img/imagewithbasebath";
import Brand from "../../core/modals/inventory/brand";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import { all_routes } from "../../Router/all_routes";
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import Table from "../../core/pagination/datatable";
import { setToogleHeader } from "../../core/redux/action";
import { Download } from "react-feather";
import {
fetchProducts,
fetchProduct,
deleteProduct,
clearProductError
} from "../../core/redux/actions/productActions";
const ProductList = () => {
// Use new Redux structure for API data, fallback to legacy for existing functionality
const {
products: apiProducts,
loading,
error
} = useSelector((state) => state.products);
// Fallback to legacy data if API data is not available
const legacyProducts = useSelector((state) => state.legacy?.product_list || []);
const dataSource = apiProducts.length > 0 ? apiProducts : legacyProducts;
const dispatch = useDispatch();
const data = useSelector((state) => state.legacy?.toggle_header || false);
const [isFilterVisible, setIsFilterVisible] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const toggleFilterVisibility = () => {
setIsFilterVisible((prevVisibility) => !prevVisibility);
};
const route = all_routes;
// Fetch products on component mount
useEffect(() => {
const loadProducts = async () => {
try {
await dispatch(fetchProducts());
// Only fetch products - categories/brands may be included in response
// or can be extracted from products data
} catch (error) {
console.error('Failed to load products:', error);
}
};
loadProducts();
}, [dispatch]);
// Handle product deletion
const handleDeleteProduct = async (productId) => {
try {
await dispatch(deleteProduct(productId));
// Show success message
MySwal.fire({
title: "Deleted!",
text: "Product has been deleted successfully.",
icon: "success",
className: "btn btn-success",
customClass: {
confirmButton: "btn btn-success",
},
});
} catch (error) {
console.error('Failed to delete product:', error);
MySwal.fire({
title: "Error!",
text: "Failed to delete product. Please try again.",
icon: "error",
className: "btn btn-danger",
customClass: {
confirmButton: "btn btn-danger",
},
});
}
};
// Handle search
const handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value);
// You can implement debounced search here
// For now, we'll just update the search term
};
// Clear error when component unmounts
useEffect(() => {
return () => {
dispatch(clearProductError());
};
}, [dispatch]);
const options = [
{ value: "sortByDate", label: "Sort by Date" },
{ value: "140923", label: "14 09 23" },
{ value: "110923", label: "11 09 23" },
];
const productlist = [
{ value: "choose", label: "Choose Product" },
{ value: "lenovo", label: "Lenovo 3rd Generation" },
{ value: "nike", label: "Nike Jordan" },
];
const categorylist = [
{ value: "choose", label: "Choose Category" },
{ value: "laptop", label: "Laptop" },
{ value: "shoe", label: "Shoe" },
];
const subcategorylist = [
{ value: "choose", label: "Choose Sub Category" },
{ value: "computers", label: "Computers" },
{ value: "fruits", label: "Fruits" },
];
const brandlist = [
{ value: "all", label: "All Brand" },
{ value: "lenovo", label: "Lenovo" },
{ value: "nike", label: "Nike" },
];
const price = [
{ value: "price", label: "Price" },
{ value: "12500", label: "$12,500.00" },
{ value: "13000", label: "$13,000.00" }, // Replace with your actual values
];
const columns = [
{
title: "Product",
dataIndex: "product",
render: (text, record) => (
<span className="productimgname">
<Link to="/profile" className="product-img stock-img">
<ImageWithBasePath
alt={record.name || text || "Product"}
src={record.productImage || record.image || record.img}
/>
</Link>
<Link to="/profile">{text}</Link>
</span>
),
sorter: (a, b) => a.product.length - b.product.length,
},
{
title: "SKU",
dataIndex: "sku",
sorter: (a, b) => a.sku.length - b.sku.length,
},
{
title: "Category",
dataIndex: "category",
sorter: (a, b) => a.category.length - b.category.length,
},
{
title: "Brand",
dataIndex: "brand",
sorter: (a, b) => a.brand.length - b.brand.length,
},
{
title: "Price",
dataIndex: "price",
sorter: (a, b) => a.price.length - b.price.length,
},
{
title: "Unit",
dataIndex: "unit",
sorter: (a, b) => a.unit.length - b.unit.length,
},
{
title: "Qty",
dataIndex: "qty",
sorter: (a, b) => a.qty.length - b.qty.length,
},
{
title: "Created By",
dataIndex: "createdby",
render: (text, record) => (
<span className="userimgname">
<Link to="/profile" className="product-img">
<ImageWithBasePath
alt={record.createdBy || text || "User"}
src={record.img || record.avatar || record.userImage}
/>
</Link>
<Link to="/profile">{text}</Link>
</span>
),
sorter: (a, b) => a.createdby.length - b.createdby.length,
},
{
title: "Action",
dataIndex: "action",
render: (text, record) => (
<td className="action-table-data">
<div className="edit-delete-action">
<div className="input-block add-lists"></div>
<Link className="me-2 p-2" to={route.productdetails}>
<Eye className="feather-view" />
</Link>
<Link
className="me-2 p-2"
to={`${route.editproduct}/${record.id || record.key}`}
onClick={() => {
// Pre-fetch product details for editing
if (record.id || record.key) {
dispatch(fetchProduct(record.id || record.key));
}
}}
>
<Edit className="feather-edit" />
</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) {
handleDeleteProduct(record.id || record.key);
}
});
}}
>
<Trash2 className="feather-trash-2" />
</Link>
</div>
</td>
),
sorter: (a, b) => a.createdby.length - b.createdby.length,
},
];
const MySwal = withReactContent(Swal);
// Removed showConfirmationAlert as we handle confirmation inline
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>
);
return (
<div className="page-wrapper">
<div className="content">
<div className="page-header">
<div className="add-item d-flex">
<div className="page-title">
<h4>Product List</h4>
<h6>Manage your products</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={(e) => {
e.preventDefault();
dispatch(setToogleHeader(!data));
}}
>
<ChevronUp />
</Link>
</OverlayTrigger>
</li>
</ul>
<div className="page-btn">
<Link to={route.addproduct} className="btn btn-added">
<PlusCircle className="me-2 iconsize" />
Add New Product
</Link>
</div>
<div className="page-btn import">
<Link
to="#"
className="btn btn-added color"
data-bs-toggle="modal"
data-bs-target="#view-notes"
>
<Download className="me-2" />
Import Product
</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"
value={searchTerm}
onChange={handleSearch}
/>
<Link to className="btn btn-searchset">
<i data-feather="search" className="feather-search" />
</Link>
</div>
</div>
<div className="search-path">
<Link
className={`btn btn-filter ${
isFilterVisible ? "setclose" : ""
}`}
id="filter_search"
>
<Filter
className="filter-icon"
onClick={toggleFilterVisibility}
/>
<span onClick={toggleFilterVisibility}>
<ImageWithBasePath
src="assets/img/icons/closes.svg"
alt="img"
/>
</span>
</Link>
</div>
<div className="form-sort">
<Sliders className="info-img" />
<Select
className="select"
options={options}
placeholder="14 09 23"
/>
</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-12 col-sm-12">
<div className="row">
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Box className="info-img" />
<Select
className="select"
options={productlist}
placeholder="Choose Product"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<StopCircle className="info-img" />
<Select
className="select"
options={categorylist}
placeholder="Choose Category"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<GitMerge className="info-img" />
<Select
className="select"
options={subcategorylist}
placeholder="Choose Sub Category"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<StopCircle className="info-img" />
<Select
className="select"
options={brandlist}
placeholder="Nike"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<i className="fas fa-money-bill info-img" />
<Select
className="select"
options={price}
placeholder="Price"
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Link className="btn btn-filters ms-auto">
{" "}
<i
data-feather="search"
className="feather-search"
/>{" "}
Search{" "}
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* /Filter */}
<div className="table-responsive">
{loading ? (
<div className="text-center p-4">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="mt-2">Loading products...</p>
</div>
) : error ? (
<div className="alert alert-danger" role="alert">
<strong>Error:</strong> {error}
<button
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => dispatch(fetchProducts())}
>
Retry
</button>
</div>
) : (
<Table columns={columns} dataSource={dataSource} />
)}
</div>
</div>
</div>
{/* /product list */}
<Brand />
</div>
</div>
);
};
export default ProductList;