Compare commits
3 Commits
68355e31a8
...
86a4a19209
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86a4a19209 | ||
|
|
0656cda2ec | ||
|
|
a40a1994e5 |
3
.env
@ -1,3 +1,4 @@
|
||||
REACT_APP_API_BASE_URL=https://trantran.zenstores.com.vn/api/
|
||||
# REACT_APP_API_BASE_URL=https://trantran.zenstores.com.vn/api/
|
||||
REACT_APP_API_BASE_URL=http://localhost:4000/api/v1
|
||||
# CORS Proxy for development (uncomment if needed)
|
||||
# REACT_APP_API_BASE_URL=https://cors-anywhere.herokuapp.com/https://trantran.zenstores.com.vn/api/
|
||||
|
||||
25
package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "my-app",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ckeditor/ckeditor5-build-classic": "^41.2.0",
|
||||
"@ckeditor/ckeditor5-react": "^6.2.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
@ -142,13 +143,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ant-design/icons": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.3.tgz",
|
||||
"integrity": "sha512-Zfci1s4f4+vfpVD6ksmmPuBv00SB/slpUAQlsBlMeRJdSleVVkgTUdlBM4j/vGzqYfMh2hF8/Poa1VSh542w0Q==",
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
|
||||
"integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.0.0",
|
||||
"@ant-design/icons-svg": "^4.4.0",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@babel/runtime": "^7.24.8",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-util": "^5.31.1"
|
||||
},
|
||||
@ -2042,12 +2044,10 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz",
|
||||
"integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
|
||||
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -21112,11 +21112,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"homepage": "/",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ckeditor/ckeditor5-build-classic": "^41.2.0",
|
||||
"@ckeditor/ckeditor5-react": "^6.2.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
|
||||
BIN
public/assets/img/_logo.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/assets/img/authentication/_login-img.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 31 KiB |
26
public/assets/img/logo.svg
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 31 KiB |
@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<link rel="icon" href="./favicon.png" />
|
||||
<title>Dreams Pos admin template</title>
|
||||
<title>APSKEL</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -1,16 +1,25 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import FeatherIcon from "feather-icons-react";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import { Search, XCircle } from "react-feather";
|
||||
import { all_routes } from "../../Router/all_routes";
|
||||
import { useSelector } from "react-redux";
|
||||
import authApi from "../../services/authApi";
|
||||
|
||||
const Header = () => {
|
||||
const route = all_routes;
|
||||
const navigate = useNavigate();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const [toggle, SetToggle] = useState(false);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
const handleLogout = () => {
|
||||
authApi.logout()
|
||||
navigate(route.signin);
|
||||
};
|
||||
|
||||
const isElementVisible = (element) => {
|
||||
return element.offsetWidth > 0 || element.offsetHeight > 0;
|
||||
};
|
||||
@ -616,8 +625,8 @@ const Header = () => {
|
||||
/>
|
||||
</span>
|
||||
<span className="user-detail">
|
||||
<span className="user-name">John Smilga</span>
|
||||
<span className="user-role">Super Admin</span>
|
||||
<span className="user-name">{authState.user?.name || 'User'}</span>
|
||||
<span className="user-role">{authState.user?.role || 'Admin'}</span>
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
@ -632,12 +641,12 @@ const Header = () => {
|
||||
<span className="status online" />
|
||||
</span>
|
||||
<div className="profilesets">
|
||||
<h6>John Smilga</h6>
|
||||
<h5>Super Admin</h5>
|
||||
<h6>{authState.user?.name || 'User'}</h6>
|
||||
<h5>{authState.user?.role || 'Admin'}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="m-0" />
|
||||
<Link className="dropdown-item" to={route.route}>
|
||||
<Link className="dropdown-item" to={route.profile}>
|
||||
<i className="me-2" data-feather="user" /> My Profile
|
||||
</Link>
|
||||
<Link className="dropdown-item" to={route.generalsettings}>
|
||||
@ -645,14 +654,18 @@ const Header = () => {
|
||||
Settings
|
||||
</Link>
|
||||
<hr className="m-0" />
|
||||
<Link className="dropdown-item logout pb-0" to="/signin">
|
||||
<button
|
||||
className="dropdown-item logout pb-0"
|
||||
onClick={handleLogout}
|
||||
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left' }}
|
||||
>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/log-out.svg"
|
||||
alt="img"
|
||||
className="me-2"
|
||||
/>
|
||||
Logout
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@ -675,9 +688,13 @@ const Header = () => {
|
||||
<Link className="dropdown-item" to="generalsettings">
|
||||
Settings
|
||||
</Link>
|
||||
<Link className="dropdown-item" to="signin">
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={handleLogout}
|
||||
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left' }}
|
||||
>
|
||||
Logout
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Mobile Menu */}
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import Scrollbars from "react-custom-scrollbars-2";
|
||||
// import { useSelector } from "react-redux";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { SidebarData } from "../../core/json/siderbar_data";
|
||||
import HorizontalSidebar from "./horizontalSidebar";
|
||||
import CollapsedSidebar from "./collapsedSidebar";
|
||||
import HorizontalSidebar from "./horizontalSidebar";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const Sidebar = () => {
|
||||
// const SidebarData = useSelector((state) => state.sidebar_data);
|
||||
// console.log(sidebarData, "sidebar");
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
const Location = useLocation();
|
||||
|
||||
console.log("Location.pathname", Location.pathname);
|
||||
|
||||
const [subOpen, setSubopen] = useState("");
|
||||
const [subsidebar, setSubsidebar] = useState("");
|
||||
|
||||
@ -46,6 +43,12 @@ const Sidebar = () => {
|
||||
|
||||
<ul>
|
||||
{mainLabel?.submenuItems?.map((title, i) => {
|
||||
if (title?.role) {
|
||||
if (user?.role !== title?.role) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let link_array = [];
|
||||
title?.submenuItems?.map((link) => {
|
||||
link_array?.push(link?.link);
|
||||
|
||||
@ -54,7 +54,7 @@ export const all_routes = {
|
||||
floatinglabel: "/form-floating-labels",
|
||||
formvalidation: "/form-validation",
|
||||
select2: "/form-select2",
|
||||
|
||||
companylist: "/company-list",
|
||||
toasts: "/ui-toasts",
|
||||
video: "/ui-video",
|
||||
sweetalerts: "/ui-sweetalerts",
|
||||
@ -81,7 +81,7 @@ export const all_routes = {
|
||||
typicons: "/icon-typicon",
|
||||
flagicons: "/icon-flag",
|
||||
ribbon: "/ui-ribbon",
|
||||
|
||||
paymentmethodlist: "/payment-method-list",
|
||||
chat: "/chat",
|
||||
videocall: "/video-call",
|
||||
audiocall: "/audio-call",
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Outlet, Route, Routes } from "react-router-dom";
|
||||
import Header from "../InitialPage/Sidebar/Header";
|
||||
import Sidebar from "../InitialPage/Sidebar/Sidebar";
|
||||
import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import ThemeSettings from "../InitialPage/themeSettings";
|
||||
import ProtectedRoute from "../components/ProtectedRoute";
|
||||
import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
||||
// import CollapsedSidebar from "../InitialPage/Sidebar/collapsedSidebar";
|
||||
import GuestRoute from "../components/GuestRoute";
|
||||
import Loader from "../feature-module/loader/loader";
|
||||
// import HorizontalSidebar from "../InitialPage/Sidebar/horizontalSidebar";
|
||||
//import LoadingSpinner from "../InitialPage/Sidebar/LoadingSpinner";
|
||||
|
||||
const AllRoutes = () => {
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
// const layoutStyles = useSelector((state) => state.layoutstyledata);
|
||||
const HeaderLayout = () => (
|
||||
@ -48,8 +49,6 @@ const AllRoutes = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
console.log(publicRoutes, "dashboard");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Routes>
|
||||
@ -58,13 +57,29 @@ const AllRoutes = () => {
|
||||
<Route path={route.path} element={route.element} key={id} />
|
||||
))}
|
||||
</Route>
|
||||
<Route path={"/"} element={<HeaderLayout />}>
|
||||
{publicRoutes.map((route, id) => (
|
||||
<Route path={route.path} element={route.element} key={id} />
|
||||
))}
|
||||
|
||||
<Route
|
||||
path={"/"}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<HeaderLayout />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
{publicRoutes.map((route, id) => {
|
||||
if (route?.role && route?.role !== user?.role) return null;
|
||||
return <Route path={route.path} element={route.element} key={id} />;
|
||||
})}
|
||||
</Route>
|
||||
|
||||
<Route path={"/"} element={<Authpages />}>
|
||||
<Route
|
||||
path={"/"}
|
||||
element={
|
||||
<GuestRoute>
|
||||
<Authpages />
|
||||
</GuestRoute>
|
||||
}
|
||||
>
|
||||
{pagesRoute.map((route, id) => (
|
||||
<Route path={route.path} element={route.element} key={id} />
|
||||
))}
|
||||
|
||||
@ -204,6 +204,9 @@ import AddWeddingGuest from "../feature-module/inventory/addWeddingGuest";
|
||||
import ProductList2 from "../feature-module/inventory/productlist2";
|
||||
import ProductList3 from "../feature-module/inventory/productlist3";
|
||||
import { all_routes } from "./all_routes";
|
||||
import PaymentMethodList from "../feature-module/FinanceAccounts/paymentmethodlist";
|
||||
import CompanyList from "../feature-module/superadmin/companylist";
|
||||
|
||||
export const publicRoutes = [
|
||||
{
|
||||
id: 1,
|
||||
@ -1404,6 +1407,13 @@ export const publicRoutes = [
|
||||
element: <ProductDetail />,
|
||||
route: Route,
|
||||
},
|
||||
{
|
||||
id: 113.1,
|
||||
path: `${routes.productdetails}/:id`,
|
||||
name: "productdetails",
|
||||
element: <ProductDetail />,
|
||||
route: Route,
|
||||
},
|
||||
{
|
||||
id: 114,
|
||||
path: routes.warehouses,
|
||||
@ -1481,7 +1491,23 @@ export const publicRoutes = [
|
||||
element: <Navigate to="/" />,
|
||||
route: Route,
|
||||
},
|
||||
{
|
||||
id: 120,
|
||||
path: routes.paymentmethodlist,
|
||||
name: "paymendMethodList",
|
||||
element: <PaymentMethodList /> ,
|
||||
route: Route,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
path: routes.companylist,
|
||||
name: "companies",
|
||||
element: <CompanyList />,
|
||||
route: Route,
|
||||
role: 'superadmin'
|
||||
}
|
||||
];
|
||||
|
||||
export const posRoutes = [
|
||||
{
|
||||
id: 1,
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
DoubleLeftOutlined,
|
||||
DoubleRightOutlined,
|
||||
LeftOutlined,
|
||||
RightOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const CustomPagination = ({
|
||||
currentPage = 1,
|
||||
@ -13,28 +19,32 @@ const CustomPagination = ({
|
||||
showInfo = true,
|
||||
showPageSizeSelector = true,
|
||||
compact = false,
|
||||
className = ''
|
||||
className = "",
|
||||
}) => {
|
||||
// Theme state for force re-render
|
||||
const [themeKey, setThemeKey] = useState(0);
|
||||
|
||||
|
||||
// Get theme from Redux and localStorage fallback
|
||||
const reduxTheme = useSelector((state) => state.theme?.isDarkMode);
|
||||
const localStorageTheme = localStorage.getItem('colorschema') === 'dark_mode';
|
||||
const documentTheme = document.documentElement.getAttribute('data-layout-mode') === 'dark_mode';
|
||||
|
||||
const localStorageTheme = localStorage.getItem("colorschema") === "dark_mode";
|
||||
const documentTheme =
|
||||
document.documentElement.getAttribute("data-layout-mode") === "dark_mode";
|
||||
|
||||
const isDarkMode = reduxTheme || localStorageTheme || documentTheme;
|
||||
|
||||
// Listen for theme changes
|
||||
useEffect(() => {
|
||||
const handleThemeChange = () => {
|
||||
setThemeKey(prev => prev + 1);
|
||||
setThemeKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
// Listen for data-layout-mode attribute changes
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'data-layout-mode') {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "data-layout-mode"
|
||||
) {
|
||||
handleThemeChange();
|
||||
}
|
||||
});
|
||||
@ -42,27 +52,25 @@ const CustomPagination = ({
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-layout-mode']
|
||||
attributeFilter: ["data-layout-mode"],
|
||||
});
|
||||
|
||||
// Also listen for localStorage changes
|
||||
const handleStorageChange = (e) => {
|
||||
if (e.key === 'colorschema') {
|
||||
if (e.key === "colorschema") {
|
||||
handleThemeChange();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
|
||||
window.addEventListener("storage", handleStorageChange);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
window.removeEventListener("storage", handleStorageChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Calculate pagination info
|
||||
const startRecord = totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
||||
const endRecord = Math.min(currentPage * pageSize, totalCount);
|
||||
console.log(totalCount);
|
||||
|
||||
// Handle page change
|
||||
const handlePageClick = (page) => {
|
||||
@ -80,190 +88,191 @@ const CustomPagination = ({
|
||||
|
||||
// Container styles based on compact mode
|
||||
const containerStyles = {
|
||||
background: isDarkMode
|
||||
? 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)'
|
||||
: 'linear-gradient(135deg, #ffffff, #f8f9fa)',
|
||||
border: isDarkMode
|
||||
? '1px solid rgba(52, 152, 219, 0.3)'
|
||||
: '1px solid rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: compact ? '8px' : '12px',
|
||||
boxShadow: isDarkMode
|
||||
? (compact ? '0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(52, 152, 219, 0.1)' : '0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)')
|
||||
: (compact ? '0 1px 6px rgba(0, 0, 0, 0.06)' : '0 2px 12px rgba(0, 0, 0, 0.08)'),
|
||||
backdropFilter: isDarkMode ? (compact ? 'blur(8px)' : 'blur(10px)') : 'none',
|
||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: compact ? '8px 16px' : '16px 24px',
|
||||
margin: compact ? '8px 0' : '16px 0',
|
||||
fontSize: compact ? '13px' : '14px'
|
||||
background: isDarkMode
|
||||
? "linear-gradient(135deg, #2c3e50 0%, #34495e 100%)"
|
||||
: "linear-gradient(135deg, #ffffff, #f8f9fa)",
|
||||
border: isDarkMode
|
||||
? "1px solid rgba(52, 152, 219, 0.3)"
|
||||
: "1px solid rgba(0, 0, 0, 0.1)",
|
||||
borderRadius: compact ? "8px" : "12px",
|
||||
boxShadow: isDarkMode
|
||||
? compact
|
||||
? "0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(52, 152, 219, 0.1)"
|
||||
: "0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)"
|
||||
: compact
|
||||
? "0 1px 6px rgba(0, 0, 0, 0.06)"
|
||||
: "0 2px 12px rgba(0, 0, 0, 0.08)",
|
||||
backdropFilter: isDarkMode
|
||||
? compact
|
||||
? "blur(8px)"
|
||||
: "blur(10px)"
|
||||
: "none",
|
||||
transition: compact ? "all 0.2s ease" : "all 0.3s ease",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
padding: compact ? "8px 16px" : "16px 24px",
|
||||
margin: compact ? "8px 0" : "16px 0",
|
||||
fontSize: compact ? "13px" : "14px",
|
||||
};
|
||||
|
||||
// Button styles
|
||||
const getButtonStyles = (isActive) => ({
|
||||
background: loading
|
||||
? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
||||
: isActive
|
||||
? (isDarkMode ? 'linear-gradient(45deg, #f39c12, #e67e22)' : 'linear-gradient(135deg, #007bff, #0056b3)')
|
||||
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
||||
border: isActive
|
||||
? (isDarkMode ? (compact ? '1px solid #f39c12' : '2px solid #f39c12') : (compact ? '1px solid #007bff' : '2px solid #007bff'))
|
||||
: (isDarkMode ? '1px solid rgba(52, 152, 219, 0.3)' : '1px solid #dee2e6'),
|
||||
borderRadius: '50%',
|
||||
width: compact ? '24px' : '32px',
|
||||
height: compact ? '24px' : '32px',
|
||||
color: isActive
|
||||
? '#ffffff'
|
||||
: (isDarkMode ? '#ffffff' : '#495057'),
|
||||
fontSize: compact ? '11px' : '14px',
|
||||
fontWeight: compact ? '600' : '700',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease',
|
||||
boxShadow: loading
|
||||
? 'none'
|
||||
: isActive
|
||||
? (isDarkMode ? (compact ? '0 2px 6px rgba(243, 156, 18, 0.3)' : '0 4px 12px rgba(243, 156, 18, 0.4)') : (compact ? '0 2px 4px rgba(0, 123, 255, 0.2)' : '0 3px 8px rgba(0, 123, 255, 0.3)'))
|
||||
: (isDarkMode ? (compact ? '0 1px 4px rgba(52, 73, 94, 0.2)' : '0 2px 8px rgba(52, 73, 94, 0.3)') : (compact ? '0 1px 2px rgba(0, 0, 0, 0.08)' : '0 1px 3px rgba(0, 0, 0, 0.1)')),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
opacity: loading ? 0.6 : 1
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`pagination-${themeKey}`}
|
||||
className={`custom-pagination-container ${isDarkMode ? '' : 'light-mode'} ${className}`}
|
||||
className={`custom-pagination-container ${
|
||||
isDarkMode ? "" : "light-mode"
|
||||
} ${className}`}
|
||||
style={containerStyles}
|
||||
>
|
||||
{/* Pagination Info */}
|
||||
{showInfo && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: compact ? '8px' : '16px',
|
||||
flexWrap: 'wrap',
|
||||
gap: compact ? '8px' : '12px'
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: compact ? "8px" : "0px",
|
||||
flexWrap: "wrap",
|
||||
gap: compact ? "8px" : "12px",
|
||||
}}
|
||||
>
|
||||
{showPageSizeSelector && (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>Số hàng mỗi trang</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: compact ? "6px" : "12px",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: isDarkMode ? "#bdc3c7" : "#2c3e50",
|
||||
fontSize: compact ? "12px" : "14px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
Number of rows per page
|
||||
</span>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={(e) => handlePageSizeClick(parseInt(e.target.value))}
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: loading
|
||||
? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
||||
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
||||
border: isDarkMode
|
||||
? '1px solid rgba(52, 152, 219, 0.3)'
|
||||
: '1px solid #dee2e6',
|
||||
borderRadius: compact ? '4px' : '6px',
|
||||
color: isDarkMode ? '#ffffff' : '#495057',
|
||||
padding: compact ? '2px 6px' : '4px 8px',
|
||||
fontSize: compact ? '12px' : '14px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
? isDarkMode
|
||||
? "linear-gradient(45deg, #7f8c8d, #95a5a6)"
|
||||
: "linear-gradient(135deg, #f8f9fa, #e9ecef)"
|
||||
: isDarkMode
|
||||
? "linear-gradient(45deg, #34495e, #2c3e50)"
|
||||
: "linear-gradient(135deg, #ffffff, #f8f9fa)",
|
||||
border: isDarkMode
|
||||
? "1px solid rgba(52, 152, 219, 0.3)"
|
||||
: "1px solid #dee2e6",
|
||||
borderRadius: compact ? "4px" : "6px",
|
||||
color: isDarkMode ? "#ffffff" : "#495057",
|
||||
padding: compact ? "2px 6px" : "4px 8px",
|
||||
fontSize: compact ? "12px" : "14px",
|
||||
cursor: loading ? "not-allowed" : "pointer",
|
||||
opacity: loading ? 0.7 : 1,
|
||||
boxShadow: isDarkMode ? 'none' : (compact ? '0 1px 2px rgba(0, 0, 0, 0.05)' : '0 1px 3px rgba(0, 0, 0, 0.1)')
|
||||
boxShadow: isDarkMode
|
||||
? "none"
|
||||
: compact
|
||||
? "0 1px 2px rgba(0, 0, 0, 0.05)"
|
||||
: "0 1px 3px rgba(0, 0, 0, 0.1)",
|
||||
}}
|
||||
>
|
||||
{pageSizeOptions.map(option => (
|
||||
<option
|
||||
key={option}
|
||||
value={option}
|
||||
style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}
|
||||
{pageSizeOptions.map((option) => (
|
||||
<option
|
||||
key={option}
|
||||
value={option}
|
||||
style={{
|
||||
background: isDarkMode ? "#2c3e50" : "#ffffff",
|
||||
color: isDarkMode ? "#ffffff" : "#495057",
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>bản ghi</span>
|
||||
<span
|
||||
style={{
|
||||
color: isDarkMode ? "#bdc3c7" : "#2c3e50",
|
||||
fontSize: compact ? "12px" : "14px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
records
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
||||
<div
|
||||
style={{
|
||||
background: isDarkMode
|
||||
? 'linear-gradient(45deg, #3498db, #2ecc71)'
|
||||
: 'linear-gradient(45deg, #007bff, #28a745)',
|
||||
borderRadius: '50%',
|
||||
width: compact ? '16px' : '24px',
|
||||
height: compact ? '16px' : '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: compact ? '8px' : '12px',
|
||||
boxShadow: isDarkMode
|
||||
? (compact ? '0 1px 4px rgba(52, 152, 219, 0.3)' : '0 2px 8px rgba(52, 152, 219, 0.3)')
|
||||
: (compact ? '0 1px 4px rgba(0, 123, 255, 0.2)' : '0 2px 8px rgba(0, 123, 255, 0.2)'),
|
||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
📊
|
||||
</div>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>
|
||||
Xem <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{startRecord}</strong> đến <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{endRecord}</strong> của <strong style={{color: isDarkMode ? '#e74c3c' : '#dc3545'}}>{totalCount}</strong> bản
|
||||
</span>
|
||||
{/* Pagination Buttons */}
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: compact ? "4px" : "8px",
|
||||
}}
|
||||
>
|
||||
<nav aria-label="Custom pagination">
|
||||
<ul className="pagination justify-content-center custom-pagination">
|
||||
<li className="page-item">
|
||||
<a
|
||||
onClick={() => handlePageClick(1)}
|
||||
className="page-link"
|
||||
aria-label="First"
|
||||
>
|
||||
<DoubleLeftOutlined
|
||||
style={{ fontSize: "12px", marginRight: "-10px" }}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (currentPage > 1) handlePageClick(currentPage - 1);
|
||||
}}
|
||||
className={`page-item ${currentPage === 1 ? "disabled" : ""}`}
|
||||
>
|
||||
<a className="page-link">
|
||||
<LeftOutlined style={{ fontSize: "12px" }} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="page-item active bg-primary text-secondary rounded-5">
|
||||
<span className="page-link">{currentPage}</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (currentPage < totalPages)
|
||||
handlePageClick(currentPage + 1);
|
||||
}}
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<a className="page-link" aria-label="Next">
|
||||
<RightOutlined style={{ fontSize: "12px" }} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="page-item">
|
||||
<a
|
||||
onClick={() => handlePageClick(totalPages)}
|
||||
className="page-link"
|
||||
aria-label="Last"
|
||||
>
|
||||
<DoubleRightOutlined
|
||||
style={{ fontSize: "12px", marginLeft: "-10px" }}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination Buttons */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: compact ? '4px' : '8px'
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: totalPages }, (_, i) => {
|
||||
const pageNum = i + 1;
|
||||
const isActive = currentPage === pageNum;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => handlePageClick(pageNum)}
|
||||
disabled={loading}
|
||||
style={getButtonStyles(isActive)}
|
||||
onMouseEnter={(e) => {
|
||||
if (!loading && !isActive) {
|
||||
e.target.style.background = isDarkMode
|
||||
? 'linear-gradient(45deg, #3498db, #2980b9)'
|
||||
: 'linear-gradient(135deg, #e9ecef, #f8f9fa)';
|
||||
e.target.style.transform = isDarkMode ? (compact ? 'scale(1.05)' : 'scale(1.1)') : (compact ? 'translateY(-1px) scale(1.02)' : 'translateY(-1px) scale(1.05)');
|
||||
e.target.style.boxShadow = isDarkMode
|
||||
? (compact ? '0 2px 6px rgba(52, 152, 219, 0.3)' : '0 4px 12px rgba(52, 152, 219, 0.4)')
|
||||
: (compact ? '0 2px 4px rgba(0, 0, 0, 0.12)' : '0 3px 8px rgba(0, 0, 0, 0.15)');
|
||||
e.target.style.borderColor = isDarkMode ? '#3498db' : '#adb5bd';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!loading && !isActive) {
|
||||
e.target.style.background = isDarkMode
|
||||
? 'linear-gradient(45deg, #34495e, #2c3e50)'
|
||||
: 'linear-gradient(135deg, #ffffff, #f8f9fa)';
|
||||
e.target.style.transform = 'scale(1)';
|
||||
e.target.style.boxShadow = isDarkMode
|
||||
? (compact ? '0 1px 4px rgba(52, 73, 94, 0.2)' : '0 2px 8px rgba(52, 73, 94, 0.3)')
|
||||
: (compact ? '0 1px 2px rgba(0, 0, 0, 0.08)' : '0 1px 3px rgba(0, 0, 0, 0.1)');
|
||||
e.target.style.borderColor = isDarkMode ? 'rgba(52, 152, 219, 0.3)' : '#dee2e6';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
15
src/components/GuestRoute.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const GuestRoute = ({ children }) => {
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const isAuthenticated = authState?.isAuthenticated || authState?.token;
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default GuestRoute;
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
// Color variants
|
||||
.primary {
|
||||
--loader-color: #ff9f43;
|
||||
--loader-color: #36175e;
|
||||
--loader-secondary: rgba(255, 159, 67, 0.3);
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
// Color variants
|
||||
&.primary {
|
||||
background: #ff9f43;
|
||||
background: #36175e;
|
||||
color: white;
|
||||
|
||||
&:hover:not(.loading):not(.disabled) {
|
||||
@ -83,11 +83,11 @@
|
||||
|
||||
&.outline-primary {
|
||||
background: transparent;
|
||||
color: #ff9f43;
|
||||
border: 2px solid #ff9f43;
|
||||
color: #36175e;
|
||||
border: 2px solid #36175e;
|
||||
|
||||
&:hover:not(.loading):not(.disabled) {
|
||||
background: #ff9f43;
|
||||
background: #36175e;
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 159, 67, 0.4);
|
||||
|
||||
18
src/components/ProtectedRoute.jsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const ProtectedRoute = ({ children }) => {
|
||||
// Check if user is authenticated using Redux state
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const isAuthenticated = authState?.isAuthenticated || authState?.token;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
// Redirect to login page if not authenticated
|
||||
return <Navigate to="/signin" replace />;
|
||||
}
|
||||
|
||||
// If authenticated, render the protected component
|
||||
return children;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
@ -1,72 +1,148 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Swal from "sweetalert2";
|
||||
import {
|
||||
createCategory,
|
||||
fetchCategories,
|
||||
} from "../../redux/actions/categoryActions";
|
||||
|
||||
const AddCategoryList = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Add Category */}
|
||||
<div className="modal fade" id="add-category">
|
||||
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper-new p-0">
|
||||
<div className="content">
|
||||
<div className="modal-header border-0 custom-modal-header">
|
||||
<div className="page-title">
|
||||
<h4>Create Category</h4>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body custom-modal-body">
|
||||
<form>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category</label>
|
||||
<input type="text" className="form-control" />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category Slug</label>
|
||||
<input type="text" className="form-control" />
|
||||
</div>
|
||||
<div className="mb-0">
|
||||
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||
<span className="status-label">Status</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="user2"
|
||||
className="check"
|
||||
defaultChecked="true"
|
||||
/>
|
||||
<label htmlFor="user2" className="checktoggle" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-cancel me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<Link to="#" className="btn btn-submit">
|
||||
Create Category
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Add Category */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
const { creating } = useSelector((state) => state.categories);
|
||||
|
||||
export default AddCategoryList
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await dispatch(createCategory(formData));
|
||||
|
||||
await dispatch(fetchCategories());
|
||||
|
||||
Swal.fire({
|
||||
title: "Success!",
|
||||
text: "Category created successfully!",
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 1500,
|
||||
}).then(() => {
|
||||
const closeButton = document.querySelector("#add-category .btn-close");
|
||||
closeButton.click();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating category:", error);
|
||||
|
||||
// Show error message
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Error!",
|
||||
text: error.message || "Failed to create category. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Add Category */}
|
||||
<div className="modal fade" id="add-category">
|
||||
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper-new p-0">
|
||||
<div className="content">
|
||||
<div className="modal-header">
|
||||
<div className="page-title">
|
||||
<h4>Create Category</h4>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body custom-modal-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control border"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control border"
|
||||
name="description"
|
||||
value={formData.description}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-0">
|
||||
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||
<span className="status-label">Status</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="user2"
|
||||
className="check"
|
||||
defaultChecked="true"
|
||||
/>
|
||||
<label htmlFor="user2" className="checktoggle" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-dark me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={creating}
|
||||
className="btn btn-submit"
|
||||
>
|
||||
{creating ? (
|
||||
<>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm me-2"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
"Create Category"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Add Category */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCategoryList;
|
||||
|
||||
@ -1,80 +1,160 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Swal from "sweetalert2";
|
||||
import {
|
||||
fetchCategories,
|
||||
updateCategory,
|
||||
} from "../../redux/actions/categoryActions";
|
||||
|
||||
const EditCategoryList = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Edit Category */}
|
||||
<div className="modal fade" id="edit-category">
|
||||
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper-new p-0">
|
||||
<div className="content">
|
||||
<div className="modal-header border-0 custom-modal-header">
|
||||
<div className="page-title">
|
||||
<h4>Edit Category</h4>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body custom-modal-body">
|
||||
<form>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
defaultValue="Laptop"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category Slug</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
defaultValue="laptop"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-0">
|
||||
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||
<span className="status-label">Status</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="user3"
|
||||
className="check"
|
||||
defaultChecked="true"
|
||||
/>
|
||||
<label htmlFor="user3" className="checktoggle" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-cancel me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<Link to="#" className="btn btn-submit">
|
||||
Save Changes
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Edit Category */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
|
||||
export default EditCategoryList
|
||||
const { currentCategory, updating, currentPage, pageSize } = useSelector(
|
||||
(state) => state.categories
|
||||
);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentCategory) {
|
||||
setFormData({
|
||||
name: currentCategory.name,
|
||||
description: currentCategory.description,
|
||||
});
|
||||
}
|
||||
}, [currentCategory]);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await dispatch(updateCategory(currentCategory?.id, formData));
|
||||
|
||||
await dispatch(fetchCategories({ page: currentPage, limit: pageSize }));
|
||||
|
||||
Swal.fire({
|
||||
title: "Success!",
|
||||
text: "Category updated successfully!",
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 1500,
|
||||
}).then(() => {
|
||||
const closeButton = document.querySelector("#edit-category .btn-close");
|
||||
closeButton.click();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating category:", error);
|
||||
|
||||
// Show error message
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Error!",
|
||||
text: error.message || "Failed to update category. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Edit Category */}
|
||||
<div className="modal fade" id="edit-category">
|
||||
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper-new p-0">
|
||||
<div className="content">
|
||||
<div className="modal-header">
|
||||
<div className="page-title">
|
||||
<h4>Edit Category</h4>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body custom-modal-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control border"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control border"
|
||||
name="description"
|
||||
value={formData.description}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-0">
|
||||
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||
<span className="status-label">Status</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="user3"
|
||||
className="check"
|
||||
defaultChecked="true"
|
||||
/>
|
||||
<label htmlFor="user3" className="checktoggle" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-dark me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={updating}
|
||||
className="btn btn-submit"
|
||||
>
|
||||
{updating ? (
|
||||
<>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm me-2"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
Updating...
|
||||
</>
|
||||
) : (
|
||||
"Update Category"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Edit Category */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditCategoryList;
|
||||
|
||||
@ -1,26 +1,149 @@
|
||||
import React from "react";
|
||||
import Select from "react-select";
|
||||
import ImageWithBasePath from "../../img/imagewithbasebath";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import AsyncSelect from "react-select/async";
|
||||
import Swal from "sweetalert2";
|
||||
import outletsApi from "../../../services/outletsApi";
|
||||
import productsApi from "../../../services/productsApi";
|
||||
import {
|
||||
createInventory,
|
||||
fetchInventories,
|
||||
updateInventory,
|
||||
} from "../../redux/actions/inventoryActions";
|
||||
|
||||
const ManageStockModal = () => {
|
||||
const options1 = [
|
||||
{ value: "choose", label: "Choose" },
|
||||
{ value: "lobarHandy", label: "Lobar Handy" },
|
||||
{ value: "quaintWarehouse", label: "Quaint Warehouse" },
|
||||
];
|
||||
const dispatch = useDispatch();
|
||||
const { creating, updating, currentInventory } = useSelector(
|
||||
(state) => state.inventories
|
||||
);
|
||||
|
||||
const options2 = [
|
||||
{ value: "choose", label: "Choose" },
|
||||
{ value: "selosy", label: "Selosy" },
|
||||
{ value: "logerro", label: "Logerro" },
|
||||
];
|
||||
const initializeFormData = () => {
|
||||
return {
|
||||
outlet_id: "",
|
||||
product_id: "",
|
||||
quantity: "",
|
||||
min_stock_level: "",
|
||||
max_stock_level: "",
|
||||
};
|
||||
};
|
||||
|
||||
const [formData, setFormData] = useState(initializeFormData());
|
||||
const [selectedOutlet, setSelectedOutlet] = useState(null);
|
||||
const [selectedProduct, setSelectedProduct] = useState(null);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: Number(e.target.value),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectChange = (field, selectedOption) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[field]: selectedOption ? selectedOption.value : "",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentInventory) {
|
||||
setFormData(currentInventory);
|
||||
}
|
||||
}, [currentInventory]);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await dispatch(createInventory(formData));
|
||||
|
||||
await dispatch(fetchInventories());
|
||||
|
||||
Swal.fire({
|
||||
title: "Success!",
|
||||
text: "Stock created successfully!",
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 1500,
|
||||
}).then(() => {
|
||||
const closeButton = document.querySelector("#add-units .btn-close");
|
||||
closeButton.click();
|
||||
setFormData(initializeFormData());
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating stock:", error);
|
||||
|
||||
// Show error message
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Error!",
|
||||
text: error.message || "Failed to create stock. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await dispatch(updateInventory(currentInventory?.id, formData));
|
||||
|
||||
await dispatch(fetchInventories());
|
||||
|
||||
Swal.fire({
|
||||
title: "Success!",
|
||||
text: "Stock updated successfully!",
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 1500,
|
||||
}).then(() => {
|
||||
const closeButton = document.querySelector("#edit-units .btn-close");
|
||||
closeButton.click();
|
||||
setFormData(initializeFormData());
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating stock:", error);
|
||||
|
||||
// Show error message
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Error!",
|
||||
text: error.message || "Failed to update stock. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const loadOutlets = async (inputValue) => {
|
||||
try {
|
||||
const response = await outletsApi.getAllOutlets({ search: inputValue });
|
||||
|
||||
const options = response.data.outlets.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
|
||||
return options;
|
||||
} catch (error) {
|
||||
console.error("Error fetching options:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const loadProducts = async (inputValue) => {
|
||||
try {
|
||||
const response = await productsApi.getAllProducts({ search: inputValue });
|
||||
|
||||
const options = response.data.products.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
|
||||
return options;
|
||||
} catch (error) {
|
||||
console.error("Error fetching options:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const options3 = [
|
||||
{ value: "choose", label: "Choose" },
|
||||
{ value: "steven", label: "Steven" },
|
||||
{ value: "gravely", label: "Gravely" },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{/* Add Stock */}
|
||||
@ -29,65 +152,109 @@ const ManageStockModal = () => {
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper-new p-0">
|
||||
<div className="content">
|
||||
<div className="modal-header border-0 custom-modal-header">
|
||||
<div className="modal-header">
|
||||
<div className="page-title">
|
||||
<h4>Add Stock</h4>
|
||||
<h4>Create Stock</h4>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body custom-modal-body">
|
||||
<form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Outlet</label>
|
||||
<AsyncSelect
|
||||
className="select"
|
||||
loadOptions={loadOutlets}
|
||||
placeholder="Choose"
|
||||
isClearable
|
||||
cacheOptions={true}
|
||||
defaultOptions={true}
|
||||
value={selectedOutlet}
|
||||
onChange={(selectedOption) => {
|
||||
handleSelectChange("outlet_id", selectedOption);
|
||||
setSelectedOutlet(selectedOption);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Product</label>
|
||||
<AsyncSelect
|
||||
className="select"
|
||||
loadOptions={loadProducts}
|
||||
placeholder="Choose"
|
||||
isClearable
|
||||
cacheOptions={true}
|
||||
defaultOptions={true}
|
||||
value={selectedProduct}
|
||||
onChange={(selectedOption) => {
|
||||
handleSelectChange("product_id", selectedOption);
|
||||
setSelectedProduct(selectedOption);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Quantity</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control border"
|
||||
name="quantity"
|
||||
value={formData.quantity}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="input-blocks">
|
||||
<label>Warehouse</label>
|
||||
<Select className="select" options={options1} />
|
||||
</div>
|
||||
<div className="col">
|
||||
<label className="form-label">Min Stock</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control border"
|
||||
name="min_stock_level"
|
||||
value={formData.min_stock_level}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="input-blocks">
|
||||
<label>Shop</label>
|
||||
<Select className="select" options={options2} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-12">
|
||||
<div className="input-blocks">
|
||||
<label>Responsible Person</label>
|
||||
<Select className="select" options={options3} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-12">
|
||||
<div className="input-blocks search-form mb-0">
|
||||
<label>Product</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Select Product"
|
||||
/>
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search custom-search"
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<label className="form-label">Max Stock</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control border"
|
||||
name="max_stock_level"
|
||||
value={formData.max_stock_level}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-cancel me-2"
|
||||
className="btn btn-outline-dark me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-submit">
|
||||
Create
|
||||
<button
|
||||
type="submit"
|
||||
disabled={creating}
|
||||
className="btn btn-submit"
|
||||
>
|
||||
{creating ? (
|
||||
<>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm me-2"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
"Create"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -100,167 +267,79 @@ const ManageStockModal = () => {
|
||||
{/* /Add Stock */}
|
||||
{/* Edit Stock */}
|
||||
<div className="modal fade" id="edit-units">
|
||||
<div className="modal-dialog modal-dialog-centered stock-adjust-modal">
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper-new p-0">
|
||||
<div className="content">
|
||||
<div className="modal-header border-0 custom-modal-header">
|
||||
<div className="modal-header">
|
||||
<div className="page-title">
|
||||
<h4>Edit Stock</h4>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body custom-modal-body">
|
||||
<form>
|
||||
<div className="input-blocks search-form">
|
||||
<label>Product</label>
|
||||
<form onSubmit={handleUpdate}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Quantity</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
defaultValue="Nike Jordan"
|
||||
/>
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search custom-search"
|
||||
type="number"
|
||||
className="form-control border"
|
||||
name="quantity"
|
||||
value={formData.quantity}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="input-blocks">
|
||||
<label>Warehouse</label>
|
||||
<Select className="select" options={options1} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="input-blocks">
|
||||
<label>Shop</label>
|
||||
<Select className="select" options={options2} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-12">
|
||||
<div className="input-blocks">
|
||||
<label>Responsible Person</label>
|
||||
<Select className="select" options={options3} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-12">
|
||||
<div className="input-blocks search-form mb-3">
|
||||
<label>Product</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Select Product"
|
||||
defaultValue="Nike Jordan"
|
||||
/>
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search custom-search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-12">
|
||||
<div className="modal-body-table">
|
||||
<div className="table-responsive">
|
||||
<table className="table datanew">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>SKU</th>
|
||||
<th>Category</th>
|
||||
<th>Qty</th>
|
||||
<th className="no-sort">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div className="productimgname">
|
||||
<Link
|
||||
to="#"
|
||||
className="product-img stock-img"
|
||||
>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/products/stock-img-02.png"
|
||||
alt="product"
|
||||
/>
|
||||
</Link>
|
||||
<Link to="#">
|
||||
Nike Jordan
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
<td>PT002</td>
|
||||
<td>Nike</td>
|
||||
<td>
|
||||
<div className="product-quantity">
|
||||
<span className="quantity-btn">
|
||||
<i
|
||||
data-feather="minus-circle"
|
||||
className="feather-search"
|
||||
/>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="quntity-input"
|
||||
defaultValue={2}
|
||||
/>
|
||||
<span className="quantity-btn">
|
||||
+
|
||||
<i
|
||||
data-feather="plus-circle"
|
||||
className="plus-circle"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
<Link
|
||||
className="me-2 p-2"
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-units"
|
||||
>
|
||||
<i
|
||||
data-feather="edit"
|
||||
className="feather-edit"
|
||||
/>
|
||||
</Link>
|
||||
<Link
|
||||
className="confirm-text p-2"
|
||||
to="#"
|
||||
>
|
||||
<i
|
||||
data-feather="trash-2"
|
||||
className="feather-trash-2"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Min Stock</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control border"
|
||||
name="min_stock_level"
|
||||
value={formData.min_stock_level}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Max Stock</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control border"
|
||||
name="max_stock_level"
|
||||
value={formData.max_stock_level}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer-btn">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-cancel me-2"
|
||||
className="btn btn-outline-dark me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-submit">
|
||||
Save Changes
|
||||
<button
|
||||
type="submit"
|
||||
disabled={updating}
|
||||
className="btn btn-submit"
|
||||
>
|
||||
{updating ? (
|
||||
<>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm me-2"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
Updating...
|
||||
</>
|
||||
) : (
|
||||
"Update"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -7,7 +7,6 @@ import { onShowSizeChange } from "./pagination";
|
||||
const Datatable = ({ props, columns, dataSource }) => {
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||||
const onSelectChange = (newSelectedRowKeys) => {
|
||||
console.log("selectedRowKeys changed: ", selectedRowKeys);
|
||||
setSelectedRowKeys(newSelectedRowKeys);
|
||||
};
|
||||
|
||||
@ -15,15 +14,18 @@ const Datatable = ({ props, columns, dataSource }) => {
|
||||
selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
key={props}
|
||||
className="table datanew dataTable no-footer"
|
||||
className="custom-table table datanew dataTable"
|
||||
rowSelection={rowSelection}
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
|
||||
rowKey={(record) => record.id}
|
||||
pagination={{
|
||||
pageSize: 100
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
195
src/core/redux/actions/categoryActions.js
Normal file
@ -0,0 +1,195 @@
|
||||
import { categoriesApi } from '../../../services/categoriesApi';
|
||||
|
||||
// Action Types
|
||||
export const CATEGORY_ACTIONS = {
|
||||
// Fetch Categories
|
||||
FETCH_CATEGORIES_REQUEST: 'FETCH_CATEGORIES_REQUEST',
|
||||
FETCH_CATEGORIES_SUCCESS: 'FETCH_CATEGORIES_SUCCESS',
|
||||
FETCH_CATEGORIES_FAILURE: 'FETCH_CATEGORIES_FAILURE',
|
||||
|
||||
// Fetch Single Category
|
||||
FETCH_CATEGORY_REQUEST: 'FETCH_CATEGORY_REQUEST',
|
||||
FETCH_CATEGORY_SUCCESS: 'FETCH_CATEGORY_SUCCESS',
|
||||
FETCH_CATEGORY_FAILURE: 'FETCH_CATEGORY_FAILURE',
|
||||
|
||||
// Create Category
|
||||
CREATE_CATEGORY_REQUEST: 'CREATE_CATEGORY_REQUEST',
|
||||
CREATE_CATEGORY_SUCCESS: 'CREATE_CATEGORY_SUCCESS',
|
||||
CREATE_CATEGORY_FAILURE: 'CREATE_CATEGORY_FAILURE',
|
||||
|
||||
// Update Category
|
||||
UPDATE_CATEGORY_REQUEST: 'UPDATE_CATEGORY_REQUEST',
|
||||
UPDATE_CATEGORY_SUCCESS: 'UPDATE_CATEGORY_SUCCESS',
|
||||
UPDATE_CATEGORY_FAILURE: 'UPDATE_CATEGORY_FAILURE',
|
||||
|
||||
// Delete Category
|
||||
DELETE_CATEGORY_REQUEST: 'DELETE_CATEGORY_REQUEST',
|
||||
DELETE_CATEGORY_SUCCESS: 'DELETE_CATEGORY_SUCCESS',
|
||||
DELETE_CATEGORY_FAILURE: 'DELETE_CATEGORY_FAILURE',
|
||||
|
||||
// Search Categories
|
||||
SEARCH_CATEGORIES_REQUEST: 'SEARCH_CATEGORIES_REQUEST',
|
||||
SEARCH_CATEGORIES_SUCCESS: 'SEARCH_CATEGORIES_SUCCESS',
|
||||
SEARCH_CATEGORIES_FAILURE: 'SEARCH_CATEGORIES_FAILURE',
|
||||
|
||||
// Get Category Products
|
||||
FETCH_CATEGORY_PRODUCTS_REQUEST: 'FETCH_CATEGORY_PRODUCTS_REQUEST',
|
||||
FETCH_CATEGORY_PRODUCTS_SUCCESS: 'FETCH_CATEGORY_PRODUCTS_SUCCESS',
|
||||
FETCH_CATEGORY_PRODUCTS_FAILURE: 'FETCH_CATEGORY_PRODUCTS_FAILURE',
|
||||
|
||||
// Clear States
|
||||
CLEAR_CATEGORY_ERROR: 'CLEAR_CATEGORY_ERROR',
|
||||
CLEAR_CURRENT_CATEGORY: 'CLEAR_CURRENT_CATEGORY',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
// Fetch all categories
|
||||
export const fetchCategories = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.FETCH_CATEGORIES_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await categoriesApi.getAllCategories(params);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.FETCH_CATEGORIES_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.FETCH_CATEGORIES_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch categories',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch single category
|
||||
export const fetchCategory = (id) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.FETCH_CATEGORY_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await categoriesApi.getCategoryById(id);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.FETCH_CATEGORY_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.FETCH_CATEGORY_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch category',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Create category
|
||||
export const createCategory = (categoryData) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.CREATE_CATEGORY_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await categoriesApi.createCategory(categoryData);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.CREATE_CATEGORY_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.CREATE_CATEGORY_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to create category',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Update category
|
||||
export const updateCategory = (id, categoryData) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.UPDATE_CATEGORY_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await categoriesApi.updateCategory(id, categoryData);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.UPDATE_CATEGORY_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.UPDATE_CATEGORY_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to update category',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Delete category
|
||||
export const deleteCategory = (id) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.DELETE_CATEGORY_REQUEST });
|
||||
|
||||
try {
|
||||
await categoriesApi.deleteCategory(id);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.DELETE_CATEGORY_SUCCESS,
|
||||
payload: id,
|
||||
});
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.DELETE_CATEGORY_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to delete category',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Search categories
|
||||
export const searchCategories = (query, params = {}) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.SEARCH_CATEGORIES_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await categoriesApi.searchCategories(query, params);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.SEARCH_CATEGORIES_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.SEARCH_CATEGORIES_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to search categories',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch category products
|
||||
export const fetchCategoryProducts = (id, params = {}) => async (dispatch) => {
|
||||
dispatch({ type: CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await categoriesApi.getCategoryProducts(id, params);
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch category products',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear error
|
||||
export const clearCategoryError = () => ({
|
||||
type: CATEGORY_ACTIONS.CLEAR_CATEGORY_ERROR,
|
||||
});
|
||||
|
||||
// Clear current category
|
||||
export const clearCurrentCategory = () => ({
|
||||
type: CATEGORY_ACTIONS.CLEAR_CURRENT_CATEGORY,
|
||||
});
|
||||
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/orderActions.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { ordersApi } from '../../../services/ordersApi';
|
||||
|
||||
// Action Types
|
||||
export const ORDER_ACTIONS = {
|
||||
// Fetch Orders
|
||||
FETCH_ORDERS_REQUEST: 'FETCH_ORDERS_REQUEST',
|
||||
FETCH_ORDERS_SUCCESS: 'FETCH_ORDERS_SUCCESS',
|
||||
FETCH_ORDERS_FAILURE: 'FETCH_ORDERS_FAILURE',
|
||||
|
||||
// Fetch Single Order
|
||||
FETCH_ORDER_REQUEST: 'FETCH_ORDER_REQUEST',
|
||||
FETCH_ORDER_SUCCESS: 'FETCH_ORDER_SUCCESS',
|
||||
FETCH_ORDER_FAILURE: 'FETCH_ORDER_FAILURE',
|
||||
|
||||
// Create Order
|
||||
CREATE_ORDER_REQUEST: 'CREATE_ORDER_REQUEST',
|
||||
CREATE_ORDER_SUCCESS: 'CREATE_ORDER_SUCCESS',
|
||||
CREATE_ORDER_FAILURE: 'CREATE_ORDER_FAILURE',
|
||||
|
||||
// Update Order
|
||||
UPDATE_ORDER_REQUEST: 'UPDATE_ORDER_REQUEST',
|
||||
UPDATE_ORDER_SUCCESS: 'UPDATE_ORDER_SUCCESS',
|
||||
UPDATE_ORDER_FAILURE: 'UPDATE_ORDER_FAILURE',
|
||||
|
||||
// Delete Order
|
||||
DELETE_ORDER_REQUEST: 'DELETE_ORDER_REQUEST',
|
||||
DELETE_ORDER_SUCCESS: 'DELETE_ORDER_SUCCESS',
|
||||
DELETE_ORDER_FAILURE: 'DELETE_ORDER_FAILURE',
|
||||
|
||||
// Search Orders
|
||||
SEARCH_ORDERS_REQUEST: 'SEARCH_ORDERS_REQUEST',
|
||||
SEARCH_ORDERS_SUCCESS: 'SEARCH_ORDERS_SUCCESS',
|
||||
SEARCH_ORDERS_FAILURE: 'SEARCH_ORDERS_FAILURE',
|
||||
|
||||
// Clear States
|
||||
CLEAR_ORDER_ERROR: 'CLEAR_ORDER_ERROR',
|
||||
CLEAR_CURRENT_ORDER: 'CLEAR_CURRENT_ORDER',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const fetchOrders = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: ORDER_ACTIONS.FETCH_ORDERS_REQUEST });
|
||||
try {
|
||||
const data = await ordersApi.getAllOrders(params);
|
||||
dispatch({ type: ORDER_ACTIONS.FETCH_ORDERS_SUCCESS, payload: data });
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORDER_ACTIONS.FETCH_ORDERS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch orders',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchOrder = (id) => async (dispatch) => {
|
||||
dispatch({ type: ORDER_ACTIONS.FETCH_ORDER_REQUEST });
|
||||
try {
|
||||
const data = await ordersApi.getOrderById(id);
|
||||
dispatch({ type: ORDER_ACTIONS.FETCH_ORDER_SUCCESS, payload: data });
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORDER_ACTIONS.FETCH_ORDER_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch order',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createOrder = (orderData) => async (dispatch) => {
|
||||
dispatch({ type: ORDER_ACTIONS.CREATE_ORDER_REQUEST });
|
||||
try {
|
||||
const data = await ordersApi.createOrder(orderData);
|
||||
dispatch({ type: ORDER_ACTIONS.CREATE_ORDER_SUCCESS, payload: data });
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORDER_ACTIONS.CREATE_ORDER_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to create order',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOrder = (id, orderData) => async (dispatch) => {
|
||||
dispatch({ type: ORDER_ACTIONS.UPDATE_ORDER_REQUEST });
|
||||
try {
|
||||
const data = await ordersApi.updateOrder(id, orderData);
|
||||
dispatch({ type: ORDER_ACTIONS.UPDATE_ORDER_SUCCESS, payload: { id, data } });
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORDER_ACTIONS.UPDATE_ORDER_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to update order',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteOrder = (id) => async (dispatch) => {
|
||||
dispatch({ type: ORDER_ACTIONS.DELETE_ORDER_REQUEST });
|
||||
try {
|
||||
await ordersApi.deleteOrder(id);
|
||||
dispatch({ type: ORDER_ACTIONS.DELETE_ORDER_SUCCESS, payload: id });
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORDER_ACTIONS.DELETE_ORDER_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to delete order',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const searchOrders = (query, params = {}) => async (dispatch) => {
|
||||
dispatch({ type: ORDER_ACTIONS.SEARCH_ORDERS_REQUEST });
|
||||
try {
|
||||
const data = await ordersApi.searchOrders(query, params);
|
||||
dispatch({ type: ORDER_ACTIONS.SEARCH_ORDERS_SUCCESS, payload: data });
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORDER_ACTIONS.SEARCH_ORDERS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to search orders',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const clearOrderError = () => ({
|
||||
type: ORDER_ACTIONS.CLEAR_ORDER_ERROR,
|
||||
});
|
||||
|
||||
export const clearCurrentOrder = () => ({
|
||||
type: ORDER_ACTIONS.CLEAR_CURRENT_ORDER,
|
||||
});
|
||||
132
src/core/redux/actions/organizationActions.js
Normal file
@ -0,0 +1,132 @@
|
||||
import { organizationsApi } from '../../../services/organizationsApi';
|
||||
|
||||
// Action Types
|
||||
export const ORGANIZATION_ACTIONS = {
|
||||
FETCH_ORGANIZATIONS_REQUEST: 'FETCH_ORGANIZATIONS_REQUEST',
|
||||
FETCH_ORGANIZATIONS_SUCCESS: 'FETCH_ORGANIZATIONS_SUCCESS',
|
||||
FETCH_ORGANIZATIONS_FAILURE: 'FETCH_ORGANIZATIONS_FAILURE',
|
||||
|
||||
FETCH_ORGANIZATION_REQUEST: 'FETCH_ORGANIZATION_REQUEST',
|
||||
FETCH_ORGANIZATION_SUCCESS: 'FETCH_ORGANIZATION_SUCCESS',
|
||||
FETCH_ORGANIZATION_FAILURE: 'FETCH_ORGANIZATION_FAILURE',
|
||||
|
||||
CREATE_ORGANIZATION_REQUEST: 'CREATE_ORGANIZATION_REQUEST',
|
||||
CREATE_ORGANIZATION_SUCCESS: 'CREATE_ORGANIZATION_SUCCESS',
|
||||
CREATE_ORGANIZATION_FAILURE: 'CREATE_ORGANIZATION_FAILURE',
|
||||
|
||||
UPDATE_ORGANIZATION_REQUEST: 'UPDATE_ORGANIZATION_REQUEST',
|
||||
UPDATE_ORGANIZATION_SUCCESS: 'UPDATE_ORGANIZATION_SUCCESS',
|
||||
UPDATE_ORGANIZATION_FAILURE: 'UPDATE_ORGANIZATION_FAILURE',
|
||||
|
||||
DELETE_ORGANIZATION_REQUEST: 'DELETE_ORGANIZATION_REQUEST',
|
||||
DELETE_ORGANIZATION_SUCCESS: 'DELETE_ORGANIZATION_SUCCESS',
|
||||
DELETE_ORGANIZATION_FAILURE: 'DELETE_ORGANIZATION_FAILURE',
|
||||
|
||||
CLEAR_ORGANIZATION_ERROR: 'CLEAR_ORGANIZATION_ERROR',
|
||||
CLEAR_CURRENT_ORGANIZATION: 'CLEAR_CURRENT_ORGANIZATION',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const fetchOrganizations = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.getAllOrganizations(params);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch organizations',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchOrganization = (id) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.getOrganizationById(id);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createOrganization = (organizationData) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.createOrganization(organizationData);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to create organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOrganization = (id, organizationData) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.updateOrganization(id, organizationData);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to update organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteOrganization = (id) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
await organizationsApi.deleteOrganization(id);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_SUCCESS,
|
||||
payload: id,
|
||||
});
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to delete organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const clearOrganizationError = () => ({
|
||||
type: ORGANIZATION_ACTIONS.CLEAR_ORGANIZATION_ERROR,
|
||||
});
|
||||
|
||||
export const clearCurrentOrganization = () => ({
|
||||
type: ORGANIZATION_ACTIONS.CLEAR_CURRENT_ORGANIZATION,
|
||||
});
|
||||
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,
|
||||
});
|
||||
126
src/core/redux/actions/paymentMethodActions.js
Normal file
@ -0,0 +1,126 @@
|
||||
import { paymentMethodsApi } from '../../../services/paymentMethodsApi';
|
||||
|
||||
export const PAYMENT_METHOD_ACTIONS = {
|
||||
FETCH_PAYMENT_METHODS_REQUEST: 'FETCH_PAYMENT_METHODS_REQUEST',
|
||||
FETCH_PAYMENT_METHODS_SUCCESS: 'FETCH_PAYMENT_METHODS_SUCCESS',
|
||||
FETCH_PAYMENT_METHODS_FAILURE: 'FETCH_PAYMENT_METHODS_FAILURE',
|
||||
|
||||
FETCH_PAYMENT_METHOD_REQUEST: 'FETCH_PAYMENT_METHOD_REQUEST',
|
||||
FETCH_PAYMENT_METHOD_SUCCESS: 'FETCH_PAYMENT_METHOD_SUCCESS',
|
||||
FETCH_PAYMENT_METHOD_FAILURE: 'FETCH_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
CREATE_PAYMENT_METHOD_REQUEST: 'CREATE_PAYMENT_METHOD_REQUEST',
|
||||
CREATE_PAYMENT_METHOD_SUCCESS: 'CREATE_PAYMENT_METHOD_SUCCESS',
|
||||
CREATE_PAYMENT_METHOD_FAILURE: 'CREATE_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
UPDATE_PAYMENT_METHOD_REQUEST: 'UPDATE_PAYMENT_METHOD_REQUEST',
|
||||
UPDATE_PAYMENT_METHOD_SUCCESS: 'UPDATE_PAYMENT_METHOD_SUCCESS',
|
||||
UPDATE_PAYMENT_METHOD_FAILURE: 'UPDATE_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
DELETE_PAYMENT_METHOD_REQUEST: 'DELETE_PAYMENT_METHOD_REQUEST',
|
||||
DELETE_PAYMENT_METHOD_SUCCESS: 'DELETE_PAYMENT_METHOD_SUCCESS',
|
||||
DELETE_PAYMENT_METHOD_FAILURE: 'DELETE_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
CLEAR_PAYMENT_METHOD_ERROR: 'CLEAR_PAYMENT_METHOD_ERROR',
|
||||
CLEAR_CURRENT_PAYMENT_METHOD: 'CLEAR_CURRENT_PAYMENT_METHOD',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const fetchPaymentMethods = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.getAll(params);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPaymentMethod = (id) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.getById(id);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createPaymentMethod = (formData) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.create(formData);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePaymentMethod = (id, formData) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.update(id, formData);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePaymentMethod = (id) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
await paymentMethodsApi.remove(id);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_SUCCESS,
|
||||
payload: id,
|
||||
});
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const clearPaymentMethodError = () => ({
|
||||
type: PAYMENT_METHOD_ACTIONS.CLEAR_PAYMENT_METHOD_ERROR,
|
||||
});
|
||||
|
||||
export const clearCurrentPaymentMethod = () => ({
|
||||
type: PAYMENT_METHOD_ACTIONS.CLEAR_CURRENT_PAYMENT_METHOD,
|
||||
});
|
||||
@ -89,6 +89,7 @@ export const createProduct = (productData) => async (dispatch) => {
|
||||
|
||||
try {
|
||||
const data = await productsApi.createProduct(productData);
|
||||
console.log('data', data)
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS,
|
||||
payload: data,
|
||||
@ -163,21 +164,6 @@ export const searchProducts = (query, params = {}) => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch categories
|
||||
export const fetchCategories = () => async (dispatch) => {
|
||||
try {
|
||||
const data = await productsApi.getCategories();
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_CATEGORIES_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch categories:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch brands
|
||||
export const fetchBrands = () => async (dispatch) => {
|
||||
try {
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
import initialState from "./initial.value";
|
||||
import productReducer from './reducers/productReducer';
|
||||
import authReducer from './reducers/authReducer';
|
||||
import categoryReducer from './reducers/categoryReducer';
|
||||
import orderReducer from './reducers/orderReducer';
|
||||
import paymentMethodReducer from './reducers/paymentMethodReducer';
|
||||
import organizationReducer from './reducers/organizationReducer';
|
||||
import inventoryReducer from './reducers/inventoryReducer';
|
||||
import outletReducer from './reducers/outletReducer';
|
||||
|
||||
// Legacy reducer for existing functionality
|
||||
const legacyReducer = (state = initialState, action) => {
|
||||
@ -73,6 +80,13 @@ const legacyReducer = (state = initialState, action) => {
|
||||
const rootReducer = combineReducers({
|
||||
legacy: legacyReducer,
|
||||
products: productReducer,
|
||||
auth: authReducer,
|
||||
categories: categoryReducer,
|
||||
orders: orderReducer,
|
||||
paymentMethods: paymentMethodReducer,
|
||||
organizations: organizationReducer,
|
||||
inventories: inventoryReducer,
|
||||
outlets: outletReducer
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
||||
59
src/core/redux/reducers/authReducer.js
Normal file
@ -0,0 +1,59 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
token: localStorage.getItem('authToken') || null,
|
||||
loading: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {
|
||||
loginStart: (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
},
|
||||
loginSuccess: (state, action) => {
|
||||
state.isAuthenticated = true;
|
||||
state.user = action.payload.data.user;
|
||||
state.token = action.payload.data.token;
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
// Store token in localStorage
|
||||
localStorage.setItem('authToken', action.payload.data.token);
|
||||
localStorage.setItem('user', JSON.stringify(action.payload.data.user));
|
||||
},
|
||||
loginFailure: (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload.error;
|
||||
state.isAuthenticated = false;
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
},
|
||||
logout: (state) => {
|
||||
state.isAuthenticated = false;
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
// Clear localStorage
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('user');
|
||||
},
|
||||
checkAuth: (state) => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const user = localStorage.getItem('user');
|
||||
if (token && user) {
|
||||
state.isAuthenticated = true;
|
||||
state.token = token;
|
||||
state.user = JSON.parse(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { loginStart, loginSuccess, loginFailure, logout, checkAuth } = authSlice.actions;
|
||||
export default authSlice.reducer;
|
||||
245
src/core/redux/reducers/categoryReducer.js
Normal file
@ -0,0 +1,245 @@
|
||||
import { CATEGORY_ACTIONS } from '../actions/categoryActions';
|
||||
|
||||
const initialState = {
|
||||
// Categories list
|
||||
categories: [],
|
||||
totalCategories: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
|
||||
// Current category (for edit/view)
|
||||
currentCategory: null,
|
||||
|
||||
// Search results
|
||||
searchResults: [],
|
||||
searchQuery: '',
|
||||
|
||||
// Category products
|
||||
categoryProducts: [],
|
||||
categoryProductsLoading: false,
|
||||
categoryProductsError: null,
|
||||
|
||||
// Loading states
|
||||
loading: false,
|
||||
categoryLoading: false,
|
||||
searchLoading: false,
|
||||
|
||||
// Error states
|
||||
error: null,
|
||||
categoryError: null,
|
||||
searchError: null,
|
||||
|
||||
// Operation states
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
};
|
||||
|
||||
const categoryReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
// Fetch Categories
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORIES_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORIES_SUCCESS: {
|
||||
// Handle different API response structures
|
||||
const { categories, total_count, page, total_pages, limit } =
|
||||
action.payload.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
categories: categories,
|
||||
totalCategories: total_count || categories.length,
|
||||
currentPage: page || 1,
|
||||
totalPages: total_pages || 1,
|
||||
pageSize: limit || 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORIES_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Fetch Single Category
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
categoryLoading: true,
|
||||
categoryError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORY_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
categoryLoading: false,
|
||||
currentCategory: action.payload.data,
|
||||
categoryError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORY_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
categoryLoading: false,
|
||||
categoryError: action.payload,
|
||||
};
|
||||
|
||||
// Create Category
|
||||
case CATEGORY_ACTIONS.CREATE_CATEGORY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
creating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.CREATE_CATEGORY_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
categories: [action.payload.data, ...state.categories],
|
||||
totalCategories: state.totalCategories + 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.CREATE_CATEGORY_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Update Category
|
||||
case CATEGORY_ACTIONS.UPDATE_CATEGORY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
updating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.UPDATE_CATEGORY_SUCCESS:
|
||||
console.log('state', state)
|
||||
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
categories: state.categories.map(category =>
|
||||
category.id === action.payload.data.id ? action.payload.data : category
|
||||
),
|
||||
currentCategory: action.payload.data,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.UPDATE_CATEGORY_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Delete Category
|
||||
case CATEGORY_ACTIONS.DELETE_CATEGORY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
deleting: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.DELETE_CATEGORY_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
categories: state.categories.filter(category => category.id !== action.payload),
|
||||
totalCategories: state.totalCategories - 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.DELETE_CATEGORY_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Search Categories
|
||||
case CATEGORY_ACTIONS.SEARCH_CATEGORIES_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: true,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.SEARCH_CATEGORIES_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchResults: action.payload.data || action.payload,
|
||||
searchQuery: action.payload.query || '',
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.SEARCH_CATEGORIES_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchError: action.payload,
|
||||
};
|
||||
|
||||
// Category Products
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
categoryProductsLoading: true,
|
||||
categoryProductsError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
categoryProductsLoading: false,
|
||||
categoryProducts: action.payload.data || action.payload,
|
||||
categoryProductsError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
categoryProductsLoading: false,
|
||||
categoryProductsError: action.payload,
|
||||
};
|
||||
|
||||
// Clear States
|
||||
case CATEGORY_ACTIONS.CLEAR_CATEGORY_ERROR:
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
categoryError: null,
|
||||
searchError: null,
|
||||
categoryProductsError: null,
|
||||
};
|
||||
|
||||
case CATEGORY_ACTIONS.CLEAR_CURRENT_CATEGORY:
|
||||
return {
|
||||
...state,
|
||||
currentCategory: null,
|
||||
categoryError: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default categoryReducer;
|
||||
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;
|
||||
211
src/core/redux/reducers/orderReducer.js
Normal file
@ -0,0 +1,211 @@
|
||||
import { ORDER_ACTIONS } from '../actions/orderActions';
|
||||
|
||||
const initialState = {
|
||||
// Orders list
|
||||
orders: [],
|
||||
totalOrders: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
|
||||
// Current order (for detail/edit)
|
||||
currentOrder: null,
|
||||
|
||||
// Search results
|
||||
searchResults: [],
|
||||
searchQuery: '',
|
||||
|
||||
// Loading states
|
||||
loading: false,
|
||||
orderLoading: false,
|
||||
searchLoading: false,
|
||||
|
||||
// Error states
|
||||
error: null,
|
||||
orderError: null,
|
||||
searchError: null,
|
||||
|
||||
// Operation states
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
};
|
||||
|
||||
const orderReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
// Fetch Orders
|
||||
case ORDER_ACTIONS.FETCH_ORDERS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.FETCH_ORDERS_SUCCESS: {
|
||||
const { orders, total_count, page, total_pages, limit } = action.payload.data;
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
orders: orders,
|
||||
totalOrders: total_count || orders.length,
|
||||
currentPage: page || 1,
|
||||
totalPages: total_pages || 1,
|
||||
pageSize: limit || 10,
|
||||
hasPrevious: page > 1,
|
||||
hasNext: page < total_pages,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
case ORDER_ACTIONS.FETCH_ORDERS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Fetch Single Order
|
||||
case ORDER_ACTIONS.FETCH_ORDER_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
orderLoading: true,
|
||||
orderError: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.FETCH_ORDER_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
orderLoading: false,
|
||||
currentOrder: action.payload.data,
|
||||
orderError: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.FETCH_ORDER_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
orderLoading: false,
|
||||
orderError: action.payload,
|
||||
};
|
||||
|
||||
// Create Order
|
||||
case ORDER_ACTIONS.CREATE_ORDER_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
creating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.CREATE_ORDER_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
orders: [action.payload.data, ...state.orders],
|
||||
totalOrders: state.totalOrders + 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.CREATE_ORDER_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Update Order
|
||||
case ORDER_ACTIONS.UPDATE_ORDER_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
updating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.UPDATE_ORDER_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
orders: state.orders.map(order =>
|
||||
order.id === action.payload.data.id ? action.payload.data : order
|
||||
),
|
||||
currentOrder: action.payload.data,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.UPDATE_ORDER_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Delete Order
|
||||
case ORDER_ACTIONS.DELETE_ORDER_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
deleting: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.DELETE_ORDER_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
orders: state.orders.filter(order => order.id !== action.payload),
|
||||
totalOrders: state.totalOrders - 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.DELETE_ORDER_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Search Orders
|
||||
case ORDER_ACTIONS.SEARCH_ORDERS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: true,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.SEARCH_ORDERS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchResults: action.payload.data || action.payload,
|
||||
searchQuery: action.payload.query || '',
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.SEARCH_ORDERS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchError: action.payload,
|
||||
};
|
||||
|
||||
// Clear States
|
||||
case ORDER_ACTIONS.CLEAR_ORDER_ERROR:
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
orderError: null,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORDER_ACTIONS.CLEAR_CURRENT_ORDER:
|
||||
return {
|
||||
...state,
|
||||
currentOrder: null,
|
||||
orderError: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default orderReducer;
|
||||
212
src/core/redux/reducers/organizationReducer.js
Normal file
@ -0,0 +1,212 @@
|
||||
import { ORGANIZATION_ACTIONS } from '../actions/organizationActions';
|
||||
|
||||
const initialState = {
|
||||
// Organizations list
|
||||
organizations: [],
|
||||
totalOrganizations: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
|
||||
// Current organization (for edit/view)
|
||||
currentOrganization: null,
|
||||
|
||||
// Search results
|
||||
searchResults: [],
|
||||
searchQuery: '',
|
||||
|
||||
// Loading states
|
||||
loading: false,
|
||||
organizationLoading: false,
|
||||
searchLoading: false,
|
||||
|
||||
// Error states
|
||||
error: null,
|
||||
organizationError: null,
|
||||
searchError: null,
|
||||
|
||||
// Operation states
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
};
|
||||
|
||||
const organizationReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
// Fetch Organizations
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_SUCCESS: {
|
||||
const { organizations, total_count, page, total_pages, limit } = action.payload.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
organizations: organizations,
|
||||
totalOrganizations: total_count || organizations.length,
|
||||
currentPage: page || 1,
|
||||
totalPages: total_pages || 1,
|
||||
pageSize: limit || 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Fetch Single Organization
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
organizationLoading: true,
|
||||
organizationError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
organizationLoading: false,
|
||||
currentOrganization: action.payload.data,
|
||||
organizationError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
organizationLoading: false,
|
||||
organizationError: action.payload,
|
||||
};
|
||||
|
||||
// Create Organization
|
||||
case ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
creating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
organizations: [action.payload.data, ...state.organizations],
|
||||
totalOrganizations: state.totalOrganizations + 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Update Organization
|
||||
case ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
updating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
organizations: state.organizations.map(org =>
|
||||
org.id === action.payload.data.id ? action.payload.data : org
|
||||
),
|
||||
currentOrganization: action.payload.data,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Delete Organization
|
||||
case ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
deleting: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
organizations: state.organizations.filter(org => org.id !== action.payload),
|
||||
totalOrganizations: state.totalOrganizations - 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Search Organizations
|
||||
case ORGANIZATION_ACTIONS.SEARCH_ORGANIZATIONS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: true,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.SEARCH_ORGANIZATIONS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchResults: action.payload.data || action.payload,
|
||||
searchQuery: action.payload.query || '',
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.SEARCH_ORGANIZATIONS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchError: action.payload,
|
||||
};
|
||||
|
||||
// Clear States
|
||||
case ORGANIZATION_ACTIONS.CLEAR_ORGANIZATION_ERROR:
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
organizationError: null,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.CLEAR_CURRENT_ORGANIZATION:
|
||||
return {
|
||||
...state,
|
||||
currentOrganization: null,
|
||||
organizationError: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default organizationReducer;
|
||||
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;
|
||||
106
src/core/redux/reducers/paymentMethodReducer.js
Normal file
@ -0,0 +1,106 @@
|
||||
import { PAYMENT_METHOD_ACTIONS } from '../actions/paymentMethodActions';
|
||||
|
||||
const initialState = {
|
||||
paymentMethods: [],
|
||||
totalPaymentMethods: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 10,
|
||||
|
||||
currentPaymentMethod: null,
|
||||
|
||||
loading: false,
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
|
||||
error: null,
|
||||
detailError: null,
|
||||
};
|
||||
|
||||
const paymentMethodReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_REQUEST:
|
||||
return { ...state, loading: true, error: null };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_SUCCESS: {
|
||||
const { payment_methods, total_count, page, total_pages, limit } = action.payload.data;
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
paymentMethods: payment_methods,
|
||||
totalPaymentMethods: total_count || payment_methods.length,
|
||||
currentPage: page || 1,
|
||||
totalPages: total_pages || 1,
|
||||
pageSize: limit || 10,
|
||||
};
|
||||
}
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_FAILURE:
|
||||
return { ...state, loading: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, detailLoading: true, detailError: null };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_SUCCESS:
|
||||
return { ...state, detailLoading: false, currentPaymentMethod: action.payload.data };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, detailLoading: false, detailError: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, creating: true };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
paymentMethods: [action.payload.data, ...state.paymentMethods],
|
||||
totalPaymentMethods: state.totalPaymentMethods + 1,
|
||||
};
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, creating: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, updating: true };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
paymentMethods: state.paymentMethods.map((item) =>
|
||||
item.id === action.payload.data.id ? action.payload.data : item
|
||||
),
|
||||
currentPaymentMethod: action.payload.data,
|
||||
};
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, updating: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, deleting: true };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
paymentMethods: state.paymentMethods.filter((item) => item.id !== action.payload),
|
||||
totalPaymentMethods: state.totalPaymentMethods - 1,
|
||||
};
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, deleting: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CLEAR_PAYMENT_METHOD_ERROR:
|
||||
return { ...state, error: null, detailError: null };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CLEAR_CURRENT_PAYMENT_METHOD:
|
||||
return { ...state, currentPaymentMethod: null };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default paymentMethodReducer;
|
||||
@ -1,4 +1,4 @@
|
||||
import { PRODUCT_ACTIONS } from '../actions/productActions';
|
||||
import { PRODUCT_ACTIONS } from "../actions/productActions";
|
||||
|
||||
const initialState = {
|
||||
// Products list
|
||||
@ -6,7 +6,7 @@ const initialState = {
|
||||
totalProducts: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
|
||||
@ -15,7 +15,7 @@ const initialState = {
|
||||
|
||||
// Search results
|
||||
searchResults: [],
|
||||
searchQuery: '',
|
||||
searchQuery: "",
|
||||
|
||||
// Categories and brands
|
||||
categories: [],
|
||||
@ -49,20 +49,19 @@ const productReducer = (state = initialState, action) => {
|
||||
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS: {
|
||||
// Handle different API response structures
|
||||
const isArrayResponse = Array.isArray(action.payload);
|
||||
const products = isArrayResponse ? action.payload : (action.payload.data || action.payload.items || []);
|
||||
const pagination = action.payload.pagination || {};
|
||||
const { products, total_count, page, total_pages, limit } =
|
||||
action.payload.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
products: products,
|
||||
totalProducts: pagination.totalCount || action.payload.total || products.length,
|
||||
currentPage: pagination.currentPage || action.payload.currentPage || 1,
|
||||
totalPages: pagination.totalPages || action.payload.totalPages || 1,
|
||||
pageSize: pagination.pageSize || 20,
|
||||
hasPrevious: pagination.hasPrevious || false,
|
||||
hasNext: pagination.hasNext || false,
|
||||
totalProducts: total_count,
|
||||
currentPage: page,
|
||||
totalPages: total_pages,
|
||||
pageSize: limit,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
@ -86,7 +85,7 @@ const productReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
productLoading: false,
|
||||
currentProduct: action.payload,
|
||||
currentProduct: action.payload.data,
|
||||
productError: null,
|
||||
};
|
||||
|
||||
@ -106,10 +105,11 @@ const productReducer = (state = initialState, action) => {
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS:
|
||||
console.log("state", state);
|
||||
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
products: [action.payload, ...state.products],
|
||||
totalProducts: state.totalProducts + 1,
|
||||
error: null,
|
||||
};
|
||||
@ -133,7 +133,7 @@ const productReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
products: state.products.map(product =>
|
||||
products: state.products.map((product) =>
|
||||
product.id === action.payload.id ? action.payload.data : product
|
||||
),
|
||||
currentProduct: action.payload.data,
|
||||
@ -159,7 +159,9 @@ const productReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
products: state.products.filter(product => product.id !== action.payload),
|
||||
products: state.products.filter(
|
||||
(product) => product.id !== action.payload
|
||||
),
|
||||
totalProducts: state.totalProducts - 1,
|
||||
error: null,
|
||||
};
|
||||
@ -184,7 +186,7 @@ const productReducer = (state = initialState, action) => {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchResults: action.payload.data || action.payload,
|
||||
searchQuery: action.payload.query || '',
|
||||
searchQuery: action.payload.query || "",
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
|
||||
@ -5,4 +5,5 @@ const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
});
|
||||
|
||||
export { store };
|
||||
export default store;
|
||||
|
||||
387
src/feature-module/FinanceAccounts/paymentmethodlist.jsx
Normal file
@ -0,0 +1,387 @@
|
||||
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 { deletePaymentMethod, fetchPaymentMethod, fetchPaymentMethods } from "../../core/redux/actions/paymentMethodActions";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
const PaymentMethodList = () => {
|
||||
const {
|
||||
paymentMethods: apiPaymentMethods,
|
||||
loading,
|
||||
error,
|
||||
totalPaymentMethods,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage,
|
||||
} = useSelector((state) => state.paymentMethods);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const dataSource = apiPaymentMethods?.length > 0 ? apiPaymentMethods : [];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadPaymentMethods = 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(fetchPaymentMethods(cleanParams));
|
||||
} catch (error) {
|
||||
console.error("Failed to load categories", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadPaymentMethods();
|
||||
}, [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 = totalPaymentMethods || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
const handleDeletePaymentMethod = async (paymentMethodId) => {
|
||||
try {
|
||||
await dispatch(deletePaymentMethod(paymentMethodId));
|
||||
// Show success message
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Payment Method has been deleted successfully.",
|
||||
icon: "success",
|
||||
className: "btn btn-success",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete payment method:", error);
|
||||
MySwal.fire({
|
||||
title: "Error!",
|
||||
text: "Failed to delete payment method. 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: "Payment Method",
|
||||
dataIndex: "paymentmethod",
|
||||
render: (_, record) => {
|
||||
return <span>{record.name}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.name.length - b.name.length,
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
dataIndex: "type",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.type}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.type.length - b.type.length,
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
render: (_, record) => {
|
||||
return <span>{formatDate(record.created_at)}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.created_at.length - b.created_at.length,
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (_, record) => (
|
||||
<Tag color="#87d068">{record.is_active ? "Active" : "Inactive"}</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(fetchPaymentMethod(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) {
|
||||
handleDeletePaymentMethod(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>Payment Method</h4>
|
||||
<h6>Manage your payment methods</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(fetchPaymentMethods())}
|
||||
>
|
||||
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="paymentmethod-list-pagination"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /category list */}
|
||||
</div>
|
||||
</div>
|
||||
<AddCategoryList />
|
||||
<EditCategoryList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentMethodList;
|
||||
@ -1,324 +1,388 @@
|
||||
import React, { useState } from 'react'
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import ImageWithBasePath from '../../core/img/imagewithbasebath';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ChevronUp, Filter, PlusCircle, RotateCcw, Sliders, StopCircle, Zap } from 'feather-icons-react/build/IconComponents';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { setToogleHeader } from '../../core/redux/action';
|
||||
import Select from 'react-select';
|
||||
import { DatePicker } from 'antd';
|
||||
import AddCategoryList from '../../core/modals/inventory/addcategorylist';
|
||||
import EditCategoryList from '../../core/modals/inventory/editcategorylist';
|
||||
import withReactContent from 'sweetalert2-react-content';
|
||||
import Swal from 'sweetalert2';
|
||||
import Table from '../../core/pagination/datatable'
|
||||
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 CategoryList = () => {
|
||||
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 = useSelector((state) => state.categotylist_data);
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const dataSource = apiCategories?.length > 0 ? apiCategories : [];
|
||||
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
const toggleFilterVisibility = () => {
|
||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||
};
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const handleDateChange = (date) => {
|
||||
setSelectedDate(date);
|
||||
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]);
|
||||
|
||||
const oldandlatestvalue = [
|
||||
{ value: 'date', label: 'Sort by Date' },
|
||||
{ value: 'newest', label: 'Newest' },
|
||||
{ value: 'oldest', label: 'Oldest' },
|
||||
];
|
||||
const category = [
|
||||
{ value: 'chooseCategory', label: 'Choose Category' },
|
||||
{ value: 'laptop', label: 'Laptop' },
|
||||
{ value: 'electronics', label: 'Electronics' },
|
||||
{ value: 'shoe', label: 'Shoe' },
|
||||
];
|
||||
const status = [
|
||||
{ value: 'chooseStatus', label: 'Choose Status' },
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'inactive', label: 'Inactive' },
|
||||
];
|
||||
// Debounce search term
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
}, 500); // 500ms delay
|
||||
|
||||
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 () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
const columns = [
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
{
|
||||
title: "Category",
|
||||
dataIndex: "category",
|
||||
sorter: (a, b) => a.category.length - b.category.length,
|
||||
// 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",
|
||||
},
|
||||
{
|
||||
title: "Category Slug",
|
||||
dataIndex: "categoryslug",
|
||||
sorter: (a, b) => a.categoryslug.length - b.categoryslug.length,
|
||||
});
|
||||
} 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",
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
sorter: (a, b) => a.createdon.length - b.createdon.length,
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (text) => (
|
||||
<span className="badge badge-linesuccess">
|
||||
<Link to="#"> {text}</Link>
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.status.length - b.status.length,
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
dataIndex: 'actions',
|
||||
key: 'actions',
|
||||
render: () => (
|
||||
<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">
|
||||
<i data-feather="edit" className="feather-edit"></i>
|
||||
</Link>
|
||||
<Link className="confirm-text p-2" to="#" >
|
||||
<i data-feather="trash-2" className="feather-trash-2" onClick={showConfirmationAlert}></i>
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
},
|
||||
]
|
||||
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) {
|
||||
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: 'Deleted!',
|
||||
text: 'Your file has been deleted.',
|
||||
className: "btn btn-success",
|
||||
confirmButtonText: 'OK',
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-success',
|
||||
},
|
||||
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);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
MySwal.close();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
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"
|
||||
/>
|
||||
<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={oldandlatestvalue}
|
||||
placeholder="Newest"
|
||||
/>
|
||||
</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-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
|
||||
<Zap className="info-img" />
|
||||
<Select
|
||||
options={category}
|
||||
className="select"
|
||||
placeholder="Choose Category"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<i data-feather="calendar" className="info-img" />
|
||||
<div className="input-groupicon">
|
||||
<DatePicker
|
||||
selected={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
type="date"
|
||||
className="filterdatepicker"
|
||||
dateFormat="dd-MM-yyyy"
|
||||
placeholder='Choose Date'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<StopCircle className="info-img" />
|
||||
|
||||
<Select options={status} className="select" placeholder="Choose Status" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 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" />{" "}
|
||||
Search{" "}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div className="table-responsive">
|
||||
<Table columns={columns} dataSource={dataSource} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /product list */}
|
||||
</div>
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<AddCategoryList />
|
||||
<EditCategoryList />
|
||||
</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>
|
||||
|
||||
export default CategoryList
|
||||
<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 CategoryList;
|
||||
|
||||
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;
|
||||
@ -1,115 +1,166 @@
|
||||
import React from 'react'
|
||||
import ImageWithBasePath from '../../core/img/imagewithbasebath'
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import productsApi from "../../services/productsApi";
|
||||
import { formatRupiah } from "../../utils/currency";
|
||||
|
||||
const ProductDetail = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
<div className="page-header">
|
||||
<div className="page-title">
|
||||
<h4>Product Details</h4>
|
||||
<h6>Full details of a product</h6>
|
||||
</div>
|
||||
</div>
|
||||
{/* /add */}
|
||||
<div className="row">
|
||||
<div className="col-lg-8 col-sm-12">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="bar-code-view">
|
||||
<ImageWithBasePath src="assets/img/barcode/barcode1.png" alt="barcode" />
|
||||
<a className="printimg">
|
||||
<ImageWithBasePath src="assets/img/icons/printer.svg" alt="print" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="productdetails">
|
||||
<ul className="product-bar">
|
||||
<li>
|
||||
<h4>Product</h4>
|
||||
<h6>Macbook pro </h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Category</h4>
|
||||
<h6>Computers</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Sub Category</h4>
|
||||
<h6>None</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Brand</h4>
|
||||
<h6>None</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Unit</h4>
|
||||
<h6>Piece</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>SKU</h4>
|
||||
<h6>PT0001</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Minimum Qty</h4>
|
||||
<h6>5</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Quantity</h4>
|
||||
<h6>50</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Tax</h4>
|
||||
<h6>0.00 %</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Discount Type</h4>
|
||||
<h6>Percentage</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Price</h4>
|
||||
<h6>1500.00</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Status</h4>
|
||||
<h6>Active</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Description</h4>
|
||||
<h6>
|
||||
Lorem Ipsum is simply dummy text of the printing and
|
||||
typesetting industry. Lorem Ipsum has been the industrys
|
||||
standard dummy text ever since the 1500s,
|
||||
</h6>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-4 col-sm-12">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="slider-product-details">
|
||||
<div className="owl-carousel owl-theme product-slide">
|
||||
<div className="slider-product">
|
||||
<ImageWithBasePath src="assets/img/products/product69.jpg" alt="img" />
|
||||
<h4>macbookpro.jpg</h4>
|
||||
<h6>581kb</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /add */}
|
||||
const { id } = useParams();
|
||||
|
||||
const [currentProduct, setCurrentProduct] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProduct = async () => {
|
||||
try {
|
||||
const response = await productsApi.getProductById(id);
|
||||
setCurrentProduct(response.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching product:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (id) {
|
||||
fetchProduct();
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
<div className="page-header">
|
||||
<div className="page-title">
|
||||
<h4>Product Details</h4>
|
||||
<h6>Full details of a product</h6>
|
||||
</div>
|
||||
</div>
|
||||
{/* /add */}
|
||||
<div className="row">
|
||||
<div className="col-lg-8 col-sm-12">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="bar-code-view">
|
||||
<ImageWithBasePath
|
||||
src="assets/img/barcode/barcode1.png"
|
||||
alt="barcode"
|
||||
/>
|
||||
<a className="printimg">
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/printer.svg"
|
||||
alt="print"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="productdetails">
|
||||
<ul className="product-bar">
|
||||
<li>
|
||||
<h4>Product</h4>
|
||||
<h6>{currentProduct?.name} </h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Category</h4>
|
||||
<h6>{currentProduct?.category ?? "-"}</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>SKU</h4>
|
||||
<h6>{currentProduct?.sku ?? "-"}</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Business</h4>
|
||||
<h6>{currentProduct?.business_type ?? "-"}</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Printer</h4>
|
||||
<h6>{currentProduct?.printer_type ?? "-"}</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Price</h4>
|
||||
<h6>{formatRupiah(Number(currentProduct?.price))}</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Cost</h4>
|
||||
<h6>{formatRupiah(Number(currentProduct?.cost))}</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Status</h4>
|
||||
<h6>
|
||||
{currentProduct?.is_active ? (
|
||||
<span className="badge text-bg-success">
|
||||
Active
|
||||
</span>
|
||||
) : (
|
||||
"Inactive"
|
||||
)}
|
||||
</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Description</h4>
|
||||
<h6>{currentProduct?.description ?? "-"}</h6>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{Array.isArray(currentProduct?.variants) && (
|
||||
<div className="page-header">
|
||||
<div className="page-title">
|
||||
<h4>Variant Product Details</h4>
|
||||
<h6>Full details of a variant product</h6>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Array.isArray(currentProduct?.variants) &&
|
||||
currentProduct.variants.map((variant, index) => (
|
||||
<div key={index} className="card">
|
||||
<div className="card-body">
|
||||
<div className="productdetails">
|
||||
<ul className="product-bar">
|
||||
<li>
|
||||
<h4>Variant</h4>
|
||||
<h6>{variant?.name} </h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Price</h4>
|
||||
<h6>
|
||||
{formatRupiah(Number(variant.price_modifier))}
|
||||
</h6>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Cost</h4>
|
||||
<h6>{formatRupiah(Number(variant.cost))}</h6>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="col-lg-4 col-sm-12">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="slider-product-details">
|
||||
<div className="owl-carousel owl-theme product-slide">
|
||||
<div className="slider-product">
|
||||
<ImageWithBasePath
|
||||
src={currentProduct?.image_url}
|
||||
alt="img"
|
||||
/>
|
||||
<h4>{currentProduct?.name}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /add */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetail
|
||||
export default ProductDetail;
|
||||
|
||||
@ -1,36 +1,33 @@
|
||||
import { Select, Space } from "antd";
|
||||
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 { useEffect, useState } from "react";
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
import { Download } from "react-feather";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import Select from "react-select";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import CustomPagination from "../../components/CustomPagination";
|
||||
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,
|
||||
clearProductError,
|
||||
deleteProduct,
|
||||
clearProductError
|
||||
fetchProduct,
|
||||
fetchProducts,
|
||||
} from "../../core/redux/actions/productActions";
|
||||
import CustomPagination from '../../components/CustomPagination';
|
||||
import { all_routes } from "../../Router/all_routes";
|
||||
import categoriesApi from "../../services/categoriesApi";
|
||||
import { formatRupiah } from "../../utils/currency";
|
||||
|
||||
// Add CSS animations for beautiful UI
|
||||
const shimmerKeyframes = `
|
||||
@ -62,11 +59,16 @@ const shimmerKeyframes = `
|
||||
`;
|
||||
|
||||
// Inject CSS into head if not already present
|
||||
if (typeof document !== 'undefined' && !document.getElementById('beautiful-pagination-styles')) {
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.id = 'beautiful-pagination-styles';
|
||||
styleSheet.type = 'text/css';
|
||||
styleSheet.innerText = shimmerKeyframes + `
|
||||
if (
|
||||
typeof document !== "undefined" &&
|
||||
!document.getElementById("beautiful-pagination-styles")
|
||||
) {
|
||||
const styleSheet = document.createElement("style");
|
||||
styleSheet.id = "beautiful-pagination-styles";
|
||||
styleSheet.type = "text/css";
|
||||
styleSheet.innerText =
|
||||
shimmerKeyframes +
|
||||
`
|
||||
/* Hide all Ant Design pagination elements */
|
||||
.ant-pagination,
|
||||
.ant-pagination-item,
|
||||
@ -433,40 +435,24 @@ const ProductList = () => {
|
||||
totalProducts,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage
|
||||
currentPage: reduxCurrentPage,
|
||||
} = 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 dataSource = apiProducts?.length > 0 ? apiProducts : [];
|
||||
|
||||
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: '',
|
||||
category: '',
|
||||
subCategory: '',
|
||||
brand: '',
|
||||
priceRange: ''
|
||||
});
|
||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [category, setCategory] = useState(null);
|
||||
|
||||
// Debounced search term
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
const toggleFilterVisibility = () => {
|
||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||
};
|
||||
const [categoryOptions, setCategoryOptions] = useState([]);
|
||||
|
||||
const route = all_routes;
|
||||
|
||||
@ -479,29 +465,51 @@ const ProductList = () => {
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const response = await categoriesApi.getAllCategories();
|
||||
const categories = response?.data?.categories;
|
||||
const formattedCategory = categories.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
}));
|
||||
|
||||
formattedCategory.unshift({ value: "", label: "All" });
|
||||
|
||||
setCategoryOptions(formattedCategory);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch categories", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadCategories();
|
||||
}, []);
|
||||
|
||||
// Fetch products when debounced search term or pagination changes
|
||||
useEffect(() => {
|
||||
const loadProducts = async () => {
|
||||
try {
|
||||
const searchParams = {
|
||||
Page: currentPage,
|
||||
PageSize: pageSize,
|
||||
SearchTerm: debouncedSearchTerm || ''
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
search: debouncedSearchTerm || "",
|
||||
category_id: category,
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== '')
|
||||
Object.entries(searchParams).filter(([, value]) => value !== "")
|
||||
);
|
||||
|
||||
await dispatch(fetchProducts(cleanParams));
|
||||
} catch (error) {
|
||||
console.error('Failed to load products:', error);
|
||||
console.error("Failed to load products:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadProducts();
|
||||
}, [dispatch, currentPage, pageSize, debouncedSearchTerm]);
|
||||
}, [dispatch, currentPage, pageSize, debouncedSearchTerm, category]);
|
||||
|
||||
// Handle product deletion
|
||||
const handleDeleteProduct = async (productId) => {
|
||||
@ -518,7 +526,7 @@ const ProductList = () => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to delete product:', error);
|
||||
console.error("Failed to delete product:", error);
|
||||
MySwal.fire({
|
||||
title: "Error!",
|
||||
text: "Failed to delete product. Please try again.",
|
||||
@ -542,74 +550,12 @@ const ProductList = () => {
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
|
||||
// Dispatch action to fetch products for the new page
|
||||
const searchParams = {
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
SearchTerm: debouncedSearchTerm || ''
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== '')
|
||||
);
|
||||
|
||||
dispatch(fetchProducts(cleanParams));
|
||||
};
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = (newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1); // Reset to first page when changing page size
|
||||
|
||||
// Dispatch action to fetch products with new page size
|
||||
const searchParams = {
|
||||
Page: 1,
|
||||
PageSize: newPageSize,
|
||||
SearchTerm: debouncedSearchTerm || ''
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== '')
|
||||
);
|
||||
|
||||
dispatch(fetchProducts(cleanParams));
|
||||
};
|
||||
|
||||
// Handle filter value changes
|
||||
const handleFilterChange = (filterType, value) => {
|
||||
setFilterValues(prev => ({
|
||||
...prev,
|
||||
[filterType]: value
|
||||
}));
|
||||
};
|
||||
|
||||
// Handle search with filters
|
||||
const handleSearchWithFilters = () => {
|
||||
setCurrentPage(1); // Reset to first page when searching
|
||||
|
||||
// Combine search term with filter values
|
||||
const searchParams = {
|
||||
Page: 1,
|
||||
PageSize: pageSize,
|
||||
SearchTerm: debouncedSearchTerm || '',
|
||||
// Map filter values to API expected parameters
|
||||
ProductName: filterValues.product || '',
|
||||
Category: filterValues.category || '',
|
||||
SubCategory: filterValues.subCategory || '',
|
||||
Brand: filterValues.brand || '',
|
||||
PriceRange: filterValues.priceRange || ''
|
||||
};
|
||||
|
||||
// Remove empty parameters to clean up API call
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== '')
|
||||
);
|
||||
|
||||
console.log('Search with filters (clean params):', cleanParams);
|
||||
dispatch(fetchProducts(cleanParams));
|
||||
};
|
||||
|
||||
// Calculate pagination info
|
||||
@ -617,85 +563,95 @@ const ProductList = () => {
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
// Debug logs removed for production
|
||||
|
||||
// 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" },
|
||||
{ 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" },
|
||||
];
|
||||
// Removed unused select option arrays since we're using simple inputs now
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Sản phẩm",
|
||||
title: "SKU",
|
||||
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: "Product",
|
||||
dataIndex: "product",
|
||||
render: (text, record) => (
|
||||
<span className="productimgname">
|
||||
<Link to="/profile" className="product-img stock-img">
|
||||
<Link className="product-img stock-img">
|
||||
<ImageWithBasePath
|
||||
alt={record.name || text || "Product"}
|
||||
src={record.productImage || record.image || record.img}
|
||||
alt={"jpg"}
|
||||
src={record.image_url}
|
||||
/>
|
||||
</Link>
|
||||
<Link to="/profile">{text}</Link>
|
||||
<Link className="fw-medium text-black">{record.name}</Link>
|
||||
</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: "Danh mục",
|
||||
title: "Category",
|
||||
dataIndex: "category",
|
||||
render: (_, record) => {
|
||||
const category = record.category || record.categoryName || '-';
|
||||
const category = record.category || record.categoryName || "-";
|
||||
return <span>{category}</span>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const catA = a.category || a.categoryName || '';
|
||||
const catB = b.category || b.categoryName || '';
|
||||
const catA = a.category || a.categoryName || "";
|
||||
const catB = b.category || b.categoryName || "";
|
||||
return catA.length - catB.length;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "Thương hiệu",
|
||||
dataIndex: "brand",
|
||||
title: "Business",
|
||||
dataIndex: "business",
|
||||
render: (_, record) => {
|
||||
const brand = record.brand || record.brandName || '-';
|
||||
const brand = record.business_type || record.brandName || "-";
|
||||
return <span>{brand}</span>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const brandA = a.brand || a.brandName || '';
|
||||
const brandB = b.brand || b.brandName || '';
|
||||
const brandA = a.brand || a.brandName || "";
|
||||
const brandB = b.brand || b.brandName || "";
|
||||
return brandA.length - brandB.length;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Giá",
|
||||
title: "Printer",
|
||||
dataIndex: "printer",
|
||||
render: (_, record) => {
|
||||
const printer = record.printer_type || "-";
|
||||
return <span>{printer}</span>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const brandA = a.brand || a.brandName || "";
|
||||
const brandB = b.brand || b.brandName || "";
|
||||
return brandA.length - brandB.length;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Price",
|
||||
dataIndex: "price",
|
||||
render: (_, record) => {
|
||||
const price = record.price || record.salePrice || record.unitPrice || 0;
|
||||
return <span>${Number(price).toFixed(2)}</span>;
|
||||
return <span>{formatRupiah(Number(price))}</span>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
|
||||
@ -704,54 +660,30 @@ const ProductList = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Đơn vị",
|
||||
dataIndex: "unit",
|
||||
title: "Cost",
|
||||
dataIndex: "cost",
|
||||
render: (_, record) => {
|
||||
const unit = record.unit || record.unitOfMeasure || '-';
|
||||
return <span>{unit}</span>;
|
||||
const cost = record.cost || 0;
|
||||
return <span>{formatRupiah(Number(cost))}</span>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const unitA = a.unit || a.unitOfMeasure || '';
|
||||
const unitB = b.unit || b.unitOfMeasure || '';
|
||||
return unitA.length - unitB.length;
|
||||
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
|
||||
const priceB = Number(b.price || b.salePrice || b.unitPrice || 0);
|
||||
return priceA - priceB;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Qty",
|
||||
dataIndex: "qty",
|
||||
render: (_, record) => {
|
||||
// Try multiple possible field names for quantity
|
||||
const quantity = record.qty || record.quantity || record.stock || record.stockQuantity || 0;
|
||||
return <span>{quantity}</span>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const qtyA = a.qty || a.quantity || a.stock || a.stockQuantity || 0;
|
||||
const qtyB = b.qty || b.quantity || b.stock || b.stockQuantity || 0;
|
||||
return Number(qtyA) - Number(qtyB);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "Người tạo",
|
||||
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: "Thao tác",
|
||||
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
|
||||
className="me-2 p-2"
|
||||
to={`${route.productdetails}/${record.id || record.key}`}
|
||||
>
|
||||
<Eye className="feather-edit" />
|
||||
</Link>
|
||||
<Link
|
||||
className="me-2 p-2"
|
||||
@ -795,8 +727,6 @@ const ProductList = () => {
|
||||
];
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
// Removed showConfirmationAlert as we handle confirmation inline
|
||||
|
||||
const renderTooltip = (props) => (
|
||||
<Tooltip id="pdf-tooltip" {...props}>
|
||||
Pdf
|
||||
@ -822,14 +752,15 @@ const ProductList = () => {
|
||||
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>Danh sách sản phẩm</h4>
|
||||
<h6>Quản lý sản phẩm</h6>
|
||||
<h4>Product List</h4>
|
||||
<h6>Manage your products</h6>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="table-top-head">
|
||||
@ -884,18 +815,17 @@ const ProductList = () => {
|
||||
<div className="page-btn">
|
||||
<Link to={route.addproduct} className="btn btn-added">
|
||||
<PlusCircle className="me-2 iconsize" />
|
||||
Thêm mới
|
||||
Add New Product
|
||||
</Link>
|
||||
</div>
|
||||
<div className="page-btn import">
|
||||
<Link
|
||||
to="#"
|
||||
className="btn btn-added color"
|
||||
className="btn btn-outline-primary rounded-2"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#view-notes"
|
||||
>
|
||||
<Download className="me-2" />
|
||||
Nhập sản phẩm
|
||||
<Download className="me-2 iconsize" />
|
||||
Import Product
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -917,291 +847,28 @@ const ProductList = () => {
|
||||
</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" />
|
||||
|
||||
<Space warp>
|
||||
<Select
|
||||
className="select"
|
||||
options={options}
|
||||
placeholder="14 09 23"
|
||||
style={{ height: 36, width: 100 }}
|
||||
placeholder={"Category"}
|
||||
options={categoryOptions}
|
||||
value={
|
||||
categoryOptions.find(
|
||||
(option) => option.value === category
|
||||
) || null
|
||||
}
|
||||
onChange={(selectedOption) => setCategory(selectedOption)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
style={{ height: 36 }}
|
||||
defaultValue={options[0]?.value}
|
||||
options={options}
|
||||
/>
|
||||
</Space>
|
||||
</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 custom-dropdown">
|
||||
<select
|
||||
className="form-control custom-select"
|
||||
value={filterValues.product}
|
||||
onChange={(e) => handleFilterChange('product', e.target.value)}
|
||||
style={{
|
||||
paddingLeft: '40px',
|
||||
background: '#2c3e50',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '6px',
|
||||
height: '40px',
|
||||
appearance: 'none',
|
||||
backgroundImage: 'url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'%23ffffff\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'%3e%3cpolyline points=\'6,9 12,15 18,9\'%3e%3c/polyline%3e%3c/svg%3e")',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'right 12px center',
|
||||
backgroundSize: '16px',
|
||||
paddingRight: '40px'
|
||||
}}
|
||||
>
|
||||
<option value="">Choose Product</option>
|
||||
<option value="lenovo">Lenovo 3rd Generation</option>
|
||||
<option value="nike">Nike Jordan</option>
|
||||
<option value="apple">Apple iPhone</option>
|
||||
<option value="samsung">Samsung Galaxy</option>
|
||||
</select>
|
||||
<Box
|
||||
className="info-img"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '12px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#3498db',
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks custom-dropdown">
|
||||
<select
|
||||
className="form-control custom-select"
|
||||
value={filterValues.category}
|
||||
onChange={(e) => handleFilterChange('category', e.target.value)}
|
||||
style={{
|
||||
paddingLeft: '40px',
|
||||
background: '#2c3e50',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '6px',
|
||||
height: '40px',
|
||||
appearance: 'none',
|
||||
backgroundImage: 'url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'%23ffffff\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'%3e%3cpolyline points=\'6,9 12,15 18,9\'%3e%3c/polyline%3e%3c/svg%3e")',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'right 12px center',
|
||||
backgroundSize: '16px',
|
||||
paddingRight: '40px'
|
||||
}}
|
||||
>
|
||||
<option value="">Choose Category</option>
|
||||
<option value="laptop">Laptop</option>
|
||||
<option value="phone">Phone</option>
|
||||
<option value="shoe">Shoe</option>
|
||||
<option value="clothing">Clothing</option>
|
||||
</select>
|
||||
<StopCircle
|
||||
className="info-img"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '12px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#e74c3c',
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks custom-dropdown">
|
||||
<select
|
||||
className="form-control custom-select"
|
||||
value={filterValues.subCategory}
|
||||
onChange={(e) => handleFilterChange('subCategory', e.target.value)}
|
||||
style={{
|
||||
paddingLeft: '40px',
|
||||
background: '#2c3e50',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '6px',
|
||||
height: '40px',
|
||||
appearance: 'none',
|
||||
backgroundImage: 'url("data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' fill=\'white\' viewBox=\'0 0 16 16\'%3e%3cpath d=\'M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\'/%3e%3c/svg%3e")',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'right 12px center',
|
||||
backgroundSize: '12px',
|
||||
paddingRight: '40px'
|
||||
}}
|
||||
>
|
||||
<option value="">Choose Sub Category</option>
|
||||
<option value="computers">Computers</option>
|
||||
<option value="accessories">Accessories</option>
|
||||
<option value="sports">Sports</option>
|
||||
<option value="electronics">Electronics</option>
|
||||
</select>
|
||||
<GitMerge
|
||||
className="info-img"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '12px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#2ecc71',
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks custom-dropdown">
|
||||
<select
|
||||
className="form-control custom-select"
|
||||
value={filterValues.brand}
|
||||
onChange={(e) => handleFilterChange('brand', e.target.value)}
|
||||
style={{
|
||||
paddingLeft: '40px',
|
||||
background: '#2c3e50',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '6px',
|
||||
height: '40px',
|
||||
appearance: 'none',
|
||||
backgroundImage: 'url("data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' fill=\'white\' viewBox=\'0 0 16 16\'%3e%3cpath d=\'M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\'/%3e%3c/svg%3e")',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'right 12px center',
|
||||
backgroundSize: '12px',
|
||||
paddingRight: '40px'
|
||||
}}
|
||||
>
|
||||
<option value="">Choose Brand</option>
|
||||
<option value="lenovo">Lenovo</option>
|
||||
<option value="nike">Nike</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="samsung">Samsung</option>
|
||||
<option value="adidas">Adidas</option>
|
||||
</select>
|
||||
<StopCircle
|
||||
className="info-img"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '12px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#f39c12',
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks custom-dropdown">
|
||||
<select
|
||||
className="form-control custom-select"
|
||||
value={filterValues.priceRange}
|
||||
onChange={(e) => handleFilterChange('priceRange', e.target.value)}
|
||||
style={{
|
||||
paddingLeft: '40px',
|
||||
background: '#2c3e50',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '6px',
|
||||
height: '40px',
|
||||
appearance: 'none',
|
||||
backgroundImage: 'url("data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' fill=\'white\' viewBox=\'0 0 16 16\'%3e%3cpath d=\'M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\'/%3e%3c/svg%3e")',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'right 12px center',
|
||||
backgroundSize: '12px',
|
||||
paddingRight: '40px'
|
||||
}}
|
||||
>
|
||||
<option value="">Choose Price Range</option>
|
||||
<option value="0-100">$0 - $100</option>
|
||||
<option value="100-500">$100 - $500</option>
|
||||
<option value="500-1000">$500 - $1,000</option>
|
||||
<option value="1000+">$1,000+</option>
|
||||
</select>
|
||||
<i
|
||||
className="fas fa-money-bill info-img"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '12px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#9b59b6',
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<button
|
||||
className="btn btn-filters ms-auto"
|
||||
onClick={handleSearchWithFilters}
|
||||
type="button"
|
||||
style={{
|
||||
background: 'linear-gradient(45deg, #3498db, #2980b9)',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '6px',
|
||||
padding: '8px 16px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.background = 'linear-gradient(45deg, #2980b9, #3498db)';
|
||||
e.target.style.transform = 'translateY(-2px)';
|
||||
e.target.style.boxShadow = '0 4px 12px rgba(52, 152, 219, 0.4)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.target.style.background = 'linear-gradient(45deg, #3498db, #2980b9)';
|
||||
e.target.style.transform = 'translateY(0)';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
>
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search"
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
|
||||
<div className="table-responsive">
|
||||
{loading ? (
|
||||
<div className="text-center p-4">
|
||||
|
||||
@ -1,16 +1,48 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import ImageWithBasePath from "../../../core/img/imagewithbasebath";
|
||||
import { Link } from "react-router-dom";
|
||||
import { all_routes } from "../../../Router/all_routes";
|
||||
import authApi from "../../../services/authApi";
|
||||
|
||||
const Signin = () => {
|
||||
const route = all_routes;
|
||||
const navigate = useNavigate();
|
||||
// const dispatch = useDispatch();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: ''
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
try {
|
||||
await authApi.login(formData)
|
||||
|
||||
navigate(route.dashboard);
|
||||
} catch (error) {
|
||||
setError(error.message || 'Login failed');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="main-wrapper">
|
||||
<div className="account-content">
|
||||
<div className="login-wrapper bg-img">
|
||||
<div className="login-content">
|
||||
<form action="index">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="login-userset">
|
||||
<div className="login-logo logo-normal">
|
||||
<ImageWithBasePath src="assets/img/logo.png" alt="img" />
|
||||
@ -24,10 +56,22 @@ const Signin = () => {
|
||||
Access the Dreamspos panel using your email and passcode.
|
||||
</h4>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div className="form-login mb-3">
|
||||
<label className="form-label">Email Address</label>
|
||||
<div className="form-addons">
|
||||
<input type="text" className="form- control" />
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
className="form-control"
|
||||
required
|
||||
/>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/mail.svg"
|
||||
alt="img"
|
||||
@ -39,7 +83,11 @@ const Signin = () => {
|
||||
<div className="pass-group">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
className="pass-input form-control"
|
||||
required
|
||||
/>
|
||||
<span className="fas toggle-password fa-eye-slash" />
|
||||
</div>
|
||||
@ -63,9 +111,13 @@ const Signin = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-login">
|
||||
<Link to={route.dashboard} className="btn btn-login">
|
||||
Sign In
|
||||
</Link>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-login"
|
||||
disabled={authState.loading}
|
||||
>
|
||||
{authState.loading ? 'Signing In...' : 'Sign In'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="signinform">
|
||||
<h4>
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import React from "react";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const Profile = () => {
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
return (
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
@ -27,17 +30,19 @@ const Profile = () => {
|
||||
/>
|
||||
<div className="profileupload">
|
||||
<input type="file" id="imgInp" />
|
||||
<Link to="#">
|
||||
<ImageWithBasePath src="assets/img/icons/edit-set.svg" alt="img" />
|
||||
<Link to="#">
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/edit-set.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="profile-contentname">
|
||||
<h2>William Castillo</h2>
|
||||
<h2>{user?.name}</h2>
|
||||
<h4>Updates Your Photo and Personal Details.</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
@ -68,6 +73,7 @@ const Profile = () => {
|
||||
type="email"
|
||||
className="form-control"
|
||||
defaultValue="william@example.com"
|
||||
value={user?.email}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -100,10 +106,10 @@ const Profile = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Link to="#" className="btn btn-submit me-2">
|
||||
<Link to="#" className="btn btn-submit me-2">
|
||||
Submit
|
||||
</Link>
|
||||
<Link to="#" className="btn btn-cancel">
|
||||
<Link to="#" className="btn btn-cancel">
|
||||
Cancel
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@ -1,115 +1,181 @@
|
||||
import React, { useState } from "react";
|
||||
import Breadcrumbs from "../../core/breadcrumbs";
|
||||
import { Filter, Sliders } from "react-feather";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import Select from "react-select";
|
||||
import { Link } from "react-router-dom";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { Tag } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { Archive, Box, Calendar, User } from "react-feather";
|
||||
import ManageStockModal from "../../core/modals/stocks/managestockModal";
|
||||
import { Edit, Trash2 } from "react-feather";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import CustomPagination from "../../components/CustomPagination";
|
||||
import Breadcrumbs from "../../core/breadcrumbs";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import ManageStockModal from "../../core/modals/stocks/managestockModal";
|
||||
import Table from "../../core/pagination/datatable";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
deleteInventory,
|
||||
fetchInventories,
|
||||
INVENTORY_ACTIONS,
|
||||
} from "../../core/redux/actions/inventoryActions";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
const Managestock = () => {
|
||||
const data = useSelector((state) => state.managestockdata);
|
||||
const {
|
||||
inventories: apiInventories,
|
||||
loading,
|
||||
error,
|
||||
totalInventories,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage,
|
||||
} = useSelector((state) => state.inventories);
|
||||
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const dispatch = useDispatch();
|
||||
const dataSource = apiInventories?.length > 0 ? apiInventories : [];
|
||||
|
||||
const toggleFilterVisibility = () => {
|
||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||
};
|
||||
const handleDateChange = (date) => {
|
||||
setSelectedDate(date);
|
||||
const [params, setParams] = useState({
|
||||
page: reduxCurrentPage || 1,
|
||||
limit: reduxPageSize || 10,
|
||||
});
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadInventories = async () => {
|
||||
try {
|
||||
const receivedParams = {
|
||||
page: params.page,
|
||||
limit: params.limit,
|
||||
search: debouncedSearchTerm || "",
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(receivedParams).filter(([, value]) => value !== "")
|
||||
);
|
||||
|
||||
await dispatch(fetchInventories(cleanParams));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch orders", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadInventories();
|
||||
}, [dispatch, params, debouncedSearchTerm]);
|
||||
|
||||
// Debounce search term
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
handleSetParams("page", 1);
|
||||
}, 500); // 500ms delay
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
const handleSetParams = (key, value) => {
|
||||
setParams({
|
||||
...params,
|
||||
[key]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const options = [
|
||||
{ value: "sortByDate", label: "Sort by Date" },
|
||||
{ value: "140923", label: "14 09 23" },
|
||||
{ value: "110923", label: "11 09 23" },
|
||||
];
|
||||
const handleSearch = (e) => {
|
||||
const value = e.target.value;
|
||||
setSearchTerm(value);
|
||||
};
|
||||
|
||||
const warehouseOptions = [
|
||||
{ value: "Choose Warehouse", label: "Choose Warehouse" },
|
||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
||||
{ value: "Quaint Warehouse", label: "Quaint Warehouse" },
|
||||
{ value: "Traditional Warehouse", label: "Traditional Warehouse" },
|
||||
{ value: "Cool Warehouse", label: "Cool Warehouse" },
|
||||
];
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
handleSetParams("page", page);
|
||||
};
|
||||
|
||||
const productOptions = [
|
||||
{ value: "Choose Product", label: "Choose Product" },
|
||||
{ value: "Nike Jordan", label: "Nike Jordan" },
|
||||
{ value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" },
|
||||
{ value: "Amazon Echo Dot", label: "Amazon Echo Dot" },
|
||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
||||
];
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = (newPageSize) => {
|
||||
handleSetParams("limit", newPageSize);
|
||||
handleSetParams("page", 1);
|
||||
};
|
||||
|
||||
const personOptions = [
|
||||
{ value: "Choose Person", label: "Choose Person" },
|
||||
{ value: "Steven", label: "Steven" },
|
||||
{ value: "Gravely", label: "Gravely" },
|
||||
];
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
await dispatch(deleteInventory(id));
|
||||
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Your stock has been deleted.",
|
||||
className: "btn btn-success",
|
||||
confirmButtonText: "OK",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete inventory", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate pagination info
|
||||
const totalRecords = totalInventories || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Warehouse",
|
||||
dataIndex: "Warehouse",
|
||||
title: "Outlet",
|
||||
dataIndex: "outlet",
|
||||
render: (_, record) => <span>{record.outlet || "-"}</span>,
|
||||
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
||||
},
|
||||
{
|
||||
title: "Shop",
|
||||
dataIndex: "Shop",
|
||||
sorter: (a, b) => a.Shop.length - b.Shop.length,
|
||||
},
|
||||
{
|
||||
title: "Product",
|
||||
dataIndex: "Product",
|
||||
render: (text, record) => (
|
||||
<span className="userimgname">
|
||||
<Link to="#" className="product-img">
|
||||
<ImageWithBasePath alt="img" src={record.Product.Image} />
|
||||
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
|
||||
</Link>
|
||||
<Link to="#">{record.Product.Name}</Link>
|
||||
<Link to="#">{record.product_name || ""}</Link>
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Date",
|
||||
dataIndex: "Date",
|
||||
title: "Qty",
|
||||
dataIndex: "qty",
|
||||
render: (_, record) => <span>{record.quantity || 0}</span>,
|
||||
sorter: (a, b) => a.Email.length - b.Email.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Person",
|
||||
dataIndex: "Person",
|
||||
render: (text, record) => (
|
||||
<span className="userimgname">
|
||||
<Link to="#" className="product-img">
|
||||
<ImageWithBasePath alt="img" src={record.Person.Image} />
|
||||
</Link>
|
||||
<Link to="#">{record.Person.Name}</Link>
|
||||
</span>
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (_, record) => (
|
||||
<Tag color={record.is_low_stock ? "red" : "green"}>
|
||||
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
|
||||
</Tag>
|
||||
),
|
||||
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Quantity",
|
||||
dataIndex: "Quantity",
|
||||
title: "Record Level",
|
||||
dataIndex: "recordlevel",
|
||||
render: (_, record) => <span>{record.record_level || "-"}</span>,
|
||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||
},
|
||||
{
|
||||
title: "Updated Date",
|
||||
dataIndex: "updateddate",
|
||||
render: (_, record) => (
|
||||
<span>{formatDate(record.updated_at) || "-"}</span>
|
||||
),
|
||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Action",
|
||||
dataIndex: "action",
|
||||
render: () => (
|
||||
render: (_, record) => (
|
||||
<td className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
<div className="input-block add-lists"></div>
|
||||
@ -119,6 +185,12 @@ const Managestock = () => {
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-units"
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: INVENTORY_ACTIONS.FETCH_INVENTORY_SUCCESS,
|
||||
payload: { data: record },
|
||||
})
|
||||
}
|
||||
>
|
||||
<Edit className="feather-edit" />
|
||||
</Link>
|
||||
@ -126,7 +198,7 @@ const Managestock = () => {
|
||||
<Link
|
||||
className="confirm-text p-2"
|
||||
to="#"
|
||||
onClick={showConfirmationAlert}
|
||||
onClick={() => showConfirmationAlert(record.id)}
|
||||
>
|
||||
<Trash2 className="feather-trash-2" />
|
||||
</Link>
|
||||
@ -139,7 +211,7 @@ const Managestock = () => {
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
const showConfirmationAlert = () => {
|
||||
const showConfirmationAlert = (id) => {
|
||||
MySwal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "You won't be able to revert this!",
|
||||
@ -150,20 +222,13 @@ const Managestock = () => {
|
||||
cancelButtonText: "Cancel",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Your file has been deleted.",
|
||||
className: "btn btn-success",
|
||||
confirmButtonText: "OK",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
handleDelete(id);
|
||||
} else {
|
||||
MySwal.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
@ -182,117 +247,58 @@ const Managestock = () => {
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
className="form-control form-control-sm formsearch"
|
||||
value={searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Link to className="btn btn-searchset">
|
||||
<i data-feather="search" className="feather-search" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="search-path">
|
||||
<Link
|
||||
className={`btn btn-filter ${
|
||||
isFilterVisible ? "setclose" : ""
|
||||
}`}
|
||||
id="filter_search"
|
||||
>
|
||||
<Filter
|
||||
className="filter-icon"
|
||||
onClick={toggleFilterVisibility}
|
||||
/>
|
||||
<span onClick={toggleFilterVisibility}>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/closes.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="form-sort stylewidth">
|
||||
<Sliders className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select "
|
||||
options={options}
|
||||
placeholder="Sort by Date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div
|
||||
className={`card${isFilterVisible ? " visible" : ""}`}
|
||||
id="filter_inputs"
|
||||
style={{ display: isFilterVisible ? "block" : "none" }}
|
||||
>
|
||||
<div className="card-body pb-0">
|
||||
<div className="row">
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Archive className="info-img" />
|
||||
<Select
|
||||
className="select"
|
||||
options={warehouseOptions}
|
||||
placeholder="Choose Warehouse"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Box className="info-img" />
|
||||
<Select
|
||||
className="select"
|
||||
options={productOptions}
|
||||
placeholder="Choose Product"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Calendar className="info-img" />
|
||||
<div className="input-groupicon">
|
||||
<DatePicker
|
||||
selected={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
dateFormat="dd/MM/yyyy"
|
||||
placeholderText="Choose Date"
|
||||
className="datetimepicker"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<User className="info-img" />
|
||||
<Select
|
||||
className="select"
|
||||
options={personOptions}
|
||||
placeholder="Choose Person"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-4 col-sm-6 col-12 ms-auto">
|
||||
<div className="input-blocks">
|
||||
<a className="btn btn-filters ms-auto">
|
||||
{" "}
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search"
|
||||
/>{" "}
|
||||
Search{" "}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div className="table-responsive">
|
||||
<Table
|
||||
className="table datanew"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey={(record) => record.id}
|
||||
// pagination={true}
|
||||
/>
|
||||
{loading ? (
|
||||
<div className="text-center p-4">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p className="mt-2">Loading products...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<strong>Error:</strong> {error}
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger ms-2"
|
||||
onClick={() => dispatch(fetchInventories())}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
className="table datanew"
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
rowKey={(record) => record.id}
|
||||
/>
|
||||
|
||||
<CustomPagination
|
||||
currentPage={params.page}
|
||||
pageSize={params.limit}
|
||||
totalCount={totalRecords}
|
||||
totalPages={actualTotalPages}
|
||||
loading={loading}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showInfo={true}
|
||||
showPageSizeSelector={true}
|
||||
compact={false}
|
||||
className="product-list-pagination"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,118 +1,153 @@
|
||||
import React, { useState } from "react";
|
||||
import Breadcrumbs from "../../core/breadcrumbs";
|
||||
import { Filter, Sliders } from "react-feather";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import Select from "react-select";
|
||||
import { Link } from "react-router-dom";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { useEffect, useState } from "react";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { Archive, Box, Calendar, User, Edit, Trash2 } from "react-feather";
|
||||
import { Edit, Trash2 } from "react-feather";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Table from "../../core/pagination/datatable";
|
||||
import CustomPagination from "../../components/CustomPagination";
|
||||
import Breadcrumbs from "../../core/breadcrumbs";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import StockadjustmentModal from "../../core/modals/stocks/stockadjustmentModal";
|
||||
import { useSelector } from "react-redux";
|
||||
import Table from "../../core/pagination/datatable";
|
||||
import { fetchInventories } from "../../core/redux/actions/inventoryActions";
|
||||
import { formatDate } from "../../utils/date";
|
||||
import { Tag } from "antd";
|
||||
|
||||
const StockAdjustment = () => {
|
||||
const data = useSelector((state) => state.managestockdata);
|
||||
const {
|
||||
inventories: apiInventories,
|
||||
loading,
|
||||
error,
|
||||
totalInventories,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage,
|
||||
} = useSelector((state) => state.inventories);
|
||||
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const dispatch = useDispatch();
|
||||
const dataSource = apiInventories?.length > 0 ? apiInventories : [];
|
||||
|
||||
const toggleFilterVisibility = () => {
|
||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||
const [params, setParams] = useState({
|
||||
page: reduxCurrentPage || 1,
|
||||
limit: reduxPageSize || 10,
|
||||
});
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadInventories = async () => {
|
||||
try {
|
||||
const receivedParams = {
|
||||
page: params.page,
|
||||
limit: params.limit,
|
||||
search: debouncedSearchTerm || "",
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(receivedParams).filter(([, value]) => value !== "")
|
||||
);
|
||||
|
||||
await dispatch(fetchInventories(cleanParams));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch orders", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadInventories();
|
||||
}, [dispatch, params, debouncedSearchTerm]);
|
||||
|
||||
// Debounce search term
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
handleSetParams("page", 1);
|
||||
}, 500); // 500ms delay
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
const handleSetParams = (key, value) => {
|
||||
setParams({
|
||||
...params,
|
||||
[key]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDateChange = (date) => {
|
||||
setSelectedDate(date);
|
||||
const handleSearch = (e) => {
|
||||
const value = e.target.value;
|
||||
setSearchTerm(value);
|
||||
};
|
||||
const options = [
|
||||
{ value: "sortByDate", label: "Sort by Date" },
|
||||
{ value: "140923", label: "14 09 23" },
|
||||
{ value: "110923", label: "11 09 23" },
|
||||
];
|
||||
|
||||
const warehouseOptions = [
|
||||
{ value: "Choose Warehouse", label: "Choose Warehouse" },
|
||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
||||
{ value: "Quaint Warehouse", label: "Quaint Warehouse" },
|
||||
{ value: "Traditional Warehouse", label: "Traditional Warehouse" },
|
||||
{ value: "Cool Warehouse", label: "Cool Warehouse" },
|
||||
];
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
handleSetParams("page", page);
|
||||
};
|
||||
|
||||
const productOptions = [
|
||||
{ value: "Choose Product", label: "Choose Product" },
|
||||
{ value: "Nike Jordan", label: "Nike Jordan" },
|
||||
{ value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" },
|
||||
{ value: "Amazon Echo Dot", label: "Amazon Echo Dot" },
|
||||
{ value: "Lobar Handy", label: "Lobar Handy" },
|
||||
];
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = (newPageSize) => {
|
||||
handleSetParams("limit", newPageSize);
|
||||
handleSetParams("page", 1);
|
||||
};
|
||||
|
||||
const personOptions = [
|
||||
{ value: "Choose Person", label: "Choose Person" },
|
||||
{ value: "Steven", label: "Steven" },
|
||||
{ value: "Gravely", label: "Gravely" },
|
||||
];
|
||||
// Calculate pagination info
|
||||
const totalRecords = totalInventories || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Warehouse",
|
||||
dataIndex: "Warehouse",
|
||||
title: "Outlet",
|
||||
dataIndex: "outlet",
|
||||
render: (_, record) => <span>{record.outlet || "-"}</span>,
|
||||
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
||||
},
|
||||
{
|
||||
title: "Shop",
|
||||
dataIndex: "Shop",
|
||||
sorter: (a, b) => a.Shop.length - b.Shop.length,
|
||||
},
|
||||
{
|
||||
title: "Product",
|
||||
dataIndex: "Product",
|
||||
render: (text, record) => (
|
||||
<span className="userimgname">
|
||||
<Link to="#" className="product-img">
|
||||
<ImageWithBasePath alt="img" src={record.Product.Image} />
|
||||
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
|
||||
</Link>
|
||||
<Link to="#">{record.Product.Name}</Link>
|
||||
<Link to="#">{record.product_name || ""}</Link>
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Date",
|
||||
dataIndex: "Date",
|
||||
title: "Qty",
|
||||
dataIndex: "qty",
|
||||
render: (_, record) => <span>{record.quantity || 0}</span>,
|
||||
sorter: (a, b) => a.Email.length - b.Email.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Person",
|
||||
dataIndex: "Person",
|
||||
render: (text, record) => (
|
||||
<span className="userimgname">
|
||||
<Link to="#" className="product-img">
|
||||
<ImageWithBasePath alt="img" src={record.Person.Image} />
|
||||
</Link>
|
||||
<Link to="#">{record.Person.Name}</Link>
|
||||
</span>
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (_, record) => (
|
||||
<Tag color={record.is_low_stock ? "red" : "green"}>
|
||||
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
|
||||
</Tag>
|
||||
),
|
||||
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Notes",
|
||||
// dataIndex: "Quantity",
|
||||
render: () => (
|
||||
<Link
|
||||
to="#"
|
||||
className="view-note"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#view-notes"
|
||||
>
|
||||
View Note
|
||||
</Link>
|
||||
title: "Record Level",
|
||||
dataIndex: "recordlevel",
|
||||
render: (_, record) => <span>{record.record_level || "-"}</span>,
|
||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||
},
|
||||
{
|
||||
title: "Updated Date",
|
||||
dataIndex: "updateddate",
|
||||
render: (_, record) => (
|
||||
<span>{formatDate(record.updated_at) || "-"}</span>
|
||||
),
|
||||
sorter: (a, b) => a.Notes.length - b.Notes.length,
|
||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||
},
|
||||
|
||||
{
|
||||
@ -191,115 +226,58 @@ const StockAdjustment = () => {
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
className="form-control form-control-sm formsearch"
|
||||
name="search"
|
||||
value={searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Link to className="btn btn-searchset">
|
||||
<i data-feather="search" className="feather-search" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="search-path">
|
||||
<Link
|
||||
className={`btn btn-filter ${
|
||||
isFilterVisible ? "setclose" : ""
|
||||
}`}
|
||||
id="filter_search"
|
||||
>
|
||||
<Filter
|
||||
className="filter-icon"
|
||||
onClick={toggleFilterVisibility}
|
||||
/>
|
||||
<span onClick={toggleFilterVisibility}>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/closes.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="form-sort stylewidth">
|
||||
<Sliders className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select "
|
||||
options={options}
|
||||
placeholder="Sort by Date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div
|
||||
className={`card${isFilterVisible ? " visible" : ""}`}
|
||||
id="filter_inputs"
|
||||
style={{ display: isFilterVisible ? "block" : "none" }}
|
||||
>
|
||||
<div className="card-body pb-0">
|
||||
<div className="row">
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Archive className="info-img" />
|
||||
<Select
|
||||
className="select"
|
||||
options={warehouseOptions}
|
||||
placeholder="Choose Warehouse"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Box className="info-img" />
|
||||
<Select
|
||||
className="select"
|
||||
options={productOptions}
|
||||
placeholder="Choose Product"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Calendar className="info-img" />
|
||||
<div className="input-groupicon">
|
||||
<DatePicker
|
||||
selected={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
dateFormat="dd/MM/yyyy"
|
||||
placeholderText="Choose Date"
|
||||
className="datetimepicker"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-2 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<User className="info-img" />
|
||||
<Select
|
||||
className="select"
|
||||
options={personOptions}
|
||||
placeholder="Choose Person"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-4 col-sm-6 col-12 ms-auto">
|
||||
<div className="input-blocks">
|
||||
<a className="btn btn-filters ms-auto">
|
||||
{" "}
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search"
|
||||
/>{" "}
|
||||
Search{" "}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div className="table-responsive">
|
||||
<Table
|
||||
className="table datanew"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
/>
|
||||
{loading ? (
|
||||
<div className="text-center p-4">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p className="mt-2">Loading products...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<strong>Error:</strong> {error}
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger ms-2"
|
||||
onClick={() => dispatch(fetchInventories())}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
className="table datanew"
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
/>
|
||||
|
||||
<CustomPagination
|
||||
currentPage={params.page}
|
||||
pageSize={params.limit}
|
||||
totalCount={totalRecords}
|
||||
totalPages={actualTotalPages}
|
||||
loading={loading}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showInfo={true}
|
||||
showPageSizeSelector={true}
|
||||
compact={false}
|
||||
className="product-list-pagination"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
396
src/feature-module/superadmin/companylist.jsx
Normal file
@ -0,0 +1,396 @@
|
||||
import { Select } 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 { fetchOrganizations } from "../../core/redux/actions/organizationActions";
|
||||
import { deletePaymentMethod, fetchPaymentMethod, fetchPaymentMethods } from "../../core/redux/actions/paymentMethodActions";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
const CompanyList = () => {
|
||||
const {
|
||||
organizations: apiPaymentMethods,
|
||||
loading,
|
||||
error,
|
||||
totalPaymentMethods,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage,
|
||||
} = useSelector((state) => state.organizations);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const dataSource = apiPaymentMethods?.length > 0 ? apiPaymentMethods : [];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadPaymentMethods = 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(fetchOrganizations(cleanParams));
|
||||
} catch (error) {
|
||||
console.error("Failed to load categories", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadPaymentMethods();
|
||||
}, [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 = totalPaymentMethods || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
const handleDeletePaymentMethod = async (paymentMethodId) => {
|
||||
try {
|
||||
await dispatch(deletePaymentMethod(paymentMethodId));
|
||||
// Show success message
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Payment Method has been deleted successfully.",
|
||||
icon: "success",
|
||||
className: "btn btn-success",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete payment method:", error);
|
||||
MySwal.fire({
|
||||
title: "Error!",
|
||||
text: "Failed to delete payment method. 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: "Company Name",
|
||||
dataIndex: "companyname",
|
||||
render: (_, record) => {
|
||||
return <span>{record.name}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.name.length - b.name.length,
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
dataIndex: "email",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.email ?? '-'}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.email.length - b.email.length,
|
||||
},
|
||||
{
|
||||
title: "Phone Number",
|
||||
dataIndex: "phonenumber",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.phone_number ?? '-'}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.phone_number.length - b.phone_number.length,
|
||||
},
|
||||
{
|
||||
title: "Plan",
|
||||
dataIndex: "plan",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.plan_type ?? '-'}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.plan_type.length - b.plan_type.length,
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
render: (_, record) => {
|
||||
return <span>{formatDate(record.created_at)}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.created_at.length - b.created_at.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(fetchPaymentMethod(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) {
|
||||
handleDeletePaymentMethod(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>Companies</h4>
|
||||
<h6>Manage your companies</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(fetchPaymentMethods())}
|
||||
>
|
||||
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="paymentmethod-list-pagination"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /category list */}
|
||||
</div>
|
||||
</div>
|
||||
<AddCategoryList />
|
||||
<EditCategoryList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyList;
|
||||
@ -1,354 +1,396 @@
|
||||
import React, { useState } from 'react'
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ImageWithBasePath from '../../core/img/imagewithbasebath';
|
||||
import { ChevronUp, RotateCcw } from 'feather-icons-react/build/IconComponents';
|
||||
import { setToogleHeader } from '../../core/redux/action';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Filter, PlusCircle, Sliders, StopCircle, User, Zap } from 'react-feather';
|
||||
import Select from 'react-select';
|
||||
import withReactContent from 'sweetalert2-react-content';
|
||||
import Swal from 'sweetalert2';
|
||||
import Table from '../../core/pagination/datatable'
|
||||
import AddUsers from '../../core/modals/usermanagement/addusers';
|
||||
import EditUser from '../../core/modals/usermanagement/edituser';
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import { ChevronUp, RotateCcw } from "feather-icons-react/build/IconComponents";
|
||||
import { setToogleHeader } from "../../core/redux/action";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
Filter,
|
||||
PlusCircle,
|
||||
Sliders,
|
||||
StopCircle,
|
||||
User,
|
||||
Zap,
|
||||
} from "react-feather";
|
||||
import Select from "react-select";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import Table from "../../core/pagination/datatable";
|
||||
import AddUsers from "../../core/modals/usermanagement/addusers";
|
||||
import EditUser from "../../core/modals/usermanagement/edituser";
|
||||
import usersApi from "../../services/usersApi";
|
||||
|
||||
const Users = () => {
|
||||
const oldandlatestvalue = [
|
||||
{ value: "date", label: "Sort by Date" },
|
||||
{ value: "newest", label: "Newest" },
|
||||
{ value: "oldest", label: "Oldest" },
|
||||
];
|
||||
const users = [
|
||||
{ value: "Choose Name", label: "Choose Name" },
|
||||
{ value: "Lilly", label: "Lilly" },
|
||||
{ value: "Benjamin", label: "Benjamin" },
|
||||
];
|
||||
const status = [
|
||||
{ value: "Choose Name", label: "Choose Status" },
|
||||
{ value: "Active", label: "Active" },
|
||||
{ value: "InActive", label: "InActive" },
|
||||
];
|
||||
const role = [
|
||||
{ value: "Choose Role", label: "Choose Role" },
|
||||
{ value: "AcStore Keeper", label: "Store Keeper" },
|
||||
{ value: "Salesman", label: "Salesman" },
|
||||
];
|
||||
|
||||
const oldandlatestvalue = [
|
||||
{ value: 'date', label: 'Sort by Date' },
|
||||
{ value: 'newest', label: 'Newest' },
|
||||
{ value: 'oldest', label: 'Oldest' },
|
||||
];
|
||||
const users = [
|
||||
{ value: 'Choose Name', label: 'Choose Name' },
|
||||
{ value: 'Lilly', label: 'Lilly' },
|
||||
{ value: 'Benjamin', label: 'Benjamin' },
|
||||
];
|
||||
const status = [
|
||||
{ value: 'Choose Name', label: 'Choose Status' },
|
||||
{ value: 'Active', label: 'Active' },
|
||||
{ value: 'InActive', label: 'InActive' },
|
||||
];
|
||||
const role = [
|
||||
{ value: 'Choose Role', label: 'Choose Role' },
|
||||
{ value: 'AcStore Keeper', label: 'Store Keeper' },
|
||||
{ value: 'Salesman', label: 'Salesman' },
|
||||
];
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const [dataSource, setDataSource] = useState([])
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
const toggleFilterVisibility = () => {
|
||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
const response = await usersApi.getAllUsers();
|
||||
setDataSource(response)
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const dataSource = useSelector((state) => state.userlist_data);
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
const toggleFilterVisibility = () => {
|
||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||
console.log('response', response)
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch users", error);
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
)
|
||||
loadUsers();
|
||||
}, []);
|
||||
|
||||
const columns = [
|
||||
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>
|
||||
);
|
||||
|
||||
{
|
||||
title: "User Name",
|
||||
dataIndex: "username",
|
||||
render: (text, record) => (
|
||||
<span className="userimgname">
|
||||
<Link to="#" className="userslist-img bg-img">
|
||||
<ImageWithBasePath alt="" src={record.img} />
|
||||
</Link>
|
||||
<div>
|
||||
<Link to="#">{text}</Link>
|
||||
</div>
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.username.length - b.username.length,
|
||||
},
|
||||
const columns = [
|
||||
{
|
||||
title: "User Name",
|
||||
dataIndex: "username",
|
||||
render: (text, record) => (
|
||||
<span className="userimgname">
|
||||
<Link to="#" className="userslist-img bg-img">
|
||||
<ImageWithBasePath alt="" src={record.img} />
|
||||
</Link>
|
||||
<div>
|
||||
<Link to="#">{text}</Link>
|
||||
</div>
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.username.length - b.username.length,
|
||||
},
|
||||
|
||||
{
|
||||
title: "Phone",
|
||||
dataIndex: "phone",
|
||||
sorter: (a, b) => a.phone.length - b.phone.length,
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
dataIndex: "email",
|
||||
sorter: (a, b) => a.email.length - b.email.length,
|
||||
},
|
||||
{
|
||||
title: "Role",
|
||||
dataIndex: "role",
|
||||
sorter: (a, b) => a.role.length - b.role.length,
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
sorter: (a, b) => a.createdon.length - b.createdon.length,
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (text) => (
|
||||
<div>
|
||||
{text === "Active" && (
|
||||
<span className="badge badge-linesuccess">{text}</span>
|
||||
)}
|
||||
{text === "Inactive" && (
|
||||
<span className="badge badge-linedanger">{text}</span>
|
||||
)}
|
||||
|
||||
</div>
|
||||
),
|
||||
sorter: (a, b) => a.status.length - b.status.length,
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
dataIndex: 'actions',
|
||||
key: 'actions',
|
||||
render: () => (
|
||||
<td className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
|
||||
<Link className="me-2 p-2" to="#">
|
||||
<i data-feather="eye" className="feather feather-eye action-eye"></i>
|
||||
</Link>
|
||||
<Link className="me-2 p-2" to="#" data-bs-toggle="modal" data-bs-target="#edit-units">
|
||||
<i data-feather="edit" className="feather-edit"></i>
|
||||
</Link>
|
||||
<Link className="confirm-text p-2" to="#" >
|
||||
<i data-feather="trash-2" className="feather-trash-2" onClick={showConfirmationAlert}></i>
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
},
|
||||
]
|
||||
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) {
|
||||
|
||||
MySwal.fire({
|
||||
title: 'Deleted!',
|
||||
text: 'Your file has been deleted.',
|
||||
className: "btn btn-success",
|
||||
confirmButtonText: 'OK',
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-success',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
MySwal.close();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
return (
|
||||
{
|
||||
title: "Phone",
|
||||
dataIndex: "phone",
|
||||
sorter: (a, b) => a.phone.length - b.phone.length,
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
dataIndex: "email",
|
||||
sorter: (a, b) => a.email.length - b.email.length,
|
||||
},
|
||||
{
|
||||
title: "Role",
|
||||
dataIndex: "role",
|
||||
sorter: (a, b) => a.role.length - b.role.length,
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
sorter: (a, b) => a.createdon.length - b.createdon.length,
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (text) => (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
<div className="page-header">
|
||||
<div className="add-item d-flex">
|
||||
<div className="page-title">
|
||||
<h4>User List</h4>
|
||||
<h6>Manage Your Users</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">
|
||||
<a
|
||||
to="#"
|
||||
className="btn btn-added"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-units"
|
||||
>
|
||||
<PlusCircle className="me-2" />
|
||||
Add New User
|
||||
</a>
|
||||
</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"
|
||||
/>
|
||||
<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={oldandlatestvalue}
|
||||
placeholder="Newest"
|
||||
/>
|
||||
</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-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<User className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select"
|
||||
options={users}
|
||||
placeholder="Newest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<StopCircle className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select"
|
||||
options={status}
|
||||
placeholder="Choose Status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Zap className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select"
|
||||
options={role}
|
||||
placeholder="Choose Role"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<a className="btn btn-filters ms-auto">
|
||||
{" "}
|
||||
<i data-feather="search" className="feather-search" />{" "}
|
||||
Search{" "}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div className="table-responsive">
|
||||
<Table columns={columns} dataSource={dataSource} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /product list */}
|
||||
</div>
|
||||
</div>
|
||||
<AddUsers/>
|
||||
<EditUser/>
|
||||
{text === "Active" && (
|
||||
<span className="badge badge-linesuccess">{text}</span>
|
||||
)}
|
||||
{text === "Inactive" && (
|
||||
<span className="badge badge-linedanger">{text}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
),
|
||||
sorter: (a, b) => a.status.length - b.status.length,
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: () => (
|
||||
<td className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
<Link className="me-2 p-2" to="#">
|
||||
<i
|
||||
data-feather="eye"
|
||||
className="feather feather-eye action-eye"
|
||||
></i>
|
||||
</Link>
|
||||
<Link
|
||||
className="me-2 p-2"
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-units"
|
||||
>
|
||||
<i data-feather="edit" className="feather-edit"></i>
|
||||
</Link>
|
||||
<Link className="confirm-text p-2" to="#">
|
||||
<i
|
||||
data-feather="trash-2"
|
||||
className="feather-trash-2"
|
||||
onClick={showConfirmationAlert}
|
||||
></i>
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
),
|
||||
},
|
||||
];
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
export default Users
|
||||
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) {
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Your file has been deleted.",
|
||||
className: "btn btn-success",
|
||||
confirmButtonText: "OK",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
MySwal.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
<div className="page-header">
|
||||
<div className="add-item d-flex">
|
||||
<div className="page-title">
|
||||
<h4>User List</h4>
|
||||
<h6>Manage Your Users</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">
|
||||
<a
|
||||
to="#"
|
||||
className="btn btn-added"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-units"
|
||||
>
|
||||
<PlusCircle className="me-2" />
|
||||
Add New User
|
||||
</a>
|
||||
</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"
|
||||
/>
|
||||
<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={oldandlatestvalue}
|
||||
placeholder="Newest"
|
||||
/>
|
||||
</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-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<User className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select"
|
||||
options={users}
|
||||
placeholder="Newest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<StopCircle className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select"
|
||||
options={status}
|
||||
placeholder="Choose Status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<Zap className="info-img" />
|
||||
|
||||
<Select
|
||||
className="select"
|
||||
options={role}
|
||||
placeholder="Choose Role"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-sm-6 col-12">
|
||||
<div className="input-blocks">
|
||||
<a className="btn btn-filters ms-auto">
|
||||
{" "}
|
||||
<i
|
||||
data-feather="search"
|
||||
className="feather-search"
|
||||
/>{" "}
|
||||
Search{" "}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /Filter */}
|
||||
<div className="table-responsive">
|
||||
<Table columns={columns} dataSource={dataSource} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /product list */}
|
||||
</div>
|
||||
</div>
|
||||
<AddUsers />
|
||||
<EditUser />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Users;
|
||||
|
||||
@ -15,6 +15,7 @@ import '../src/style/icons/fontawesome/css/all.min.css'
|
||||
import { Provider } from "react-redux";
|
||||
import store from "./core/redux/store.jsx";
|
||||
import AllRoutes from "./Router/router.jsx";
|
||||
import authApi from "./services/authApi";
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
@ -49,6 +50,9 @@ const initializeTheme = () => {
|
||||
// Initialize theme before rendering
|
||||
initializeTheme();
|
||||
|
||||
// Check authentication status on app startup
|
||||
authApi.checkAuthStatus();
|
||||
|
||||
|
||||
|
||||
if (rootElement) {
|
||||
|
||||
@ -41,6 +41,7 @@ api.interceptors.response.use(
|
||||
// Unauthorized - redirect to login or refresh token
|
||||
localStorage.removeItem('authToken');
|
||||
// You can add redirect logic here
|
||||
window.location.href = '/signin';
|
||||
} else if (error.response?.status === 500) {
|
||||
// Server error
|
||||
console.error('Server Error:', error.response.data);
|
||||
|
||||
64
src/services/authApi.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { store } from '../core/redux/store';
|
||||
import { loginStart, loginSuccess, loginFailure, logout, checkAuth } from '../core/redux/reducers/authReducer';
|
||||
import api from './api';
|
||||
|
||||
const ENDPOINTS = {
|
||||
LOGIN: 'auth/login',
|
||||
LOGOUT: 'auth/logout',
|
||||
};
|
||||
|
||||
export const authApi = {
|
||||
// Check if user is authenticated
|
||||
isAuthenticated() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const user = localStorage.getItem('user');
|
||||
return !!(token && user);
|
||||
},
|
||||
|
||||
// Get current user
|
||||
getCurrentUser() {
|
||||
const user = localStorage.getItem('user');
|
||||
return user ? JSON.parse(user) : null;
|
||||
},
|
||||
|
||||
// Get auth token
|
||||
getToken() {
|
||||
return localStorage.getItem('authToken');
|
||||
},
|
||||
|
||||
// Login function
|
||||
async login(credentials) {
|
||||
try {
|
||||
store.dispatch(loginStart());
|
||||
|
||||
const response = await api.post(ENDPOINTS.LOGIN, credentials);
|
||||
|
||||
store.dispatch(loginSuccess(response.data));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
store.dispatch(loginFailure(error.message));
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Logout function
|
||||
logout() {
|
||||
store.dispatch(logout());
|
||||
},
|
||||
|
||||
// Check authentication status on app start
|
||||
checkAuthStatus() {
|
||||
store.dispatch(checkAuth());
|
||||
},
|
||||
|
||||
// Set auth headers for API requests
|
||||
getAuthHeaders() {
|
||||
const token = this.getToken();
|
||||
return {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default authApi
|
||||
102
src/services/categoriesApi.js
Normal file
@ -0,0 +1,102 @@
|
||||
import api from './api';
|
||||
|
||||
// Categories API endpoints
|
||||
const ENDPOINTS = {
|
||||
CATEGORIES: 'categories',
|
||||
CATEGORY_BY_ID: (id) => `categories/${id}`,
|
||||
CATEGORY_PRODUCTS: (id) => `categories/${id}/products`,
|
||||
};
|
||||
|
||||
// Categories API service
|
||||
export const categoriesApi = {
|
||||
// Get all categories
|
||||
getAllCategories: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.CATEGORIES, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get category by ID
|
||||
getCategoryById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.CATEGORY_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching category ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create new category
|
||||
createCategory: async (categoryData) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.CATEGORIES, categoryData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating category:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update category
|
||||
updateCategory: async (id, categoryData) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.CATEGORY_BY_ID(id), categoryData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating category ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete category
|
||||
deleteCategory: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.CATEGORY_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting category ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get products by category
|
||||
getCategoryProducts: async (id, params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.CATEGORY_PRODUCTS(id), { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching products for category ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk operations
|
||||
bulkUpdateCategories: async (categories) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.CATEGORIES}/bulk`, { categories });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating categories:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
bulkDeleteCategories: async (categoryIds) => {
|
||||
try {
|
||||
const response = await api.delete(`${ENDPOINTS.CATEGORIES}/bulk`, {
|
||||
data: { ids: categoryIds }
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting categories:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default categoriesApi;
|
||||
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
@ -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;
|
||||
104
src/services/ordersApi.js
Normal file
@ -0,0 +1,104 @@
|
||||
import api from './api';
|
||||
|
||||
// Orders API endpoints
|
||||
const ENDPOINTS = {
|
||||
ORDERS: 'orders',
|
||||
ORDER_BY_ID: (id) => `orders/${id}`,
|
||||
SEARCH: 'orders/search',
|
||||
};
|
||||
|
||||
// Orders API service
|
||||
export const ordersApi = {
|
||||
// Get all orders
|
||||
getAllOrders: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.ORDERS, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching orders:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get order by ID
|
||||
getOrderById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.ORDER_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching order ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create new order
|
||||
createOrder: async (orderData) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.ORDERS, orderData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating order:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update order
|
||||
updateOrder: async (id, orderData) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.ORDER_BY_ID(id), orderData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating order ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete order
|
||||
deleteOrder: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.ORDER_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting order ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Search orders
|
||||
searchOrders: async (query, params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.SEARCH, {
|
||||
params: { q: query, ...params }
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error searching orders:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk operations
|
||||
bulkUpdateOrders: async (orders) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.ORDERS}/bulk`, { orders });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating orders:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
bulkDeleteOrders: async (orderIds) => {
|
||||
try {
|
||||
const response = await api.delete(`${ENDPOINTS.ORDERS}/bulk`, {
|
||||
data: { ids: orderIds }
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting orders:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default ordersApi;
|
||||
91
src/services/organizationsApi.js
Normal file
@ -0,0 +1,91 @@
|
||||
import api from './api';
|
||||
|
||||
const ENDPOINTS = {
|
||||
ORGANIZATIONS: 'organizations',
|
||||
ORGANIZATION_BY_ID: (id) => `organizations/${id}`,
|
||||
};
|
||||
|
||||
export const organizationsApi = {
|
||||
// Get all organizations
|
||||
getAllOrganizations: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.ORGANIZATIONS, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching organizations:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get organization by ID
|
||||
getOrganizationById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.ORGANIZATION_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching organization ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create organization
|
||||
createOrganization: async (data) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.ORGANIZATIONS, data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating organization:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update organization
|
||||
updateOrganization: async (id, data) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.ORGANIZATION_BY_ID(id), data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating organization ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete organization
|
||||
deleteOrganization: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.ORGANIZATION_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting organization ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk update
|
||||
bulkUpdate: async (organizations) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.ORGANIZATIONS}/bulk`, {
|
||||
organizations,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating organizations:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk delete
|
||||
bulkDelete: async (ids) => {
|
||||
try {
|
||||
const response = await api.delete(`${ENDPOINTS.ORGANIZATIONS}/bulk`, {
|
||||
data: { ids },
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting organizations:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default organizationsApi;
|
||||
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;
|
||||
91
src/services/paymentMethodsApi.js
Normal file
@ -0,0 +1,91 @@
|
||||
import api from './api';
|
||||
|
||||
const ENDPOINTS = {
|
||||
PAYMENT_METHODS: 'payment-methods',
|
||||
PAYMENT_METHOD_BY_ID: (id) => `payment-methods/${id}`,
|
||||
};
|
||||
|
||||
export const paymentMethodsApi = {
|
||||
// Get all payment methods
|
||||
getAll: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.PAYMENT_METHODS, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching payment methods:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get payment method by ID
|
||||
getById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.PAYMENT_METHOD_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching payment method ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create new payment method
|
||||
create: async (data) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.PAYMENT_METHODS, data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating payment method:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update payment method
|
||||
update: async (id, data) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.PAYMENT_METHOD_BY_ID(id), data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating payment method ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete payment method
|
||||
remove: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.PAYMENT_METHOD_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting payment method ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk update
|
||||
bulkUpdate: async (methods) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.PAYMENT_METHODS}/bulk`, {
|
||||
methods,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating payment methods:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk delete
|
||||
bulkDelete: async (ids) => {
|
||||
try {
|
||||
const response = await api.delete(`${ENDPOINTS.PAYMENT_METHODS}/bulk`, {
|
||||
data: { ids },
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting payment methods:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default paymentMethodsApi;
|
||||
@ -2,11 +2,11 @@ import api from './api';
|
||||
|
||||
// Products API endpoints
|
||||
const ENDPOINTS = {
|
||||
PRODUCTS: 'Products',
|
||||
PRODUCT_BY_ID: (id) => `Products/${id}`,
|
||||
CATEGORIES: 'Products/categories',
|
||||
BRANDS: 'Products/brands',
|
||||
SEARCH: 'Products/search',
|
||||
PRODUCTS: 'products',
|
||||
PRODUCT_BY_ID: (id) => `products/${id}`,
|
||||
CATEGORIES: 'products/categories',
|
||||
BRANDS: 'products/brands',
|
||||
SEARCH: 'products/search',
|
||||
};
|
||||
|
||||
// Products API service
|
||||
|
||||
228
src/services/usersApi.js
Normal file
@ -0,0 +1,228 @@
|
||||
import api from './api';
|
||||
|
||||
// Users API endpoints
|
||||
const ENDPOINTS = {
|
||||
USERS: 'users',
|
||||
USER_BY_ID: (id) => `users/${id}`,
|
||||
USER_PROFILE: 'users/profile',
|
||||
USER_PERMISSIONS: (id) => `users/${id}/permissions`,
|
||||
USER_ROLES: (id) => `users/${id}/roles`,
|
||||
SEARCH: 'users/search',
|
||||
BULK_OPERATIONS: 'users/bulk',
|
||||
};
|
||||
|
||||
// Users API service
|
||||
export const usersApi = {
|
||||
// Get all users
|
||||
getAllUsers: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.USERS, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get user by ID
|
||||
getUserById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.USER_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching user ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create new user
|
||||
createUser: async (userData) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.USERS, userData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update user
|
||||
updateUser: async (id, userData) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.USER_BY_ID(id), userData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating user ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete user
|
||||
deleteUser: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.USER_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting user ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Search users
|
||||
searchUsers: async (query, params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.SEARCH, {
|
||||
params: { q: query, ...params }
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error searching users:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get user profile
|
||||
getUserProfile: async () => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.USER_PROFILE);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user profile:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update user profile
|
||||
updateUserProfile: async (profileData) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.USER_PROFILE, profileData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error updating user profile:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get user permissions
|
||||
getUserPermissions: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.USER_PERMISSIONS(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching user permissions ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update user permissions
|
||||
updateUserPermissions: async (id, permissions) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.USER_PERMISSIONS(id), { permissions });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating user permissions ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get user roles
|
||||
getUserRoles: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.USER_ROLES(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching user roles ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update user roles
|
||||
updateUserRoles: async (id, roles) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.USER_ROLES(id), { roles });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating user roles ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Change user password
|
||||
changeUserPassword: async (id, passwordData) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.USER_BY_ID(id)}/password`, passwordData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error changing user password ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Activate/Deactivate user
|
||||
toggleUserStatus: async (id, status) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.USER_BY_ID(id)}/status`, { status });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error toggling user status ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk operations
|
||||
bulkUpdateUsers: async (users) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.BULK_OPERATIONS, { users });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating users:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
bulkDeleteUsers: async (userIds) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.BULK_OPERATIONS, {
|
||||
data: { ids: userIds }
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting users:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Export users
|
||||
exportUsers: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(`${ENDPOINTS.USERS}/export`, {
|
||||
params,
|
||||
responseType: 'blob'
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error exporting users:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Import users
|
||||
importUsers: async (fileData) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileData);
|
||||
|
||||
const response = await api.post(`${ENDPOINTS.USERS}/import`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error importing users:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default usersApi;
|
||||
@ -708,4 +708,11 @@ button {
|
||||
box-shadow: 0 3px 12px rgba($dark, .2);
|
||||
border-color: $dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center-vertical {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 1%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
@ -25,4 +25,9 @@
|
||||
@include margin-padding(5px, null);
|
||||
@include box-shadow(null, 0, 2px, 3px, null, rgb(215, 197, 255));
|
||||
}
|
||||
}
|
||||
|
||||
.icon-small {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
@ -1,171 +1,192 @@
|
||||
.page-link {
|
||||
color: $text-color;
|
||||
background-color: $white;
|
||||
border: 1px solid $border-color;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
background-color: $light;
|
||||
}
|
||||
&:hover {
|
||||
color: $primary;
|
||||
background-color: $light;
|
||||
border-color: $border-color;
|
||||
}
|
||||
color: $text-color;
|
||||
background-color: $white;
|
||||
border: 1px solid $border-color;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
background-color: $light;
|
||||
}
|
||||
&:hover {
|
||||
color: $primary;
|
||||
background-color: $light;
|
||||
border-color: $border-color;
|
||||
}
|
||||
}
|
||||
.page-item.active .page-link {
|
||||
color: $white;
|
||||
background-color: $primary;
|
||||
border-color: $primary;
|
||||
color: $white;
|
||||
background-color: $primary;
|
||||
border-color: $primary;
|
||||
}
|
||||
.disabled>.page-link, .page-link.disabled {
|
||||
color: $text-color;
|
||||
background-color: $white;
|
||||
border-color: $border-color;
|
||||
opacity: 0.7;
|
||||
.disabled > .page-link,
|
||||
.page-link.disabled {
|
||||
color: $text-color;
|
||||
background-color: $white;
|
||||
border-color: $border-color;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
[dir="rtl"] {
|
||||
.pagination{
|
||||
.page-link{
|
||||
.bx-chevron-left::before{
|
||||
content: "\ea50";
|
||||
}
|
||||
.bx-chevron-right::before{
|
||||
content: "\ea4d";
|
||||
}
|
||||
.ri-arrow-right-s-line:before{
|
||||
content: "\ea64";
|
||||
}
|
||||
.ri-arrow-left-s-line:before{
|
||||
content: "\ea6e";
|
||||
}
|
||||
}
|
||||
[dir="rtl"] {
|
||||
.pagination {
|
||||
.page-link {
|
||||
.bx-chevron-left::before {
|
||||
content: "\ea50";
|
||||
}
|
||||
.bx-chevron-right::before {
|
||||
content: "\ea4d";
|
||||
}
|
||||
.ri-arrow-right-s-line:before {
|
||||
content: "\ea64";
|
||||
}
|
||||
.ri-arrow-left-s-line:before {
|
||||
content: "\ea6e";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination-style-1 .pagination {
|
||||
.page-item {
|
||||
margin: 0 0.25rem;
|
||||
.page-link {
|
||||
border: 0;
|
||||
border-radius: $border-radius;
|
||||
font-size: 0.8125rem;
|
||||
i {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
border-radius: $border-radius;
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
border-radius: $border-radius;
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $light;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
.page-item {
|
||||
margin: 0 0.25rem;
|
||||
.page-link {
|
||||
border: 0;
|
||||
border-radius: $border-radius;
|
||||
font-size: 0.8125rem;
|
||||
i {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
border-radius: $border-radius;
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
border-radius: $border-radius;
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $light;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination-style-2 .pagination {
|
||||
border-radius: $border-radius;
|
||||
.page-item {
|
||||
margin: 0 0.25rem;
|
||||
.page-link {
|
||||
border: 0 !important;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
background-color: $white;
|
||||
color: $primary;
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
inset-block-end: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 100%;
|
||||
height: 0.1rem;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
border-radius: $border-radius;
|
||||
.page-item {
|
||||
margin: 0 0.25rem;
|
||||
.page-link {
|
||||
border: 0 !important;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
background-color: $white;
|
||||
color: $primary;
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
inset-block-end: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 100%;
|
||||
height: 0.1rem;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination-style-3 .pagination {
|
||||
border-radius: 50px;
|
||||
padding: 0.25rem;
|
||||
align-items: center;
|
||||
.page-item {
|
||||
margin: 0 0.25rem;
|
||||
.page-link {
|
||||
border: 0;
|
||||
border-radius: 50px;
|
||||
font-size: 0.8125rem;
|
||||
i {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $light;
|
||||
}
|
||||
}
|
||||
border-radius: 50px;
|
||||
padding: 0.25rem;
|
||||
align-items: center;
|
||||
.page-item {
|
||||
margin: 0 0.25rem;
|
||||
.page-link {
|
||||
border: 0;
|
||||
border-radius: 50px;
|
||||
font-size: 0.8125rem;
|
||||
i {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination-style-4 .pagination {
|
||||
.page-item {
|
||||
.page-link {
|
||||
border: 0 !important;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: $border-radius;
|
||||
i {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
border: 0;
|
||||
border-radius: $border-radius;
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.page-item {
|
||||
.page-link {
|
||||
border: 0 !important;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: $border-radius;
|
||||
i {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.page-link {
|
||||
border: 0;
|
||||
border-radius: $border-radius;
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.page-link {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-pagination .page-item .page-link {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.custom-pagination .page-item.active .page-link {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.page-link.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@ -317,7 +317,7 @@ caption {
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-size: $font-size-14;
|
||||
font-weight: $font-weight-normal;
|
||||
line-height: normal;
|
||||
@ -533,10 +533,10 @@ caption {
|
||||
padding: 0;
|
||||
}
|
||||
.table-top {
|
||||
padding: 24px 24px 0;
|
||||
padding: 18px 18px 0;
|
||||
}
|
||||
.table-responsive {
|
||||
padding: 24px;
|
||||
padding: 18px;
|
||||
border-top: 1px solid $gray-400;
|
||||
.dataTables_wrapper {
|
||||
border: 0;
|
||||
@ -545,7 +545,7 @@ caption {
|
||||
}
|
||||
.tabs-set {
|
||||
.nav-tabs {
|
||||
padding: 24px 24px 0;
|
||||
padding: 18px 18px 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@ -674,3 +674,12 @@ table {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-table .ant-table-thead > tr > th {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.custom-table .ant-table-thead .ant-table-cell {
|
||||
background-color: #fafbfe;
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
}
|
||||
h3{
|
||||
font-weight: $font-weight-bold;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-size: $font-size-18;
|
||||
@include respond-below(custom991) {
|
||||
font-size: $font-size-base;
|
||||
@ -120,7 +120,7 @@
|
||||
}
|
||||
h4 {
|
||||
font-weight: $font-weight-bold;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-size: $font-size-18;
|
||||
margin-bottom: 5px;
|
||||
@include respond-below(custom991) {
|
||||
@ -536,7 +536,7 @@ th,td {
|
||||
h4 {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $secondary
|
||||
color: $primary
|
||||
}
|
||||
}
|
||||
&.image-upload-new{
|
||||
@ -652,7 +652,7 @@ th,td {
|
||||
}
|
||||
label{
|
||||
margin-bottom: 8px;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: $font-size-base;
|
||||
display: block;
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
color: $white;
|
||||
}
|
||||
svg {
|
||||
color: #FE9F43
|
||||
color: $primary
|
||||
}
|
||||
}
|
||||
svg {
|
||||
@ -66,21 +66,21 @@
|
||||
}
|
||||
&:hover{
|
||||
background: rgba(254, 159, 67, 0.08);
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
border-radius: 5px;
|
||||
img {
|
||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||
}
|
||||
span{
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
}
|
||||
svg{
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
&.active{
|
||||
background: rgba(254, 159, 67, 0.08);
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
border-radius: 5px;
|
||||
svg{
|
||||
color: $white;
|
||||
@ -89,12 +89,12 @@
|
||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||
}
|
||||
span{
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
}
|
||||
.menu-arrow{
|
||||
background: #FFEDDC;
|
||||
&::before{
|
||||
border-color: #FE9F43;
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,7 +112,7 @@
|
||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||
}
|
||||
span{
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,8 +132,8 @@
|
||||
&.active{
|
||||
color: $secondary;
|
||||
&:after{
|
||||
background: #FE9F43;
|
||||
border: 2px solid #FDB;
|
||||
background: $primary;
|
||||
border: 2px solid $secondary;
|
||||
}
|
||||
}
|
||||
&::after{
|
||||
@ -148,8 +148,8 @@
|
||||
&:hover{
|
||||
color:$primary;
|
||||
&:after{
|
||||
background: #FE9F43;
|
||||
border: 2px solid #FDB;
|
||||
background: $primary;
|
||||
border: 2px solid $secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -188,15 +188,15 @@
|
||||
}
|
||||
}
|
||||
&:after{
|
||||
background: #FE9F43;
|
||||
border: 2px solid #FDB;
|
||||
background: $primary;
|
||||
border: 2px solid $secondary;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
color:$primary;
|
||||
&:after{
|
||||
background: #FE9F43;
|
||||
border: 2px solid #FDB;
|
||||
background: $primary;
|
||||
border: 2px solid $secondary;
|
||||
}
|
||||
span {
|
||||
color:$primary;
|
||||
@ -209,7 +209,7 @@
|
||||
&.active a{
|
||||
background: rgba(254, 159, 67, 0.08);
|
||||
border-radius: 5px;
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
span {
|
||||
color: $primary;
|
||||
}
|
||||
@ -240,25 +240,25 @@
|
||||
li {
|
||||
&.active{
|
||||
a{
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
}
|
||||
svg {
|
||||
color: #FE9F43
|
||||
color: $primary
|
||||
}
|
||||
}
|
||||
.submenu > {
|
||||
a {
|
||||
&.active{
|
||||
background: rgba(254, 159, 67, 0.08);
|
||||
color: #FE9F43;
|
||||
background: $secondary;
|
||||
color: $primary;
|
||||
border-radius: 5px;
|
||||
span{
|
||||
color: #FE9F43;
|
||||
color: $primary;
|
||||
}
|
||||
.menu-arrow{
|
||||
background: #FFEDDC;
|
||||
// background: #FFEDDC;
|
||||
&::before{
|
||||
border-color: #FE9F43;
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -321,8 +321,8 @@
|
||||
&:hover{
|
||||
color:$primary;
|
||||
&:after{
|
||||
background: #FE9F43;
|
||||
border: 2px solid #FDB;
|
||||
background: $primary;
|
||||
border: 2px solid $secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -854,10 +854,10 @@ $__basecolor: #2c3038;
|
||||
|
||||
// Badge styling in options
|
||||
.badge {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
// display: inline-block;
|
||||
// width: 8px;
|
||||
// height: 8px;
|
||||
// border-radius: 50%;
|
||||
|
||||
&.badge-blue { background: #007bff; }
|
||||
&.badge-green { background: #28a745; }
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
margin-top: 50px !important;
|
||||
p {
|
||||
font-size: $font-size-14;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
margin-bottom: 0;
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
@ -120,7 +120,7 @@
|
||||
h4 {
|
||||
font-size: $font-size-15;
|
||||
font-weight: $font-weight-normal;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.verfy-mail-content {
|
||||
@ -132,7 +132,7 @@
|
||||
margin-bottom: 15px;
|
||||
label {
|
||||
width: 100%;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
margin-bottom:10px;
|
||||
font-size: $font-size-15;
|
||||
font-weight: $font-weight-normal;
|
||||
@ -209,12 +209,12 @@
|
||||
h4{
|
||||
font-size: $font-size-15;
|
||||
font-weight: $font-weight-normal;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
@include respond-below(custom575) {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
a{
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: $font-size-14;
|
||||
}
|
||||
@ -339,7 +339,7 @@
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
width: 100%;
|
||||
border: 1px solid rgba(145, 158, 171, 0.23);
|
||||
background: $white;
|
||||
|
||||
@ -182,7 +182,7 @@
|
||||
border: 0;
|
||||
}
|
||||
h4 {
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-medium;
|
||||
width: 30%;
|
||||
@ -246,14 +246,14 @@
|
||||
|
||||
h4 {
|
||||
font-size: $font-size-base;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: $font-size-13;
|
||||
font-weight: $font-weight-normal;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,7 +409,7 @@ span {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid var(--Stroke, rgba(145, 158, 171, 0.30));
|
||||
background: $secondary;
|
||||
background: $primary;
|
||||
@include rounded(8px);
|
||||
@include respond-below(custom575) {
|
||||
position: relative;
|
||||
|
||||
@ -256,13 +256,12 @@ table {
|
||||
}
|
||||
}
|
||||
.custom-modal-header {
|
||||
background: $body-bg;
|
||||
padding: 24px;
|
||||
.page-title {
|
||||
h4 {
|
||||
font-size: $font-size-18;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $secondary;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,9 @@ $font-family-secondary: "Poppins", sans-serif;
|
||||
$font-awesome: "Fontawesome";
|
||||
|
||||
// Theme Colors Variables
|
||||
$primary: #FF9F43;
|
||||
$primary: #36175e;
|
||||
$primary-hover: darken($primary, 10%);
|
||||
$secondary: #092C4C;
|
||||
$secondary: #f1eaf9;
|
||||
$secondary-hover: darken($secondary, 10%);
|
||||
$success: #28C76F;
|
||||
$success-hover: darken($success, 10%);
|
||||
@ -29,7 +29,7 @@ $warning-hover: darken($warning, 10%);
|
||||
$danger: #FF0000;
|
||||
$danger-hover: darken($danger, 10%);
|
||||
$dark: #29344a;
|
||||
$light: #f8f9fa;
|
||||
$light: #FAFBFE;
|
||||
$white: #ffffff;
|
||||
$black: #000000;
|
||||
$purple: #7367F0;
|
||||
@ -44,26 +44,26 @@ $indigo: #4d5ddb;
|
||||
$yellow: #ffff00;
|
||||
|
||||
// Primary
|
||||
$primary-100: #FFF5EC;
|
||||
$primary-200: #FFECD9;
|
||||
$primary-300: #FFE2C7;
|
||||
$primary-400: #FFD9B4;
|
||||
$primary-500: #FFCFA1;
|
||||
$primary-600: #FFC58E;
|
||||
$primary-700: #FFBC7B;
|
||||
$primary-800: #FFB269;
|
||||
$primary-900: #FFA956;
|
||||
$primary-100: #ede7f3;
|
||||
$primary-200: #d3c2e3;
|
||||
$primary-300: #b99cd3;
|
||||
$primary-400: #9f76c3;
|
||||
$primary-500: #8551b3;
|
||||
$primary-600: #6e3f98;
|
||||
$primary-700: #57317a;
|
||||
$primary-800: #40225c;
|
||||
$primary-900: #2a153d;
|
||||
|
||||
// Secondary
|
||||
$secondary-100: #E6EAED;
|
||||
$secondary-200: #CED5DB;
|
||||
$secondary-300: #B5C0C9;
|
||||
$secondary-400: #9DABB7;
|
||||
$secondary-500: #8496A6;
|
||||
$secondary-600: #6B8094;
|
||||
$secondary-700: #536B82;
|
||||
$secondary-800: #3A5670;
|
||||
$secondary-900: #22415E;
|
||||
$secondary-100: #fdfbff;
|
||||
$secondary-200: #f8f4fc;
|
||||
$secondary-300: #f1eaf9;
|
||||
$secondary-400: #e0d3f1;
|
||||
$secondary-500: #c8b0e6;
|
||||
$secondary-600: #af8ddb;
|
||||
$secondary-700: #9369cc;
|
||||
$secondary-800: #7547aa;
|
||||
$secondary-900: #563180;
|
||||
|
||||
// Success
|
||||
$success-100: #EAF9F1;
|
||||
@ -158,7 +158,7 @@ $theme-colors: (
|
||||
"black": $black,
|
||||
"purple": $purple,
|
||||
"yellow": $yellow,
|
||||
"teal": $teal
|
||||
"teal": $teal,
|
||||
);
|
||||
|
||||
$text-color: #5B6670;
|
||||
@ -282,4 +282,4 @@ $h2-font-size: $font-size-base * 2;
|
||||
$h3-font-size: $font-size-base * 1.75;
|
||||
$h4-font-size: $font-size-base * 1.5;
|
||||
$h5-font-size: $font-size-base * 1.25;
|
||||
$h6-font-size: $font-size-base;
|
||||
$h6-font-size: $font-size-base;
|
||||
9
src/utils/currency.js
Normal file
@ -0,0 +1,9 @@
|
||||
const formatRupiah = (angka) => {
|
||||
return new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
currency: "IDR",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(angka);
|
||||
};
|
||||
|
||||
export { formatRupiah };
|
||||
17
src/utils/date.js
Normal file
@ -0,0 +1,17 @@
|
||||
const formatDate = (isoDate) => {
|
||||
const date = new Date(isoDate);
|
||||
|
||||
const formatted = date.toLocaleString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short", // example: July
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
return formatted
|
||||
};
|
||||
|
||||
const formatInputDate = (date) => {
|
||||
return new Date(date).toLocaleDateString("en-CA");
|
||||
};
|
||||
|
||||
export { formatDate, formatInputDate };
|
||||