feat: Add inventory management pages with enhanced features

- Add Nhập kho (Import) page (/product-list-2):
  * Display product, code, import price, import quantity, import date
  * Smart date-based CSS styling (green/yellow/blue/red by time)
  * Import date loads from record.createdDate
  * Remove all checkboxes and selection features
  * Custom pagination and search functionality

- Add Tồn kho (Inventory) page (/product-list-3):
  * Display total imported, total exported, current stock by product code
  * Remove warehouse location column
  * Smart CSS styling by data type (green/yellow/blue/purple)
  * Mockup data with 5 sample products
  * Inventory value calculation (stock × unit price)

- Update routing and sidebar:
  * Add routes for both new pages
  * Add menu items in inventory section
  * Archive icon for inventory page

- Enhanced UI/UX:
  * Beautiful badge styling with hover effects
  * Responsive design and loading states
  * Vietnamese localization
  * Hide Ant Design pagination and checkboxes completely
This commit is contained in:
tuan.cna 2025-06-17 23:41:29 +07:00
parent ad6106ff67
commit 1171d7c452
6 changed files with 1593 additions and 30 deletions

View File

@ -1,6 +1,8 @@
export const all_routes = { export const all_routes = {
dashboard: "/", dashboard: "/",
productlist: "/product-list", productlist: "/product-list",
productlist2: "/product-list-2",
productlist3: "/product-list-3",
addproduct: "/add-product", addproduct: "/add-product",
salesdashboard: "/sales-dashboard", salesdashboard: "/sales-dashboard",
brandlist: "/brand-list", brandlist: "/brand-list",

View File

@ -199,6 +199,8 @@ import ProjectTracker from "../feature-module/projects/projecttracker";
import CreateProject from "../feature-module/projects/createproject"; import CreateProject from "../feature-module/projects/createproject";
import EnhancedLoaders from "../feature-module/uiinterface/enhanced-loaders"; import EnhancedLoaders from "../feature-module/uiinterface/enhanced-loaders";
import WeddingGuestList from "../feature-module/inventory/weddingGuestList"; import WeddingGuestList from "../feature-module/inventory/weddingGuestList";
import ProductList2 from "../feature-module/inventory/productlist2";
import ProductList3 from "../feature-module/inventory/productlist3";
import { all_routes } from "./all_routes"; import { all_routes } from "./all_routes";
export const publicRoutes = [ export const publicRoutes = [
{ {
@ -215,6 +217,20 @@ export const publicRoutes = [
element: <ProductList />, element: <ProductList />,
route: Route, route: Route,
}, },
{
id: 2.1,
path: routes.productlist2,
name: "products2",
element: <ProductList2 />,
route: Route,
},
{
id: 2.2,
path: routes.productlist3,
name: "products3",
element: <ProductList3 />,
route: Route,
},
{ {
id: 3, id: 3,
path: routes.addproduct, path: routes.addproduct,

View File

@ -62,9 +62,11 @@ export const SidebarData = [
submenuItems: [ submenuItems: [
{ label: "Tiến độ dự án", link: "/project-tracker",icon: <Icon.Layers />,showSubRoute: false}, { label: "Tiến độ dự án", link: "/project-tracker",icon: <Icon.Layers />,showSubRoute: false},
{ label: "Sản phẩm", link: "/product-list", icon:<Icon.Box />,showSubRoute: false,submenu: false }, { label: "Sản phẩm", link: "/product-list", icon:<Icon.Box />,showSubRoute: false,submenu: false },
{ label: "Nhập kho", link: "/product-list-2", icon:<Icon.Package />,showSubRoute: false,submenu: false },
{ label: "Tồn kho", link: "/product-list-3", icon:<Icon.Archive />,showSubRoute: false,submenu: false },
{ label: "Create Product", link: "/add-product", icon: <Icon.PlusSquare />,showSubRoute: false, submenu: false }, { label: "Create Product", link: "/add-product", icon: <Icon.PlusSquare />,showSubRoute: false, submenu: false },
{ label: "Expired Products", link: "/expired-products", icon: <Icon.Codesandbox />,showSubRoute: false,submenu: false }, { label: "Sản phẩm hết hạn", link: "/expired-products", icon: <Icon.Codesandbox />,showSubRoute: false,submenu: false },
{ label: "Low Stocks", link: "/low-stocks", icon: <Icon.TrendingDown />,showSubRoute: false,submenu: false }, { label: "Hàng tồn kho", link: "/low-stocks", icon: <Icon.TrendingDown />,showSubRoute: false,submenu: false },
{ label: "Danh mục", link: "/category-list", icon: <Icon.Codepen />,showSubRoute: false,submenu: false }, { label: "Danh mục", link: "/category-list", icon: <Icon.Codepen />,showSubRoute: false,submenu: false },
{ label: "Sub Category", link: "/sub-categories", icon: <Icon.Speaker />,showSubRoute: false,submenu: false }, { label: "Sub Category", link: "/sub-categories", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
{ label: "Thương hiệu", link: "/brand-list", icon: <Icon.Tag />,showSubRoute: false,submenu: false }, { label: "Thương hiệu", link: "/brand-list", icon: <Icon.Tag />,showSubRoute: false,submenu: false },

View File

@ -28,20 +28,20 @@ const ExpiredProduct = () => {
}; };
const oldandlatestvalue = [ const oldandlatestvalue = [
{ value: 'date', label: 'Sort by Date' }, { value: 'date', label: 'Sắp xếp theo ngày' },
{ value: 'newest', label: 'Newest' }, { value: 'newest', label: 'Mới nhất' },
{ value: 'oldest', label: 'Oldest' }, { value: 'oldest', label: 'Cũ nhất' },
]; ];
const brands = [ const brands = [
{ value: 'chooseType', label: 'Choose Type' }, { value: 'chooseType', label: 'Chọn loại' },
{ value: 'lenovo3rdGen', label: 'Lenovo 3rd Generation' }, { value: 'lenovo3rdGen', label: 'Lenovo thế hệ 3' },
{ value: 'nikeJordan', label: 'Nike Jordan' }, { value: 'nikeJordan', label: 'Nike Jordan' },
]; ];
const renderTooltip = (props) => ( const renderTooltip = (props) => (
<Tooltip id="pdf-tooltip" {...props}> <Tooltip id="pdf-tooltip" {...props}>
Pdf PDF
</Tooltip> </Tooltip>
); );
const renderExcelTooltip = (props) => ( const renderExcelTooltip = (props) => (
@ -51,23 +51,23 @@ const ExpiredProduct = () => {
); );
const renderPrinterTooltip = (props) => ( const renderPrinterTooltip = (props) => (
<Tooltip id="printer-tooltip" {...props}> <Tooltip id="printer-tooltip" {...props}>
Printer In ấn
</Tooltip> </Tooltip>
); );
const renderRefreshTooltip = (props) => ( const renderRefreshTooltip = (props) => (
<Tooltip id="refresh-tooltip" {...props}> <Tooltip id="refresh-tooltip" {...props}>
Refresh Làm mới
</Tooltip> </Tooltip>
); );
const renderCollapseTooltip = (props) => ( const renderCollapseTooltip = (props) => (
<Tooltip id="refresh-tooltip" {...props}> <Tooltip id="refresh-tooltip" {...props}>
Collapse Thu gọn
</Tooltip> </Tooltip>
); );
const columns = [ const columns = [
{ {
title: "Product", title: "Sản phẩm",
dataIndex: "product", dataIndex: "product",
render: (text, record) => ( render: (text, record) => (
<span className="productimgname"> <span className="productimgname">
@ -81,23 +81,23 @@ const ExpiredProduct = () => {
width: "5%" width: "5%"
}, },
{ {
title: "SKU", title: "Mã IMEI",
dataIndex: "sku", dataIndex: "sku",
sorter: (a, b) => a.sku.length - b.sku.length, sorter: (a, b) => a.sku.length - b.sku.length,
}, },
{ {
title: "Manufactured Date", title: "Ngày sản xuất",
dataIndex: "manufactureddate", dataIndex: "manufactureddate",
sorter: (a, b) => a.manufactureddate.length - b.manufactureddate.length, sorter: (a, b) => a.manufactureddate.length - b.manufactureddate.length,
}, },
{ {
title: "Expired Date", title: "Ngày hết hạn",
dataIndex: "expireddate", dataIndex: "expireddate",
sorter: (a, b) => a.expireddate.length - b.expireddate.length, sorter: (a, b) => a.expireddate.length - b.expireddate.length,
}, },
{ {
title: 'Actions', title: 'Thao tác',
dataIndex: 'actions', dataIndex: 'actions',
key: 'actions', key: 'actions',
render: () => ( render: () => (
@ -118,19 +118,19 @@ const ExpiredProduct = () => {
const showConfirmationAlert = () => { const showConfirmationAlert = () => {
MySwal.fire({ MySwal.fire({
title: 'Are you sure?', title: 'Bạn có chắc chắn?',
text: 'You won\'t be able to revert this!', text: 'Bạn sẽ không thể hoàn tác hành động này!',
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#00ff00', confirmButtonColor: '#00ff00',
confirmButtonText: 'Yes, delete it!', confirmButtonText: 'Có, xóa nó!',
cancelButtonColor: '#ff0000', cancelButtonColor: '#ff0000',
cancelButtonText: 'Cancel', cancelButtonText: 'Hủy',
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
MySwal.fire({ MySwal.fire({
title: 'Deleted!', title: 'Đã xóa!',
text: 'Your file has been deleted.', text: 'Tệp của bạn đã được xóa.',
className: "btn btn-success", className: "btn btn-success",
confirmButtonText: 'OK', confirmButtonText: 'OK',
customClass: { customClass: {
@ -150,8 +150,8 @@ const ExpiredProduct = () => {
<div className="page-header"> <div className="page-header">
<div className="add-item d-flex"> <div className="add-item d-flex">
<div className="page-title"> <div className="page-title">
<h4>Expired Products</h4> <h4>Sản phẩm hết hạn</h4>
<h6>Manage your expired products</h6> <h6>Quản các sản phẩm hết hạn của bạn</h6>
</div> </div>
</div> </div>
<ul className="table-top-head"> <ul className="table-top-head">
@ -209,7 +209,7 @@ const ExpiredProduct = () => {
<div className="search-input"> <div className="search-input">
<input <input
type="text" type="text"
placeholder="Search" placeholder="Tìm kiếm"
className="form-control form-control-sm formsearch" className="form-control form-control-sm formsearch"
/> />
<Link to className="btn btn-searchset"> <Link to className="btn btn-searchset">
@ -235,7 +235,7 @@ const ExpiredProduct = () => {
<Select <Select
className="select" className="select"
options={oldandlatestvalue} options={oldandlatestvalue}
placeholder="Newest" placeholder="Mới nhất"
/> />
</div> </div>
</div> </div>
@ -251,7 +251,7 @@ const ExpiredProduct = () => {
<div className="input-blocks"> <div className="input-blocks">
<Box className="info-img" /> <Box className="info-img" />
<Select options={brands} className="select" placeholder="Choose Type" /> <Select options={brands} className="select" placeholder="Chọn loại" />
</div> </div>
</div> </div>
@ -264,7 +264,7 @@ const ExpiredProduct = () => {
type="date" type="date"
className="filterdatepicker" className="filterdatepicker"
dateFormat="dd-MM-yyyy" dateFormat="dd-MM-yyyy"
placeholder='Choose Date' placeholder='Chọn ngày'
/> />
</div> </div>
</div> </div>
@ -274,7 +274,7 @@ const ExpiredProduct = () => {
<Link className="btn btn-filters ms-auto"> <Link className="btn btn-filters ms-auto">
{" "} {" "}
<i data-feather="search" className="feather-search" />{" "} <i data-feather="search" className="feather-search" />{" "}
Search{" "} Tìm kiếm{" "}
</Link> </Link>
</div> </div>
</div> </div>

View File

@ -0,0 +1,784 @@
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 {
fetchProducts,
fetchProduct,
deleteProduct,
searchProducts
} from "../../core/redux/actions/productActions";
import { setToogleHeader } from "../../core/redux/action";
import { Download } from "react-feather";
import CustomPagination from "../../components/CustomPagination";
const MySwal = withReactContent(Swal);
const route = all_routes;
// Add CSS to hide Ant Design pagination and checkboxes
function hideAntElements() {
const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = `
.ant-pagination {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
/* Hide all Ant Design checkboxes */
.ant-checkbox,
.ant-checkbox-input,
.ant-checkbox-wrapper,
.ant-table-selection-column,
.ant-table-selection,
.ant-table-thead > tr > th.ant-table-selection-column,
.ant-table-tbody > tr > td.ant-table-selection-column {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
`;
document.head.appendChild(styleSheet);
}
const ProductList2 = () => {
// Use new Redux structure for API data, fallback to legacy for existing functionality
const {
products: apiProducts,
loading,
error,
totalProducts,
pageSize: reduxPageSize,
currentPage: reduxCurrentPage
} = useSelector((state) => state.products);
// Fallback to legacy data if API data is not available
const legacyProducts = useSelector((state) => state.legacy?.product_list || []);
// Sample import data for demonstration
const sampleImportData = [
{
key: 1,
product: "MacBook Pro 13 inch",
sku: "MBP13-001",
importPrice: 25000000,
importQuantity: 5,
productImage: "assets/img/products/macbook.jpg",
supplier: "Apple Vietnam",
createdDate: "2024-01-15",
importDate: "2024-01-15"
},
{
key: 2,
product: "iPhone 15 Pro",
sku: "IP15P-001",
importPrice: 28000000,
importQuantity: 10,
productImage: "assets/img/products/iphone.jpg",
supplier: "Apple Vietnam",
createdDate: "2024-01-14",
importDate: "2024-01-14"
},
{
key: 3,
product: "Samsung Galaxy S24",
sku: "SGS24-001",
importPrice: 22000000,
importQuantity: 8,
productImage: "assets/img/products/samsung.jpg",
supplier: "Samsung Vietnam",
createdDate: "2024-01-13",
importDate: "2024-01-13"
}
];
const dataSource = apiProducts.length > 0 ? apiProducts : (legacyProducts.length > 0 ? legacyProducts : sampleImportData);
const dispatch = useDispatch();
const data = useSelector((state) => state.legacy?.toggle_header || false);
const [isFilterVisible, setIsFilterVisible] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
// State for pagination - sync with Redux
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
const [pageSize, setPageSize] = useState(reduxPageSize || 20);
// State for filter values
const [filterValues, setFilterValues] = useState({
product: '',
supplier: '',
importDate: '',
priceRange: '',
quantityRange: ''
});
// Calculate total records and pages
const totalRecords = totalProducts || dataSource.length;
const actualTotalPages = Math.ceil(totalRecords / pageSize);
useEffect(() => {
hideAntElements();
// Fetch products on component mount
if (apiProducts.length === 0) {
dispatch(fetchProducts({ page: currentPage, pageSize }));
}
}, [dispatch, apiProducts.length]);
// Sync local state with Redux state
useEffect(() => {
if (reduxCurrentPage && reduxCurrentPage !== currentPage) {
setCurrentPage(reduxCurrentPage);
}
if (reduxPageSize && reduxPageSize !== pageSize) {
setPageSize(reduxPageSize);
}
}, [reduxCurrentPage, reduxPageSize, currentPage, pageSize]);
const handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value);
if (value.trim()) {
dispatch(searchProducts(value));
} else {
dispatch(fetchProducts({ page: 1, pageSize }));
setCurrentPage(1);
}
};
const handlePageChange = (page) => {
setCurrentPage(page);
dispatch(fetchProducts({ page, pageSize, search: searchTerm }));
};
const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
setCurrentPage(1);
dispatch(fetchProducts({ page: 1, pageSize: newPageSize, search: searchTerm }));
};
const handleDelete = async (productId) => {
try {
const result = await MySwal.fire({
title: "Are you sure?",
text: "You won't be able to revert this!",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#dc3545",
cancelButtonColor: "#6c757d",
confirmButtonText: "Yes, delete it!",
cancelButtonText: "Cancel",
});
if (result.isConfirmed) {
await dispatch(deleteProduct(productId));
MySwal.fire({
title: "Deleted!",
text: "Product has been deleted successfully.",
icon: "success",
confirmButtonColor: "#28a745",
});
// Refresh the product list
dispatch(fetchProducts({ page: currentPage, pageSize, search: searchTerm }));
}
} catch (error) {
MySwal.fire({
title: "Error!",
text: "Failed to delete product. Please try again.",
icon: "error",
confirmButtonColor: "#dc3545",
});
}
};
const oldandlatestvalue = [
{ value: "date", label: "Sắp xếp theo ngày" },
{ value: "newest", label: "Mới nhất" },
{ value: "oldest", label: "Cũ nhất" },
];
const supplierOptions = [
{ value: "", label: "Chọn nhà cung cấp" },
{ value: "supplier1", label: "Nhà cung cấp A" },
{ value: "supplier2", label: "Nhà cung cấp B" },
];
const importDateOptions = [
{ value: "", label: "Chọn thời gian nhập" },
{ value: "today", label: "Hôm nay" },
{ value: "week", label: "Tuần này" },
{ value: "month", label: "Tháng này" },
];
const priceRangeOptions = [
{ value: "", label: "Chọn khoảng giá" },
{ value: "0-100000", label: "0 - 100,000 ₫" },
{ value: "100000-500000", label: "100,000 - 500,000 ₫" },
{ value: "500000-1000000", label: "500,000 - 1,000,000 ₫" },
];
const quantityRangeOptions = [
{ value: "", label: "Chọn số lượng" },
{ value: "1-10", label: "1 - 10" },
{ value: "11-50", label: "11 - 50" },
{ value: "51-100", label: "51 - 100" },
{ value: "100+", label: "Trên 100" },
];
const handleMenuToggle = () => {
dispatch(setToogleHeader(!data));
};
const columns = [
{
title: "Sản phẩm",
dataIndex: "product",
render: (text, record) => (
<span>{text || record.name || record.productName}</span>
),
sorter: (a, b) => a.product.length - b.product.length,
},
{
title: "Mã",
dataIndex: "sku",
render: (_, record) => {
const sku = record.sku || record.code || record.productCode || '-';
return <span>{sku}</span>;
},
sorter: (a, b) => {
const skuA = a.sku || a.code || a.productCode || '';
const skuB = b.sku || b.code || b.productCode || '';
return skuA.length - skuB.length;
},
},
{
title: "Giá nhập",
dataIndex: "importPrice",
render: (_, record) => {
const price = record.price || record.price || record.price || 0;
return (
<span
className="price-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: '#fff3cd',
color: '#856404',
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: '1px solid #ffeaa7',
minWidth: '80px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
>
{price.toLocaleString('vi-VN')}
</span>
);
},
sorter: (a, b) => {
const priceA = a.importPrice || a.purchasePrice || a.costPrice || 0;
const priceB = b.importPrice || b.purchasePrice || b.costPrice || 0;
return priceA - priceB;
},
},
{
title: "Số lượng nhập",
dataIndex: "importQuantity",
render: (_, record) => {
const quantity = record.qty || record.quantity || record.inboundQuantity || 0;
return (
<span
className="quantity-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: quantity > 0 ? '#e8f5e8' : '#f8f9fa',
color: quantity > 0 ? '#28a745' : '#6c757d',
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: `1px solid ${quantity > 0 ? '#c3e6cb' : '#dee2e6'}`,
minWidth: '80px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
>
{quantity.toLocaleString('vi-VN')}
</span>
);
},
sorter: (a, b) => {
const qtyA = a.importQuantity || a.receivedQuantity || a.inboundQuantity || 0;
const qtyB = b.importQuantity || b.receivedQuantity || b.inboundQuantity || 0;
return qtyA - qtyB;
},
},
{
title: "Ngày nhập",
dataIndex: "importDate",
render: (_, record) => {
const importDate = record.createdDate || record.importDate || record.receivedDate || '';
if (!importDate) return <span>-</span>;
// Format date to Vietnamese format
const date = new Date(importDate);
const formattedDate = date.toLocaleDateString('vi-VN', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
// Calculate days ago
const today = new Date();
const diffTime = Math.abs(today - date);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
let badgeColor = '#d1ecf1';
let textColor = '#0c5460';
let borderColor = '#bee5eb';
if (diffDays <= 1) {
badgeColor = '#d4edda';
textColor = '#155724';
borderColor = '#c3e6cb';
} else if (diffDays <= 7) {
badgeColor = '#fff3cd';
textColor = '#856404';
borderColor = '#ffeaa7';
} else if (diffDays > 30) {
badgeColor = '#f8d7da';
textColor = '#721c24';
borderColor = '#f5c6cb';
}
return (
<span
className="date-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: badgeColor,
color: textColor,
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: `1px solid ${borderColor}`,
minWidth: '90px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
title={`${diffDays} ngày trước`}
>
{formattedDate}
</span>
);
},
sorter: (a, b) => {
const dateA = new Date(a.createdDate || a.importDate || a.receivedDate || 0);
const dateB = new Date(b.createdDate || b.importDate || b.receivedDate || 0);
return dateA - dateB;
},
},
{
title: "Thao tác",
dataIndex: "action",
render: (_, record) => (
<td className="action-table-data">
<div className="edit-delete-action">
<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();
handleDelete(record.id || record.key);
}}
>
<Trash2 className="feather-delete" />
</Link>
</div>
</td>
),
},
];
return (
<div className="page-wrapper">
<div className="content">
<div className="page-header">
<div className="add-item d-flex">
<div className="page-title">
<h4>Nhập kho</h4>
<h6>Quản thông tin nhập kho sản phẩm</h6>
</div>
</div>
<ul className="table-top-head">
<li>
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-top">Pdf</Tooltip>}
>
<Link>
<ImageWithBasePath
src="assets/img/icons/pdf.svg"
alt="img"
/>
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-top">Excel</Tooltip>}
>
<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={<Tooltip id="tooltip-top">Print</Tooltip>}
>
<Link data-bs-toggle="tooltip" data-bs-placement="top">
<ImageWithBasePath
src="assets/img/icons/printer.svg"
alt="img"
/>
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-top">Refresh</Tooltip>}
>
<Link
data-bs-toggle="tooltip"
data-bs-placement="top"
onClick={() => dispatch(fetchProducts({ page: currentPage, pageSize }))}
>
<RotateCcw />
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-top">Collapse</Tooltip>}
>
<Link
data-bs-toggle="tooltip"
data-bs-placement="top"
id="collapse-header"
className={data ? "active" : ""}
onClick={handleMenuToggle}
>
<ChevronUp />
</Link>
</OverlayTrigger>
</li>
</ul>
<div className="page-btn">
<Link to="#" className="btn btn-added">
<PlusCircle className="me-2 iconsize" />
Tạo phiếu nhập
</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 Excel
</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="Tìm kiếm sản phẩm nhập kho..."
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"
onClick={() => setIsFilterVisible(!isFilterVisible)}
>
<Filter className="filter-icon" />
<span>
<ImageWithBasePath
src="assets/img/icons/closes.svg"
alt="img"
/>
</span>
</Link>
</div>
<div className="form-sort">
<Sliders className="info-img" />
<Select
className="img-select"
classNamePrefix="react-select"
options={oldandlatestvalue}
placeholder="Newest"
/>
</div>
</div>
{/* /Filter */}
<div
className={`card${isFilterVisible ? " " : " d-none"}`}
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">
<GitMerge className="info-img" />
<Select
className="img-select"
classNamePrefix="react-select"
options={[
{ value: "", label: "Chọn sản phẩm" },
{ value: "macbook-pro", label: "Macbook Pro" },
{ value: "orange", label: "Cam" },
]}
placeholder="Chọn sản phẩm"
value={[
{ value: "", label: "Chọn sản phẩm" },
{ value: "macbook-pro", label: "Macbook Pro" },
{ value: "orange", label: "Cam" },
].find(option => option.value === filterValues.product)}
onChange={(selectedOption) =>
setFilterValues(prev => ({ ...prev, product: selectedOption?.value || '' }))
}
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<StopCircle className="info-img" />
<Select
className="img-select"
classNamePrefix="react-select"
options={supplierOptions}
placeholder="Chọn nhà cung cấp"
value={supplierOptions.find(option => option.value === filterValues.supplier)}
onChange={(selectedOption) =>
setFilterValues(prev => ({ ...prev, supplier: selectedOption?.value || '' }))
}
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<StopCircle className="info-img" />
<Select
className="img-select"
classNamePrefix="react-select"
options={importDateOptions}
placeholder="Chọn thời gian nhập"
value={importDateOptions.find(option => option.value === filterValues.importDate)}
onChange={(selectedOption) =>
setFilterValues(prev => ({ ...prev, importDate: selectedOption?.value || '' }))
}
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<Box className="info-img" />
<Select
className="img-select"
classNamePrefix="react-select"
options={priceRangeOptions}
placeholder="Chọn khoảng giá"
value={priceRangeOptions.find(option => option.value === filterValues.priceRange)}
onChange={(selectedOption) =>
setFilterValues(prev => ({ ...prev, priceRange: selectedOption?.value || '' }))
}
/>
</div>
</div>
<div className="col-lg-2 col-sm-6 col-12">
<div className="input-blocks">
<StopCircle className="info-img" />
<Select
className="img-select"
classNamePrefix="react-select"
options={quantityRangeOptions}
placeholder="Chọn số lượng"
value={quantityRangeOptions.find(option => option.value === filterValues.quantityRange)}
onChange={(selectedOption) =>
setFilterValues(prev => ({ ...prev, quantityRange: selectedOption?.value || '' }))
}
/>
</div>
</div>
<div className="col-lg-1 col-sm-6 col-12 ms-auto">
<div className="input-blocks">
<Link className="btn btn-filters ms-auto">
<i
data-feather="search"
className="feather-search"
style={{ marginRight: '8px' }}
/>
Tìm kiếm
</Link>
</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">Đang tải dữ liệu nhập kho...</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}
pagination={false} // Disable Ant Design pagination
rowSelection={null} // Disable row selection checkboxes
/>
{/* Reusable Custom Pagination Component */}
<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>
{/* /product list */}
<Brand />
</div>
</div>
);
};
export default ProductList2;

View File

@ -0,0 +1,759 @@
import {
Box,
Edit,
Eye,
Filter,
GitMerge,
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 {
fetchProducts,
fetchProduct,
deleteProduct,
searchProducts
} from "../../core/redux/actions/productActions";
import CustomPagination from "../../components/CustomPagination";
const MySwal = withReactContent(Swal);
const route = all_routes;
// Add CSS to hide Ant Design pagination and checkboxes
function hideAntElements() {
const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = `
.ant-pagination {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
/* Hide all Ant Design checkboxes */
.ant-checkbox,
.ant-checkbox-input,
.ant-checkbox-wrapper,
.ant-table-selection-column,
.ant-table-selection,
.ant-table-thead > tr > th.ant-table-selection-column,
.ant-table-tbody > tr > td.ant-table-selection-column {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
`;
document.head.appendChild(styleSheet);
}
const ProductList3 = () => {
// Use new Redux structure for API data, fallback to legacy for existing functionality
const {
products: apiProducts,
loading,
error,
totalProducts,
pageSize: reduxPageSize,
currentPage: reduxCurrentPage
} = useSelector((state) => state.products);
// Fallback to legacy data if API data is not available
const legacyProducts = useSelector((state) => state.legacy?.product_list || []);
// Sample inventory data for demonstration
const sampleInventoryData = [
{
key: 1,
product: "MacBook Pro 13 inch",
sku: "MBP13-001",
totalImported: 50,
totalExported: 35,
currentStock: 15,
minStock: 5,
maxStock: 50,
unitPrice: 25000000,
totalValue: 375000000,
productImage: "assets/img/products/macbook.jpg",
createdDate: "2024-01-15",
lastUpdated: "2024-01-20"
},
{
key: 2,
product: "iPhone 15 Pro",
sku: "IP15P-001",
totalImported: 80,
totalExported: 55,
currentStock: 25,
minStock: 10,
maxStock: 100,
unitPrice: 28000000,
totalValue: 700000000,
productImage: "assets/img/products/iphone.jpg",
createdDate: "2024-01-14",
lastUpdated: "2024-01-19"
},
{
key: 3,
product: "Samsung Galaxy S24",
sku: "SGS24-001",
totalImported: 30,
totalExported: 22,
currentStock: 8,
minStock: 5,
maxStock: 30,
unitPrice: 22000000,
totalValue: 176000000,
productImage: "assets/img/products/samsung.jpg",
createdDate: "2024-01-13",
lastUpdated: "2024-01-18"
},
{
key: 4,
product: "Dell XPS 13",
sku: "DXP13-001",
totalImported: 40,
totalExported: 28,
currentStock: 12,
minStock: 8,
maxStock: 40,
unitPrice: 23000000,
totalValue: 276000000,
productImage: "assets/img/products/dell.jpg",
createdDate: "2024-01-12",
lastUpdated: "2024-01-17"
},
{
key: 5,
product: "iPad Pro 11 inch",
sku: "IPD11-001",
totalImported: 60,
totalExported: 45,
currentStock: 15,
minStock: 10,
maxStock: 60,
unitPrice: 20000000,
totalValue: 300000000,
productImage: "assets/img/products/ipad.jpg",
createdDate: "2024-01-11",
lastUpdated: "2024-01-16"
}
];
const dataSource = apiProducts.length > 0 ? apiProducts : (legacyProducts.length > 0 ? legacyProducts : sampleInventoryData);
const dispatch = useDispatch();
const [isFilterVisible, setIsFilterVisible] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
// State for pagination - sync with Redux
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
const [pageSize, setPageSize] = useState(reduxPageSize || 20);
// State for filter values
const [filterValues, setFilterValues] = useState({
product: '',
stockLevel: '',
quantityRange: '',
stockStatus: ''
});
// Calculate total records and pages
const totalRecords = totalProducts || dataSource.length;
const actualTotalPages = Math.ceil(totalRecords / pageSize);
useEffect(() => {
hideAntElements();
// Fetch products on component mount
if (apiProducts.length === 0) {
dispatch(fetchProducts({ page: currentPage, pageSize }));
}
}, [dispatch, apiProducts.length]);
// Sync local state with Redux state
useEffect(() => {
if (reduxCurrentPage && reduxCurrentPage !== currentPage) {
setCurrentPage(reduxCurrentPage);
}
if (reduxPageSize && reduxPageSize !== pageSize) {
setPageSize(reduxPageSize);
}
}, [reduxCurrentPage, reduxPageSize, currentPage, pageSize]);
const handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value);
if (value.trim()) {
dispatch(searchProducts(value));
} else {
dispatch(fetchProducts({ page: 1, pageSize }));
setCurrentPage(1);
}
};
const handlePageChange = (page) => {
setCurrentPage(page);
dispatch(fetchProducts({ page, pageSize, search: searchTerm }));
};
const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
setCurrentPage(1);
dispatch(fetchProducts({ page: 1, pageSize: newPageSize, search: searchTerm }));
};
const handleDelete = async (productId) => {
try {
const result = await MySwal.fire({
title: "Are you sure?",
text: "You won't be able to revert this!",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#dc3545",
cancelButtonColor: "#6c757d",
confirmButtonText: "Yes, delete it!",
cancelButtonText: "Cancel",
});
if (result.isConfirmed) {
await dispatch(deleteProduct(productId));
MySwal.fire({
title: "Deleted!",
text: "Product has been deleted successfully.",
icon: "success",
confirmButtonColor: "#28a745",
});
// Refresh the product list
dispatch(fetchProducts({ page: currentPage, pageSize, search: searchTerm }));
}
} catch (error) {
MySwal.fire({
title: "Error!",
text: "Failed to delete product. Please try again.",
icon: "error",
confirmButtonColor: "#dc3545",
});
}
};
const oldandlatestvalue = [
{ value: "date", label: "Sắp xếp theo ngày" },
{ value: "newest", label: "Mới nhất" },
{ value: "oldest", label: "Cũ nhất" },
];
const stockLevelOptions = [
{ value: "", label: "Chọn mức tồn kho" },
{ value: "low", label: "Tồn kho thấp" },
{ value: "normal", label: "Tồn kho bình thường" },
{ value: "high", label: "Tồn kho cao" },
];
const stockStatusOptions = [
{ value: "", label: "Chọn trạng thái" },
{ value: "in-stock", label: "Còn hàng" },
{ value: "low-stock", label: "Sắp hết hàng" },
{ value: "out-of-stock", label: "Hết hàng" },
];
const quantityRangeOptions = [
{ value: "", label: "Chọn khoảng số lượng" },
{ value: "0-10", label: "0 - 10" },
{ value: "11-50", label: "11 - 50" },
{ value: "51-100", label: "51 - 100" },
{ value: "100+", label: "Trên 100" },
];
// Handle filter value changes
const handleFilterChange = (filterType, value) => {
setFilterValues(prev => ({
...prev,
[filterType]: value
}));
};
const columns = [
{
title: "Sản phẩm",
dataIndex: "product",
render: (text, record) => (
<span>{text || record.name || record.productName}</span>
),
sorter: (a, b) => a.product.length - b.product.length,
},
{
title: "Mã",
dataIndex: "sku",
render: (_, record) => {
const sku = record.sku || record.code || record.productCode || '-';
return <span>{sku}</span>;
},
sorter: (a, b) => {
const skuA = a.sku || a.code || a.productCode || '';
const skuB = b.sku || b.code || b.productCode || '';
return skuA.length - skuB.length;
},
},
{
title: "Tổng nhập kho",
dataIndex: "totalImported",
render: (_, record) => {
const totalImported = record.totalImported || 0;
return (
<span
className="import-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: '#e8f5e8',
color: '#28a745',
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: '1px solid #c3e6cb',
minWidth: '80px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
>
{totalImported.toLocaleString('vi-VN')}
</span>
);
},
sorter: (a, b) => {
const importedA = a.totalImported || 0;
const importedB = b.totalImported || 0;
return importedA - importedB;
},
},
{
title: "Tổng xuất kho",
dataIndex: "totalExported",
render: (_, record) => {
const totalExported = record.totalExported || 0;
return (
<span
className="export-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: '#fff3cd',
color: '#856404',
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: '1px solid #ffeaa7',
minWidth: '80px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
>
{totalExported.toLocaleString('vi-VN')}
</span>
);
},
sorter: (a, b) => {
const exportedA = a.totalExported || 0;
const exportedB = b.totalExported || 0;
return exportedA - exportedB;
},
},
{
title: "Tồn kho hiện tại",
dataIndex: "currentStock",
render: (_, record) => {
const stock = record.currentStock || record.qty || record.quantity || 0;
const minStock = record.minStock || 5;
let badgeColor = '#e3f2fd';
let textColor = '#1565c0';
let borderColor = '#bbdefb';
if (stock <= 0) {
badgeColor = '#f8d7da';
textColor = '#721c24';
borderColor = '#f5c6cb';
} else if (stock <= minStock) {
badgeColor = '#fff3cd';
textColor = '#856404';
borderColor = '#ffeaa7';
}
return (
<span
className="stock-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: badgeColor,
color: textColor,
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: `1px solid ${borderColor}`,
minWidth: '80px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
>
{stock.toLocaleString('vi-VN')}
</span>
);
},
sorter: (a, b) => {
const stockA = a.currentStock || a.qty || a.quantity || 0;
const stockB = b.currentStock || b.qty || b.quantity || 0;
return stockA - stockB;
},
},
{
title: "Giá trị tồn kho",
dataIndex: "totalValue",
render: (_, record) => {
const stock = record.currentStock || record.qty || record.quantity || 0;
const price = record.unitPrice || record.price || record.sellPrice || 0;
const totalValue = stock * price;
return (
<span
className="value-badge"
style={{
display: 'inline-block',
padding: '6px 12px',
backgroundColor: '#f3e5f5',
color: '#7b1fa2',
borderRadius: '20px',
fontSize: '13px',
fontWeight: '600',
border: '1px solid #e1bee7',
minWidth: '120px',
textAlign: 'center',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
}}
>
{totalValue.toLocaleString('vi-VN')}
</span>
);
},
sorter: (a, b) => {
const valueA = (a.currentStock || 0) * (a.unitPrice || 0);
const valueB = (b.currentStock || 0) * (b.unitPrice || 0);
return valueA - valueB;
},
},
{
title: "Thao tác",
dataIndex: "action",
render: (_, record) => (
<td className="action-table-data">
<div className="edit-delete-action">
<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();
handleDelete(record.id || record.key);
}}
>
<Trash2 className="feather-delete" />
</Link>
</div>
</td>
),
},
];
return (
<div className="page-wrapper">
<div className="content">
<div className="page-header">
<div className="add-item d-flex">
<div className="page-title">
<h4>Tồn kho</h4>
<h6>Quản thông tin tồn kho sản phẩm</h6>
</div>
</div>
<ul className="table-top-head">
<li>
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-top">Pdf</Tooltip>}
>
<Link>
<ImageWithBasePath
src="assets/img/icons/pdf.svg"
alt="img"
/>
</Link>
</OverlayTrigger>
</li>
<li>
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-top">Excel</Tooltip>}
>
<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={<Tooltip id="tooltip-top">Print</Tooltip>}
>
<Link data-bs-toggle="tooltip" data-bs-placement="top">
<ImageWithBasePath
src="assets/img/icons/printer.svg"
alt="img"
/>
</Link>
</OverlayTrigger>
</li>
</ul>
</div>
{/* /product list */}
<div className="card mb-0" id="filter_inputs">
<div className="card-body pb-0">
<div className="row">
<div className="col-lg-12 col-sm-12">
<div className="row">
<div className="col-lg col-sm-6 col-12">
<div className="input-blocks">
<StopCircle className="info-img" />
<Select
className="img-select"
options={oldandlatestvalue}
placeholder="Sắp xếp theo ngày"
/>
</div>
</div>
<div className="col-lg col-sm-6 col-12">
<div className="input-blocks">
<Box className="info-img" />
<Select
className="img-select"
options={stockLevelOptions}
placeholder="Chọn mức tồn kho"
value={stockLevelOptions.find(option => option.value === filterValues.stockLevel)}
onChange={(selectedOption) => handleFilterChange('stockLevel', selectedOption?.value || '')}
/>
</div>
</div>
<div className="col-lg col-sm-6 col-12">
<div className="input-blocks">
<Sliders className="info-img" />
<Select
className="img-select"
options={quantityRangeOptions}
placeholder="Chọn khoảng số lượng"
value={quantityRangeOptions.find(option => option.value === filterValues.quantityRange)}
onChange={(selectedOption) => handleFilterChange('quantityRange', selectedOption?.value || '')}
/>
</div>
</div>
<div className="col-lg col-sm-6 col-12">
<div className="input-blocks">
<Filter className="info-img" />
<Select
className="img-select"
options={stockStatusOptions}
placeholder="Chọn trạng thái"
value={stockStatusOptions.find(option => option.value === filterValues.stockStatus)}
onChange={(selectedOption) => handleFilterChange('stockStatus', selectedOption?.value || '')}
/>
</div>
</div>
<div className="col-lg-1 col-sm-6 col-12">
<div className="input-blocks">
<Link className="btn btn-filters ms-auto">
<i data-feather="search" className="feather-search" />
Tìm kiếm
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* /Filter */}
<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="Tìm kiếm sản phẩm tồn kho..."
className="form-control form-control-sm formsearch"
value={searchTerm}
onChange={handleSearch}
/>
<Link 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"
onClick={() => setIsFilterVisible(!isFilterVisible)}
>
<Filter className="filter-icon" />
<span>
<ImageWithBasePath
src="assets/img/icons/closes.svg"
alt="img"
/>
</span>
</Link>
</div>
<div className="form-sort">
<Sliders className="info-img" />
<Select
className="img-select"
options={oldandlatestvalue}
placeholder="Sắp xếp theo ngày"
/>
</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">Đang tải dữ liệu tồn kho...</p>
</div>
) : error ? (
<div className="text-center p-4">
<div className="alert alert-danger">
<p>Lỗi: {error}</p>
</div>
<button
className="btn btn-primary"
onClick={() => dispatch(fetchProducts({ page: currentPage, pageSize }))}
>
Retry
</button>
</div>
) : (
<>
<Table
columns={columns}
dataSource={dataSource}
pagination={false} // Disable Ant Design pagination
rowSelection={null} // Disable row selection checkboxes
/>
{/* Custom Pagination */}
<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="inventory-pagination"
/>
</>
)}
</div>
</div>
</div>
{/* /product list */}
<Brand />
</div>
</div>
);
};
export default ProductList3;