Compare commits
No commits in common. "86a4a192093c35a076ea60c8ce2d26a281f15092" and "68355e31a84403c9501b45168f3a270087de0c7b" have entirely different histories.
86a4a19209
...
68355e31a8
3
.env
@ -1,4 +1,3 @@
|
|||||||
# 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)
|
# CORS Proxy for development (uncomment if needed)
|
||||||
# REACT_APP_API_BASE_URL=https://cors-anywhere.herokuapp.com/https://trantran.zenstores.com.vn/api/
|
# REACT_APP_API_BASE_URL=https://cors-anywhere.herokuapp.com/https://trantran.zenstores.com.vn/api/
|
||||||
|
|||||||
25
package-lock.json
generated
@ -8,7 +8,6 @@
|
|||||||
"name": "my-app",
|
"name": "my-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.6.1",
|
|
||||||
"@ckeditor/ckeditor5-build-classic": "^41.2.0",
|
"@ckeditor/ckeditor5-build-classic": "^41.2.0",
|
||||||
"@ckeditor/ckeditor5-react": "^6.2.0",
|
"@ckeditor/ckeditor5-react": "^6.2.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||||
@ -143,14 +142,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ant-design/icons": {
|
"node_modules/@ant-design/icons": {
|
||||||
"version": "5.6.1",
|
"version": "5.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.3.tgz",
|
||||||
"integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
|
"integrity": "sha512-Zfci1s4f4+vfpVD6ksmmPuBv00SB/slpUAQlsBlMeRJdSleVVkgTUdlBM4j/vGzqYfMh2hF8/Poa1VSh542w0Q==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/colors": "^7.0.0",
|
"@ant-design/colors": "^7.0.0",
|
||||||
"@ant-design/icons-svg": "^4.4.0",
|
"@ant-design/icons-svg": "^4.4.0",
|
||||||
"@babel/runtime": "^7.24.8",
|
"@babel/runtime": "^7.11.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"rc-util": "^5.31.1"
|
"rc-util": "^5.31.1"
|
||||||
},
|
},
|
||||||
@ -2044,10 +2042,12 @@
|
|||||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.28.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz",
|
||||||
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
"integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==",
|
||||||
"license": "MIT",
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@ -21112,6 +21112,11 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/regenerator-transform": {
|
||||||
"version": "0.15.2",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
"homepage": "/",
|
"homepage": "/",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.6.1",
|
|
||||||
"@ckeditor/ckeditor5-build-classic": "^41.2.0",
|
"@ckeditor/ckeditor5-build-classic": "^41.2.0",
|
||||||
"@ckeditor/ckeditor5-react": "^6.2.0",
|
"@ckeditor/ckeditor5-react": "^6.2.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 3.9 KiB |
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
<link rel="icon" href="./favicon.png" />
|
<link rel="icon" href="./favicon.png" />
|
||||||
<title>APSKEL</title>
|
<title>Dreams Pos admin template</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -1,25 +1,16 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import FeatherIcon from "feather-icons-react";
|
import FeatherIcon from "feather-icons-react";
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
import { Search, XCircle } from "react-feather";
|
import { Search, XCircle } from "react-feather";
|
||||||
import { all_routes } from "../../Router/all_routes";
|
import { all_routes } from "../../Router/all_routes";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import authApi from "../../services/authApi";
|
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const route = all_routes;
|
const route = all_routes;
|
||||||
const navigate = useNavigate();
|
|
||||||
const authState = useSelector((state) => state.auth);
|
|
||||||
const [toggle, SetToggle] = useState(false);
|
const [toggle, SetToggle] = useState(false);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout()
|
|
||||||
navigate(route.signin);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isElementVisible = (element) => {
|
const isElementVisible = (element) => {
|
||||||
return element.offsetWidth > 0 || element.offsetHeight > 0;
|
return element.offsetWidth > 0 || element.offsetHeight > 0;
|
||||||
};
|
};
|
||||||
@ -625,8 +616,8 @@ const Header = () => {
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="user-detail">
|
<span className="user-detail">
|
||||||
<span className="user-name">{authState.user?.name || 'User'}</span>
|
<span className="user-name">John Smilga</span>
|
||||||
<span className="user-role">{authState.user?.role || 'Admin'}</span>
|
<span className="user-role">Super Admin</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@ -641,12 +632,12 @@ const Header = () => {
|
|||||||
<span className="status online" />
|
<span className="status online" />
|
||||||
</span>
|
</span>
|
||||||
<div className="profilesets">
|
<div className="profilesets">
|
||||||
<h6>{authState.user?.name || 'User'}</h6>
|
<h6>John Smilga</h6>
|
||||||
<h5>{authState.user?.role || 'Admin'}</h5>
|
<h5>Super Admin</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
<Link className="dropdown-item" to={route.profile}>
|
<Link className="dropdown-item" to={route.route}>
|
||||||
<i className="me-2" data-feather="user" /> My Profile
|
<i className="me-2" data-feather="user" /> My Profile
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="dropdown-item" to={route.generalsettings}>
|
<Link className="dropdown-item" to={route.generalsettings}>
|
||||||
@ -654,18 +645,14 @@ const Header = () => {
|
|||||||
Settings
|
Settings
|
||||||
</Link>
|
</Link>
|
||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
<button
|
<Link className="dropdown-item logout pb-0" to="/signin">
|
||||||
className="dropdown-item logout pb-0"
|
|
||||||
onClick={handleLogout}
|
|
||||||
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left' }}
|
|
||||||
>
|
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath
|
||||||
src="assets/img/icons/log-out.svg"
|
src="assets/img/icons/log-out.svg"
|
||||||
alt="img"
|
alt="img"
|
||||||
className="me-2"
|
className="me-2"
|
||||||
/>
|
/>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -688,13 +675,9 @@ const Header = () => {
|
|||||||
<Link className="dropdown-item" to="generalsettings">
|
<Link className="dropdown-item" to="generalsettings">
|
||||||
Settings
|
Settings
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<Link className="dropdown-item" to="signin">
|
||||||
className="dropdown-item"
|
|
||||||
onClick={handleLogout}
|
|
||||||
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left' }}
|
|
||||||
>
|
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* /Mobile Menu */}
|
{/* /Mobile Menu */}
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Scrollbars from "react-custom-scrollbars-2";
|
import Scrollbars from "react-custom-scrollbars-2";
|
||||||
|
// import { useSelector } from "react-redux";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import { SidebarData } from "../../core/json/siderbar_data";
|
import { SidebarData } from "../../core/json/siderbar_data";
|
||||||
import CollapsedSidebar from "./collapsedSidebar";
|
|
||||||
import HorizontalSidebar from "./horizontalSidebar";
|
import HorizontalSidebar from "./horizontalSidebar";
|
||||||
import { useSelector } from "react-redux";
|
import CollapsedSidebar from "./collapsedSidebar";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const { user } = useSelector((state) => state.auth);
|
// const SidebarData = useSelector((state) => state.sidebar_data);
|
||||||
|
// console.log(sidebarData, "sidebar");
|
||||||
|
|
||||||
const Location = useLocation();
|
const Location = useLocation();
|
||||||
|
|
||||||
|
console.log("Location.pathname", Location.pathname);
|
||||||
|
|
||||||
const [subOpen, setSubopen] = useState("");
|
const [subOpen, setSubopen] = useState("");
|
||||||
const [subsidebar, setSubsidebar] = useState("");
|
const [subsidebar, setSubsidebar] = useState("");
|
||||||
|
|
||||||
@ -43,12 +46,6 @@ const Sidebar = () => {
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{mainLabel?.submenuItems?.map((title, i) => {
|
{mainLabel?.submenuItems?.map((title, i) => {
|
||||||
if (title?.role) {
|
|
||||||
if (user?.role !== title?.role) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let link_array = [];
|
let link_array = [];
|
||||||
title?.submenuItems?.map((link) => {
|
title?.submenuItems?.map((link) => {
|
||||||
link_array?.push(link?.link);
|
link_array?.push(link?.link);
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export const all_routes = {
|
|||||||
floatinglabel: "/form-floating-labels",
|
floatinglabel: "/form-floating-labels",
|
||||||
formvalidation: "/form-validation",
|
formvalidation: "/form-validation",
|
||||||
select2: "/form-select2",
|
select2: "/form-select2",
|
||||||
companylist: "/company-list",
|
|
||||||
toasts: "/ui-toasts",
|
toasts: "/ui-toasts",
|
||||||
video: "/ui-video",
|
video: "/ui-video",
|
||||||
sweetalerts: "/ui-sweetalerts",
|
sweetalerts: "/ui-sweetalerts",
|
||||||
@ -81,7 +81,7 @@ export const all_routes = {
|
|||||||
typicons: "/icon-typicon",
|
typicons: "/icon-typicon",
|
||||||
flagicons: "/icon-flag",
|
flagicons: "/icon-flag",
|
||||||
ribbon: "/ui-ribbon",
|
ribbon: "/ui-ribbon",
|
||||||
paymentmethodlist: "/payment-method-list",
|
|
||||||
chat: "/chat",
|
chat: "/chat",
|
||||||
videocall: "/video-call",
|
videocall: "/video-call",
|
||||||
audiocall: "/audio-call",
|
audiocall: "/audio-call",
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
import { useSelector } from "react-redux";
|
import React from "react";
|
||||||
import { Outlet, Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import Header from "../InitialPage/Sidebar/Header";
|
import Header from "../InitialPage/Sidebar/Header";
|
||||||
import Sidebar from "../InitialPage/Sidebar/Sidebar";
|
import Sidebar from "../InitialPage/Sidebar/Sidebar";
|
||||||
import ThemeSettings from "../InitialPage/themeSettings";
|
|
||||||
import ProtectedRoute from "../components/ProtectedRoute";
|
|
||||||
import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import ThemeSettings from "../InitialPage/themeSettings";
|
||||||
// import CollapsedSidebar from "../InitialPage/Sidebar/collapsedSidebar";
|
// import CollapsedSidebar from "../InitialPage/Sidebar/collapsedSidebar";
|
||||||
import GuestRoute from "../components/GuestRoute";
|
|
||||||
import Loader from "../feature-module/loader/loader";
|
import Loader from "../feature-module/loader/loader";
|
||||||
// import HorizontalSidebar from "../InitialPage/Sidebar/horizontalSidebar";
|
// import HorizontalSidebar from "../InitialPage/Sidebar/horizontalSidebar";
|
||||||
//import LoadingSpinner from "../InitialPage/Sidebar/LoadingSpinner";
|
//import LoadingSpinner from "../InitialPage/Sidebar/LoadingSpinner";
|
||||||
|
|
||||||
const AllRoutes = () => {
|
const AllRoutes = () => {
|
||||||
const { user } = useSelector((state) => state.auth);
|
|
||||||
const data = useSelector((state) => state.toggle_header);
|
const data = useSelector((state) => state.toggle_header);
|
||||||
// const layoutStyles = useSelector((state) => state.layoutstyledata);
|
// const layoutStyles = useSelector((state) => state.layoutstyledata);
|
||||||
const HeaderLayout = () => (
|
const HeaderLayout = () => (
|
||||||
@ -49,6 +48,8 @@ const AllRoutes = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(publicRoutes, "dashboard");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Routes>
|
<Routes>
|
||||||
@ -57,29 +58,13 @@ const AllRoutes = () => {
|
|||||||
<Route path={route.path} element={route.element} key={id} />
|
<Route path={route.path} element={route.element} key={id} />
|
||||||
))}
|
))}
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path={"/"} element={<HeaderLayout />}>
|
||||||
<Route
|
{publicRoutes.map((route, id) => (
|
||||||
path={"/"}
|
<Route path={route.path} element={route.element} key={id} />
|
||||||
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>
|
||||||
|
|
||||||
<Route
|
<Route path={"/"} element={<Authpages />}>
|
||||||
path={"/"}
|
|
||||||
element={
|
|
||||||
<GuestRoute>
|
|
||||||
<Authpages />
|
|
||||||
</GuestRoute>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{pagesRoute.map((route, id) => (
|
{pagesRoute.map((route, id) => (
|
||||||
<Route path={route.path} element={route.element} key={id} />
|
<Route path={route.path} element={route.element} key={id} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -204,9 +204,6 @@ import AddWeddingGuest from "../feature-module/inventory/addWeddingGuest";
|
|||||||
import ProductList2 from "../feature-module/inventory/productlist2";
|
import ProductList2 from "../feature-module/inventory/productlist2";
|
||||||
import ProductList3 from "../feature-module/inventory/productlist3";
|
import ProductList3 from "../feature-module/inventory/productlist3";
|
||||||
import { all_routes } from "./all_routes";
|
import { all_routes } from "./all_routes";
|
||||||
import PaymentMethodList from "../feature-module/FinanceAccounts/paymentmethodlist";
|
|
||||||
import CompanyList from "../feature-module/superadmin/companylist";
|
|
||||||
|
|
||||||
export const publicRoutes = [
|
export const publicRoutes = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -1407,13 +1404,6 @@ export const publicRoutes = [
|
|||||||
element: <ProductDetail />,
|
element: <ProductDetail />,
|
||||||
route: Route,
|
route: Route,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 113.1,
|
|
||||||
path: `${routes.productdetails}/:id`,
|
|
||||||
name: "productdetails",
|
|
||||||
element: <ProductDetail />,
|
|
||||||
route: Route,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 114,
|
id: 114,
|
||||||
path: routes.warehouses,
|
path: routes.warehouses,
|
||||||
@ -1491,23 +1481,7 @@ export const publicRoutes = [
|
|||||||
element: <Navigate to="/" />,
|
element: <Navigate to="/" />,
|
||||||
route: Route,
|
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 = [
|
export const posRoutes = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
import {
|
import React, { useState, useEffect } from 'react';
|
||||||
DoubleLeftOutlined,
|
import { useSelector } from 'react-redux';
|
||||||
DoubleRightOutlined,
|
|
||||||
LeftOutlined,
|
|
||||||
RightOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
|
|
||||||
const CustomPagination = ({
|
const CustomPagination = ({
|
||||||
currentPage = 1,
|
currentPage = 1,
|
||||||
@ -19,32 +13,28 @@ const CustomPagination = ({
|
|||||||
showInfo = true,
|
showInfo = true,
|
||||||
showPageSizeSelector = true,
|
showPageSizeSelector = true,
|
||||||
compact = false,
|
compact = false,
|
||||||
className = "",
|
className = ''
|
||||||
}) => {
|
}) => {
|
||||||
// Theme state for force re-render
|
// Theme state for force re-render
|
||||||
const [themeKey, setThemeKey] = useState(0);
|
const [themeKey, setThemeKey] = useState(0);
|
||||||
|
|
||||||
// Get theme from Redux and localStorage fallback
|
// Get theme from Redux and localStorage fallback
|
||||||
const reduxTheme = useSelector((state) => state.theme?.isDarkMode);
|
const reduxTheme = useSelector((state) => state.theme?.isDarkMode);
|
||||||
const localStorageTheme = localStorage.getItem("colorschema") === "dark_mode";
|
const localStorageTheme = localStorage.getItem('colorschema') === 'dark_mode';
|
||||||
const documentTheme =
|
const documentTheme = document.documentElement.getAttribute('data-layout-mode') === 'dark_mode';
|
||||||
document.documentElement.getAttribute("data-layout-mode") === "dark_mode";
|
|
||||||
|
|
||||||
const isDarkMode = reduxTheme || localStorageTheme || documentTheme;
|
const isDarkMode = reduxTheme || localStorageTheme || documentTheme;
|
||||||
|
|
||||||
// Listen for theme changes
|
// Listen for theme changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleThemeChange = () => {
|
const handleThemeChange = () => {
|
||||||
setThemeKey((prev) => prev + 1);
|
setThemeKey(prev => prev + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen for data-layout-mode attribute changes
|
// Listen for data-layout-mode attribute changes
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
mutations.forEach((mutation) => {
|
mutations.forEach((mutation) => {
|
||||||
if (
|
if (mutation.type === 'attributes' && mutation.attributeName === 'data-layout-mode') {
|
||||||
mutation.type === "attributes" &&
|
|
||||||
mutation.attributeName === "data-layout-mode"
|
|
||||||
) {
|
|
||||||
handleThemeChange();
|
handleThemeChange();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -52,25 +42,27 @@ const CustomPagination = ({
|
|||||||
|
|
||||||
observer.observe(document.documentElement, {
|
observer.observe(document.documentElement, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ["data-layout-mode"],
|
attributeFilter: ['data-layout-mode']
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also listen for localStorage changes
|
// Also listen for localStorage changes
|
||||||
const handleStorageChange = (e) => {
|
const handleStorageChange = (e) => {
|
||||||
if (e.key === "colorschema") {
|
if (e.key === 'colorschema') {
|
||||||
handleThemeChange();
|
handleThemeChange();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("storage", handleStorageChange);
|
window.addEventListener('storage', handleStorageChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
window.removeEventListener("storage", handleStorageChange);
|
window.removeEventListener('storage', handleStorageChange);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log(totalCount);
|
// Calculate pagination info
|
||||||
|
const startRecord = totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
||||||
|
const endRecord = Math.min(currentPage * pageSize, totalCount);
|
||||||
|
|
||||||
// Handle page change
|
// Handle page change
|
||||||
const handlePageClick = (page) => {
|
const handlePageClick = (page) => {
|
||||||
@ -88,191 +80,190 @@ const CustomPagination = ({
|
|||||||
|
|
||||||
// Container styles based on compact mode
|
// Container styles based on compact mode
|
||||||
const containerStyles = {
|
const containerStyles = {
|
||||||
background: isDarkMode
|
background: isDarkMode
|
||||||
? "linear-gradient(135deg, #2c3e50 0%, #34495e 100%)"
|
? 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)'
|
||||||
: "linear-gradient(135deg, #ffffff, #f8f9fa)",
|
: 'linear-gradient(135deg, #ffffff, #f8f9fa)',
|
||||||
border: isDarkMode
|
border: isDarkMode
|
||||||
? "1px solid rgba(52, 152, 219, 0.3)"
|
? '1px solid rgba(52, 152, 219, 0.3)'
|
||||||
: "1px solid rgba(0, 0, 0, 0.1)",
|
: '1px solid rgba(0, 0, 0, 0.1)',
|
||||||
borderRadius: compact ? "8px" : "12px",
|
borderRadius: compact ? '8px' : '12px',
|
||||||
boxShadow: isDarkMode
|
boxShadow: isDarkMode
|
||||||
? compact
|
? (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)')
|
||||||
? "0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px 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)'),
|
||||||
: "0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)"
|
backdropFilter: isDarkMode ? (compact ? 'blur(8px)' : 'blur(10px)') : 'none',
|
||||||
: compact
|
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease',
|
||||||
? "0 1px 6px rgba(0, 0, 0, 0.06)"
|
position: 'relative',
|
||||||
: "0 2px 12px rgba(0, 0, 0, 0.08)",
|
overflow: 'hidden',
|
||||||
backdropFilter: isDarkMode
|
padding: compact ? '8px 16px' : '16px 24px',
|
||||||
? compact
|
margin: compact ? '8px 0' : '16px 0',
|
||||||
? "blur(8px)"
|
fontSize: compact ? '13px' : '14px'
|
||||||
: "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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`pagination-${themeKey}`}
|
key={`pagination-${themeKey}`}
|
||||||
className={`custom-pagination-container ${
|
className={`custom-pagination-container ${isDarkMode ? '' : 'light-mode'} ${className}`}
|
||||||
isDarkMode ? "" : "light-mode"
|
|
||||||
} ${className}`}
|
|
||||||
style={containerStyles}
|
style={containerStyles}
|
||||||
>
|
>
|
||||||
{/* Pagination Info */}
|
{/* Pagination Info */}
|
||||||
{showInfo && (
|
{showInfo && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginBottom: compact ? "8px" : "0px",
|
marginBottom: compact ? '8px' : '16px',
|
||||||
flexWrap: "wrap",
|
flexWrap: 'wrap',
|
||||||
gap: compact ? "8px" : "12px",
|
gap: compact ? '8px' : '12px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showPageSizeSelector && (
|
{showPageSizeSelector && (
|
||||||
<div
|
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
||||||
style={{
|
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>Số hàng mỗi trang</span>
|
||||||
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
|
<select
|
||||||
value={pageSize}
|
value={pageSize}
|
||||||
onChange={(e) => handlePageSizeClick(parseInt(e.target.value))}
|
onChange={(e) => handlePageSizeClick(parseInt(e.target.value))}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
style={{
|
style={{
|
||||||
background: loading
|
background: loading
|
||||||
? isDarkMode
|
? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
||||||
? "linear-gradient(45deg, #7f8c8d, #95a5a6)"
|
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
||||||
: "linear-gradient(135deg, #f8f9fa, #e9ecef)"
|
border: isDarkMode
|
||||||
: isDarkMode
|
? '1px solid rgba(52, 152, 219, 0.3)'
|
||||||
? "linear-gradient(45deg, #34495e, #2c3e50)"
|
: '1px solid #dee2e6',
|
||||||
: "linear-gradient(135deg, #ffffff, #f8f9fa)",
|
borderRadius: compact ? '4px' : '6px',
|
||||||
border: isDarkMode
|
color: isDarkMode ? '#ffffff' : '#495057',
|
||||||
? "1px solid rgba(52, 152, 219, 0.3)"
|
padding: compact ? '2px 6px' : '4px 8px',
|
||||||
: "1px solid #dee2e6",
|
fontSize: compact ? '12px' : '14px',
|
||||||
borderRadius: compact ? "4px" : "6px",
|
cursor: loading ? 'not-allowed' : 'pointer',
|
||||||
color: isDarkMode ? "#ffffff" : "#495057",
|
|
||||||
padding: compact ? "2px 6px" : "4px 8px",
|
|
||||||
fontSize: compact ? "12px" : "14px",
|
|
||||||
cursor: loading ? "not-allowed" : "pointer",
|
|
||||||
opacity: loading ? 0.7 : 1,
|
opacity: loading ? 0.7 : 1,
|
||||||
boxShadow: isDarkMode
|
boxShadow: isDarkMode ? 'none' : (compact ? '0 1px 2px rgba(0, 0, 0, 0.05)' : '0 1px 3px rgba(0, 0, 0, 0.1)')
|
||||||
? "none"
|
|
||||||
: compact
|
|
||||||
? "0 1px 2px rgba(0, 0, 0, 0.05)"
|
|
||||||
: "0 1px 3px rgba(0, 0, 0, 0.1)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pageSizeOptions.map((option) => (
|
{pageSizeOptions.map(option => (
|
||||||
<option
|
<option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
style={{
|
style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}
|
||||||
background: isDarkMode ? "#2c3e50" : "#ffffff",
|
|
||||||
color: isDarkMode ? "#ffffff" : "#495057",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{option}
|
{option}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<span
|
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>bản ghi</span>
|
||||||
style={{
|
|
||||||
color: isDarkMode ? "#bdc3c7" : "#2c3e50",
|
|
||||||
fontSize: compact ? "12px" : "14px",
|
|
||||||
fontWeight: "500",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
records
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pagination Buttons */}
|
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
background: isDarkMode
|
||||||
zIndex: 1,
|
? 'linear-gradient(45deg, #3498db, #2ecc71)'
|
||||||
display: "flex",
|
: 'linear-gradient(45deg, #007bff, #28a745)',
|
||||||
justifyContent: "center",
|
borderRadius: '50%',
|
||||||
alignItems: "center",
|
width: compact ? '16px' : '24px',
|
||||||
gap: compact ? "4px" : "8px",
|
height: compact ? '16px' : '24px',
|
||||||
}}
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
<nav aria-label="Custom pagination">
|
justifyContent: 'center',
|
||||||
<ul className="pagination justify-content-center custom-pagination">
|
fontSize: compact ? '8px' : '12px',
|
||||||
<li className="page-item">
|
boxShadow: isDarkMode
|
||||||
<a
|
? (compact ? '0 1px 4px rgba(52, 152, 219, 0.3)' : '0 2px 8px rgba(52, 152, 219, 0.3)')
|
||||||
onClick={() => handlePageClick(1)}
|
: (compact ? '0 1px 4px rgba(0, 123, 255, 0.2)' : '0 2px 8px rgba(0, 123, 255, 0.2)'),
|
||||||
className="page-link"
|
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease'
|
||||||
aria-label="First"
|
}}
|
||||||
>
|
>
|
||||||
<DoubleLeftOutlined
|
📊
|
||||||
style={{ fontSize: "12px", marginRight: "-10px" }}
|
</div>
|
||||||
/>
|
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>
|
||||||
</a>
|
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
|
||||||
</li>
|
</span>
|
||||||
<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>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
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
|
// Color variants
|
||||||
.primary {
|
.primary {
|
||||||
--loader-color: #36175e;
|
--loader-color: #ff9f43;
|
||||||
--loader-secondary: rgba(255, 159, 67, 0.3);
|
--loader-secondary: rgba(255, 159, 67, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
// Color variants
|
// Color variants
|
||||||
&.primary {
|
&.primary {
|
||||||
background: #36175e;
|
background: #ff9f43;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
&:hover:not(.loading):not(.disabled) {
|
&:hover:not(.loading):not(.disabled) {
|
||||||
@ -83,11 +83,11 @@
|
|||||||
|
|
||||||
&.outline-primary {
|
&.outline-primary {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #36175e;
|
color: #ff9f43;
|
||||||
border: 2px solid #36175e;
|
border: 2px solid #ff9f43;
|
||||||
|
|
||||||
&:hover:not(.loading):not(.disabled) {
|
&:hover:not(.loading):not(.disabled) {
|
||||||
background: #36175e;
|
background: #ff9f43;
|
||||||
color: white;
|
color: white;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(255, 159, 67, 0.4);
|
box-shadow: 0 4px 12px rgba(255, 159, 67, 0.4);
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
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,148 +1,72 @@
|
|||||||
import { useState } from "react";
|
import React from 'react'
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { Link } from 'react-router-dom'
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import {
|
|
||||||
createCategory,
|
|
||||||
fetchCategories,
|
|
||||||
} from "../../redux/actions/categoryActions";
|
|
||||||
|
|
||||||
const AddCategoryList = () => {
|
const AddCategoryList = () => {
|
||||||
const dispatch = useDispatch();
|
return (
|
||||||
const { creating } = useSelector((state) => state.categories);
|
<div>
|
||||||
|
{/* Add Category */}
|
||||||
const [formData, setFormData] = useState({
|
<div className="modal fade" id="add-category">
|
||||||
name: "",
|
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
||||||
description: "",
|
<div className="modal-content">
|
||||||
});
|
<div className="page-wrapper-new p-0">
|
||||||
|
<div className="content">
|
||||||
const handleInputChange = (e) => {
|
<div className="modal-header border-0 custom-modal-header">
|
||||||
setFormData({
|
<div className="page-title">
|
||||||
...formData,
|
<h4>Create Category</h4>
|
||||||
[e.target.name]: e.target.value,
|
</div>
|
||||||
});
|
<button
|
||||||
};
|
type="button"
|
||||||
|
className="close"
|
||||||
const handleSubmit = async (e) => {
|
data-bs-dismiss="modal"
|
||||||
e.preventDefault();
|
aria-label="Close"
|
||||||
|
>
|
||||||
try {
|
<span aria-hidden="true">×</span>
|
||||||
await dispatch(createCategory(formData));
|
</button>
|
||||||
|
</div>
|
||||||
await dispatch(fetchCategories());
|
<div className="modal-body custom-modal-body">
|
||||||
|
<form>
|
||||||
Swal.fire({
|
<div className="mb-3">
|
||||||
title: "Success!",
|
<label className="form-label">Category</label>
|
||||||
text: "Category created successfully!",
|
<input type="text" className="form-control" />
|
||||||
icon: "success",
|
</div>
|
||||||
showConfirmButton: false,
|
<div className="mb-3">
|
||||||
timer: 1500,
|
<label className="form-label">Category Slug</label>
|
||||||
}).then(() => {
|
<input type="text" className="form-control" />
|
||||||
const closeButton = document.querySelector("#add-category .btn-close");
|
</div>
|
||||||
closeButton.click();
|
<div className="mb-0">
|
||||||
});
|
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||||
} catch (error) {
|
<span className="status-label">Status</span>
|
||||||
console.error("Error creating category:", error);
|
<input
|
||||||
|
type="checkbox"
|
||||||
// Show error message
|
id="user2"
|
||||||
Swal.fire({
|
className="check"
|
||||||
icon: "error",
|
defaultChecked="true"
|
||||||
title: "Error!",
|
/>
|
||||||
text: error.message || "Failed to create category. Please try again.",
|
<label htmlFor="user2" className="checktoggle" />
|
||||||
});
|
</div>
|
||||||
}
|
</div>
|
||||||
};
|
<div className="modal-footer-btn">
|
||||||
|
<button
|
||||||
return (
|
type="button"
|
||||||
<div>
|
className="btn btn-cancel me-2"
|
||||||
{/* Add Category */}
|
data-bs-dismiss="modal"
|
||||||
<div className="modal fade" id="add-category">
|
>
|
||||||
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
Cancel
|
||||||
<div className="modal-content">
|
</button>
|
||||||
<div className="page-wrapper-new p-0">
|
<Link to="#" className="btn btn-submit">
|
||||||
<div className="content">
|
Create Category
|
||||||
<div className="modal-header">
|
</Link>
|
||||||
<div className="page-title">
|
</div>
|
||||||
<h4>Create Category</h4>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</div>
|
||||||
type="button"
|
</div>
|
||||||
className="btn-close"
|
</div>
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
></button>
|
|
||||||
</div>
|
</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>
|
{/* /Add Category */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
{/* /Add Category */}
|
}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddCategoryList;
|
export default AddCategoryList
|
||||||
|
|||||||
@ -1,160 +1,80 @@
|
|||||||
import { useEffect, useState } from "react";
|
import React from 'react'
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { Link } from 'react-router-dom'
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import {
|
|
||||||
fetchCategories,
|
|
||||||
updateCategory,
|
|
||||||
} from "../../redux/actions/categoryActions";
|
|
||||||
|
|
||||||
const EditCategoryList = () => {
|
const EditCategoryList = () => {
|
||||||
const dispatch = useDispatch();
|
return (
|
||||||
|
<div>
|
||||||
const { currentCategory, updating, currentPage, pageSize } = useSelector(
|
{/* Edit Category */}
|
||||||
(state) => state.categories
|
<div className="modal fade" id="edit-category">
|
||||||
);
|
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
||||||
|
<div className="modal-content">
|
||||||
const [formData, setFormData] = useState({
|
<div className="page-wrapper-new p-0">
|
||||||
name: "",
|
<div className="content">
|
||||||
description: "",
|
<div className="modal-header border-0 custom-modal-header">
|
||||||
});
|
<div className="page-title">
|
||||||
|
<h4>Edit Category</h4>
|
||||||
useEffect(() => {
|
</div>
|
||||||
if (currentCategory) {
|
<button
|
||||||
setFormData({
|
type="button"
|
||||||
name: currentCategory.name,
|
className="close"
|
||||||
description: currentCategory.description,
|
data-bs-dismiss="modal"
|
||||||
});
|
aria-label="Close"
|
||||||
}
|
>
|
||||||
}, [currentCategory]);
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
const handleInputChange = (e) => {
|
</div>
|
||||||
setFormData({
|
<div className="modal-body custom-modal-body">
|
||||||
...formData,
|
<form>
|
||||||
[e.target.name]: e.target.value,
|
<div className="mb-3">
|
||||||
});
|
<label className="form-label">Category</label>
|
||||||
};
|
<input
|
||||||
|
type="text"
|
||||||
const handleSubmit = async (e) => {
|
className="form-control"
|
||||||
e.preventDefault();
|
defaultValue="Laptop"
|
||||||
|
/>
|
||||||
try {
|
</div>
|
||||||
await dispatch(updateCategory(currentCategory?.id, formData));
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Category Slug</label>
|
||||||
await dispatch(fetchCategories({ page: currentPage, limit: pageSize }));
|
<input
|
||||||
|
type="text"
|
||||||
Swal.fire({
|
className="form-control"
|
||||||
title: "Success!",
|
defaultValue="laptop"
|
||||||
text: "Category updated successfully!",
|
/>
|
||||||
icon: "success",
|
</div>
|
||||||
showConfirmButton: false,
|
<div className="mb-0">
|
||||||
timer: 1500,
|
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||||
}).then(() => {
|
<span className="status-label">Status</span>
|
||||||
const closeButton = document.querySelector("#edit-category .btn-close");
|
<input
|
||||||
closeButton.click();
|
type="checkbox"
|
||||||
});
|
id="user3"
|
||||||
} catch (error) {
|
className="check"
|
||||||
console.error("Error updating category:", error);
|
defaultChecked="true"
|
||||||
|
/>
|
||||||
// Show error message
|
<label htmlFor="user3" className="checktoggle" />
|
||||||
Swal.fire({
|
</div>
|
||||||
icon: "error",
|
</div>
|
||||||
title: "Error!",
|
<div className="modal-footer-btn">
|
||||||
text: error.message || "Failed to update category. Please try again.",
|
<button
|
||||||
});
|
type="button"
|
||||||
}
|
className="btn btn-cancel me-2"
|
||||||
};
|
data-bs-dismiss="modal"
|
||||||
|
>
|
||||||
return (
|
Cancel
|
||||||
<div>
|
</button>
|
||||||
{/* Edit Category */}
|
<Link to="#" className="btn btn-submit">
|
||||||
<div className="modal fade" id="edit-category">
|
Save Changes
|
||||||
<div className="modal-dialog modal-dialog-centered custom-modal-two">
|
</Link>
|
||||||
<div className="modal-content">
|
</div>
|
||||||
<div className="page-wrapper-new p-0">
|
</form>
|
||||||
<div className="content">
|
</div>
|
||||||
<div className="modal-header">
|
</div>
|
||||||
<div className="page-title">
|
</div>
|
||||||
<h4>Edit Category</h4>
|
</div>
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn-close"
|
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
></button>
|
|
||||||
</div>
|
</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>
|
{/* /Edit Category */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
{/* /Edit Category */}
|
}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditCategoryList;
|
export default EditCategoryList
|
||||||
|
|||||||
@ -1,149 +1,26 @@
|
|||||||
import { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import Select from "react-select";
|
||||||
import AsyncSelect from "react-select/async";
|
import ImageWithBasePath from "../../img/imagewithbasebath";
|
||||||
import Swal from "sweetalert2";
|
import { Link } from "react-router-dom";
|
||||||
import outletsApi from "../../../services/outletsApi";
|
|
||||||
import productsApi from "../../../services/productsApi";
|
|
||||||
import {
|
|
||||||
createInventory,
|
|
||||||
fetchInventories,
|
|
||||||
updateInventory,
|
|
||||||
} from "../../redux/actions/inventoryActions";
|
|
||||||
|
|
||||||
const ManageStockModal = () => {
|
const ManageStockModal = () => {
|
||||||
const dispatch = useDispatch();
|
const options1 = [
|
||||||
const { creating, updating, currentInventory } = useSelector(
|
{ value: "choose", label: "Choose" },
|
||||||
(state) => state.inventories
|
{ value: "lobarHandy", label: "Lobar Handy" },
|
||||||
);
|
{ value: "quaintWarehouse", label: "Quaint Warehouse" },
|
||||||
|
];
|
||||||
|
|
||||||
const initializeFormData = () => {
|
const options2 = [
|
||||||
return {
|
{ value: "choose", label: "Choose" },
|
||||||
outlet_id: "",
|
{ value: "selosy", label: "Selosy" },
|
||||||
product_id: "",
|
{ value: "logerro", label: "Logerro" },
|
||||||
quantity: "",
|
];
|
||||||
min_stock_level: "",
|
|
||||||
max_stock_level: "",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState(initializeFormData());
|
|
||||||
const [selectedOutlet, setSelectedOutlet] = useState(null);
|
|
||||||
const [selectedProduct, setSelectedProduct] = useState(null);
|
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
[e.target.name]: Number(e.target.value),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectChange = (field, selectedOption) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
[field]: selectedOption ? selectedOption.value : "",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentInventory) {
|
|
||||||
setFormData(currentInventory);
|
|
||||||
}
|
|
||||||
}, [currentInventory]);
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await dispatch(createInventory(formData));
|
|
||||||
|
|
||||||
await dispatch(fetchInventories());
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: "Success!",
|
|
||||||
text: "Stock created successfully!",
|
|
||||||
icon: "success",
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 1500,
|
|
||||||
}).then(() => {
|
|
||||||
const closeButton = document.querySelector("#add-units .btn-close");
|
|
||||||
closeButton.click();
|
|
||||||
setFormData(initializeFormData());
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error creating stock:", error);
|
|
||||||
|
|
||||||
// Show error message
|
|
||||||
Swal.fire({
|
|
||||||
icon: "error",
|
|
||||||
title: "Error!",
|
|
||||||
text: error.message || "Failed to create stock. Please try again.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpdate = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await dispatch(updateInventory(currentInventory?.id, formData));
|
|
||||||
|
|
||||||
await dispatch(fetchInventories());
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: "Success!",
|
|
||||||
text: "Stock updated successfully!",
|
|
||||||
icon: "success",
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 1500,
|
|
||||||
}).then(() => {
|
|
||||||
const closeButton = document.querySelector("#edit-units .btn-close");
|
|
||||||
closeButton.click();
|
|
||||||
setFormData(initializeFormData());
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating stock:", error);
|
|
||||||
|
|
||||||
// Show error message
|
|
||||||
Swal.fire({
|
|
||||||
icon: "error",
|
|
||||||
title: "Error!",
|
|
||||||
text: error.message || "Failed to update stock. Please try again.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadOutlets = async (inputValue) => {
|
|
||||||
try {
|
|
||||||
const response = await outletsApi.getAllOutlets({ search: inputValue });
|
|
||||||
|
|
||||||
const options = response.data.outlets.map((item) => ({
|
|
||||||
label: item.name,
|
|
||||||
value: item.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return options;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching options:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadProducts = async (inputValue) => {
|
|
||||||
try {
|
|
||||||
const response = await productsApi.getAllProducts({ search: inputValue });
|
|
||||||
|
|
||||||
const options = response.data.products.map((item) => ({
|
|
||||||
label: item.name,
|
|
||||||
value: item.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return options;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching options:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const options3 = [
|
||||||
|
{ value: "choose", label: "Choose" },
|
||||||
|
{ value: "steven", label: "Steven" },
|
||||||
|
{ value: "gravely", label: "Gravely" },
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Add Stock */}
|
{/* Add Stock */}
|
||||||
@ -152,109 +29,65 @@ const ManageStockModal = () => {
|
|||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="page-wrapper-new p-0">
|
<div className="page-wrapper-new p-0">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="modal-header">
|
<div className="modal-header border-0 custom-modal-header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Create Stock</h4>
|
<h4>Add Stock</h4>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-close"
|
className="close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
></button>
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body custom-modal-body">
|
<div className="modal-body custom-modal-body">
|
||||||
<form onSubmit={handleSubmit}>
|
<form>
|
||||||
<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="row">
|
||||||
<div className="col">
|
<div className="col-lg-6">
|
||||||
<label className="form-label">Min Stock</label>
|
<div className="input-blocks">
|
||||||
<input
|
<label>Warehouse</label>
|
||||||
type="number"
|
<Select className="select" options={options1} />
|
||||||
className="form-control border"
|
</div>
|
||||||
name="min_stock_level"
|
|
||||||
value={formData.min_stock_level}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col-lg-6">
|
||||||
<label className="form-label">Max Stock</label>
|
<div className="input-blocks">
|
||||||
<input
|
<label>Shop</label>
|
||||||
type="number"
|
<Select className="select" options={options2} />
|
||||||
className="form-control border"
|
</div>
|
||||||
name="max_stock_level"
|
</div>
|
||||||
value={formData.max_stock_level}
|
<div className="col-lg-12">
|
||||||
onChange={handleInputChange}
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-outline-dark me-2"
|
className="btn btn-cancel me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="submit" className="btn btn-submit">
|
||||||
type="submit"
|
Create
|
||||||
disabled={creating}
|
|
||||||
className="btn btn-submit"
|
|
||||||
>
|
|
||||||
{creating ? (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className="spinner-border spinner-border-sm me-2"
|
|
||||||
role="status"
|
|
||||||
aria-hidden="true"
|
|
||||||
></span>
|
|
||||||
Creating...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"Create"
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -267,79 +100,167 @@ const ManageStockModal = () => {
|
|||||||
{/* /Add Stock */}
|
{/* /Add Stock */}
|
||||||
{/* Edit Stock */}
|
{/* Edit Stock */}
|
||||||
<div className="modal fade" id="edit-units">
|
<div className="modal fade" id="edit-units">
|
||||||
<div className="modal-dialog modal-dialog-centered">
|
<div className="modal-dialog modal-dialog-centered stock-adjust-modal">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="page-wrapper-new p-0">
|
<div className="page-wrapper-new p-0">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="modal-header">
|
<div className="modal-header border-0 custom-modal-header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Edit Stock</h4>
|
<h4>Edit Stock</h4>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-close"
|
className="close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
></button>
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body custom-modal-body">
|
<div className="modal-body custom-modal-body">
|
||||||
<form onSubmit={handleUpdate}>
|
<form>
|
||||||
<div className="mb-3">
|
<div className="input-blocks search-form">
|
||||||
<label className="form-label">Quantity</label>
|
<label>Product</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
className="form-control border"
|
className="form-control"
|
||||||
name="quantity"
|
defaultValue="Nike Jordan"
|
||||||
value={formData.quantity}
|
/>
|
||||||
onChange={handleInputChange}
|
<i
|
||||||
|
data-feather="search"
|
||||||
|
className="feather-search custom-search"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="row">
|
||||||
<label className="form-label">Min Stock</label>
|
<div className="col-lg-6">
|
||||||
<input
|
<div className="input-blocks">
|
||||||
type="number"
|
<label>Warehouse</label>
|
||||||
className="form-control border"
|
<Select className="select" options={options1} />
|
||||||
name="min_stock_level"
|
</div>
|
||||||
value={formData.min_stock_level}
|
</div>
|
||||||
onChange={handleInputChange}
|
<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>
|
</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">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-outline-dark me-2"
|
className="btn btn-cancel me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="submit" className="btn btn-submit">
|
||||||
type="submit"
|
Save Changes
|
||||||
disabled={updating}
|
|
||||||
className="btn btn-submit"
|
|
||||||
>
|
|
||||||
{updating ? (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className="spinner-border spinner-border-sm me-2"
|
|
||||||
role="status"
|
|
||||||
aria-hidden="true"
|
|
||||||
></span>
|
|
||||||
Updating...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"Update"
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { onShowSizeChange } from "./pagination";
|
|||||||
const Datatable = ({ props, columns, dataSource }) => {
|
const Datatable = ({ props, columns, dataSource }) => {
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||||||
const onSelectChange = (newSelectedRowKeys) => {
|
const onSelectChange = (newSelectedRowKeys) => {
|
||||||
|
console.log("selectedRowKeys changed: ", selectedRowKeys);
|
||||||
setSelectedRowKeys(newSelectedRowKeys);
|
setSelectedRowKeys(newSelectedRowKeys);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,18 +15,15 @@ const Datatable = ({ props, columns, dataSource }) => {
|
|||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
key={props}
|
key={props}
|
||||||
className="custom-table table datanew dataTable"
|
className="table datanew dataTable no-footer"
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
|
|
||||||
rowKey={(record) => record.id}
|
rowKey={(record) => record.id}
|
||||||
pagination={{
|
|
||||||
pageSize: 100
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,195 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
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,7 +89,6 @@ export const createProduct = (productData) => async (dispatch) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await productsApi.createProduct(productData);
|
const data = await productsApi.createProduct(productData);
|
||||||
console.log('data', data)
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS,
|
type: PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS,
|
||||||
payload: data,
|
payload: data,
|
||||||
@ -164,6 +163,21 @@ 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
|
// Fetch brands
|
||||||
export const fetchBrands = () => async (dispatch) => {
|
export const fetchBrands = () => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
import { combineReducers } from '@reduxjs/toolkit';
|
import { combineReducers } from '@reduxjs/toolkit';
|
||||||
import initialState from "./initial.value";
|
import initialState from "./initial.value";
|
||||||
import productReducer from './reducers/productReducer';
|
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
|
// Legacy reducer for existing functionality
|
||||||
const legacyReducer = (state = initialState, action) => {
|
const legacyReducer = (state = initialState, action) => {
|
||||||
@ -80,13 +73,6 @@ const legacyReducer = (state = initialState, action) => {
|
|||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
legacy: legacyReducer,
|
legacy: legacyReducer,
|
||||||
products: productReducer,
|
products: productReducer,
|
||||||
auth: authReducer,
|
|
||||||
categories: categoryReducer,
|
|
||||||
orders: orderReducer,
|
|
||||||
paymentMethods: paymentMethodReducer,
|
|
||||||
organizations: organizationReducer,
|
|
||||||
inventories: inventoryReducer,
|
|
||||||
outlets: outletReducer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,245 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
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 = {
|
const initialState = {
|
||||||
// Products list
|
// Products list
|
||||||
@ -6,7 +6,7 @@ const initialState = {
|
|||||||
totalProducts: 0,
|
totalProducts: 0,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
pageSize: 10,
|
pageSize: 20,
|
||||||
hasPrevious: false,
|
hasPrevious: false,
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ const initialState = {
|
|||||||
|
|
||||||
// Search results
|
// Search results
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
searchQuery: "",
|
searchQuery: '',
|
||||||
|
|
||||||
// Categories and brands
|
// Categories and brands
|
||||||
categories: [],
|
categories: [],
|
||||||
@ -49,19 +49,20 @@ const productReducer = (state = initialState, action) => {
|
|||||||
|
|
||||||
case PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS: {
|
case PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS: {
|
||||||
// Handle different API response structures
|
// Handle different API response structures
|
||||||
const { products, total_count, page, total_pages, limit } =
|
const isArrayResponse = Array.isArray(action.payload);
|
||||||
action.payload.data;
|
const products = isArrayResponse ? action.payload : (action.payload.data || action.payload.items || []);
|
||||||
|
const pagination = action.payload.pagination || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
loading: false,
|
||||||
products: products,
|
products: products,
|
||||||
totalProducts: total_count,
|
totalProducts: pagination.totalCount || action.payload.total || products.length,
|
||||||
currentPage: page,
|
currentPage: pagination.currentPage || action.payload.currentPage || 1,
|
||||||
totalPages: total_pages,
|
totalPages: pagination.totalPages || action.payload.totalPages || 1,
|
||||||
pageSize: limit,
|
pageSize: pagination.pageSize || 20,
|
||||||
hasPrevious: false,
|
hasPrevious: pagination.hasPrevious || false,
|
||||||
hasNext: false,
|
hasNext: pagination.hasNext || false,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -85,7 +86,7 @@ const productReducer = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
productLoading: false,
|
productLoading: false,
|
||||||
currentProduct: action.payload.data,
|
currentProduct: action.payload,
|
||||||
productError: null,
|
productError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,11 +106,10 @@ const productReducer = (state = initialState, action) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
case PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS:
|
case PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS:
|
||||||
console.log("state", state);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
creating: false,
|
creating: false,
|
||||||
|
products: [action.payload, ...state.products],
|
||||||
totalProducts: state.totalProducts + 1,
|
totalProducts: state.totalProducts + 1,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
@ -133,7 +133,7 @@ const productReducer = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
updating: false,
|
updating: false,
|
||||||
products: state.products.map((product) =>
|
products: state.products.map(product =>
|
||||||
product.id === action.payload.id ? action.payload.data : product
|
product.id === action.payload.id ? action.payload.data : product
|
||||||
),
|
),
|
||||||
currentProduct: action.payload.data,
|
currentProduct: action.payload.data,
|
||||||
@ -159,9 +159,7 @@ const productReducer = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
products: state.products.filter(
|
products: state.products.filter(product => product.id !== action.payload),
|
||||||
(product) => product.id !== action.payload
|
|
||||||
),
|
|
||||||
totalProducts: state.totalProducts - 1,
|
totalProducts: state.totalProducts - 1,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
@ -186,7 +184,7 @@ const productReducer = (state = initialState, action) => {
|
|||||||
...state,
|
...state,
|
||||||
searchLoading: false,
|
searchLoading: false,
|
||||||
searchResults: action.payload.data || action.payload,
|
searchResults: action.payload.data || action.payload,
|
||||||
searchQuery: action.payload.query || "",
|
searchQuery: action.payload.query || '',
|
||||||
searchError: null,
|
searchError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,5 +5,4 @@ const store = configureStore({
|
|||||||
reducer: rootReducer,
|
reducer: rootReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export { store };
|
|
||||||
export default store;
|
export default store;
|
||||||
|
|||||||
@ -1,387 +0,0 @@
|
|||||||
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,388 +1,324 @@
|
|||||||
import { Select, Tag } from "antd";
|
import React, { useState } from 'react'
|
||||||
import {
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||||
ChevronUp,
|
import ImageWithBasePath from '../../core/img/imagewithbasebath';
|
||||||
PlusCircle,
|
import { Link } from 'react-router-dom';
|
||||||
RotateCcw,
|
import { ChevronUp, Filter, PlusCircle, RotateCcw, Sliders, StopCircle, Zap } from 'feather-icons-react/build/IconComponents';
|
||||||
Trash2,
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
} from "feather-icons-react/build/IconComponents";
|
import { setToogleHeader } from '../../core/redux/action';
|
||||||
import { useEffect, useState } from "react";
|
import Select from 'react-select';
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
import { DatePicker } from 'antd';
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import AddCategoryList from '../../core/modals/inventory/addcategorylist';
|
||||||
import { Link } from "react-router-dom";
|
import EditCategoryList from '../../core/modals/inventory/editcategorylist';
|
||||||
import Swal from "sweetalert2";
|
import withReactContent from 'sweetalert2-react-content';
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import Swal from 'sweetalert2';
|
||||||
import CustomPagination from "../../components/CustomPagination";
|
import Table from '../../core/pagination/datatable'
|
||||||
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 CategoryList = () => {
|
||||||
const {
|
|
||||||
categories: apiCategories,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
totalCategories,
|
|
||||||
totalPages,
|
|
||||||
pageSize: reduxPageSize,
|
|
||||||
currentPage: reduxCurrentPage,
|
|
||||||
} = useSelector((state) => state.categories);
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const data = useSelector((state) => state.toggle_header);
|
const data = useSelector((state) => state.toggle_header);
|
||||||
const dataSource = apiCategories?.length > 0 ? apiCategories : [];
|
const dataSource = useSelector((state) => state.categotylist_data);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
const toggleFilterVisibility = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||||
|
};
|
||||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||||
|
const handleDateChange = (date) => {
|
||||||
useEffect(() => {
|
setSelectedDate(date);
|
||||||
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
|
const oldandlatestvalue = [
|
||||||
useEffect(() => {
|
{ value: 'date', label: 'Sort by Date' },
|
||||||
const timer = setTimeout(() => {
|
{ value: 'newest', label: 'Newest' },
|
||||||
setDebouncedSearchTerm(searchTerm);
|
{ value: 'oldest', label: 'Oldest' },
|
||||||
}, 500); // 500ms delay
|
];
|
||||||
|
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' },
|
||||||
|
];
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
const renderTooltip = (props) => (
|
||||||
}, [searchTerm]);
|
<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>
|
||||||
|
)
|
||||||
|
|
||||||
// Handle pagination
|
const columns = [
|
||||||
const handlePageChange = (page) => {
|
|
||||||
setCurrentPage(page);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle page size change
|
{
|
||||||
const handlePageSizeChange = (newPageSize) => {
|
title: "Category",
|
||||||
setPageSize(newPageSize);
|
dataIndex: "category",
|
||||||
setCurrentPage(1); // Reset to first page when changing page size
|
sorter: (a, b) => a.category.length - b.category.length,
|
||||||
};
|
|
||||||
|
|
||||||
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) {
|
title: "Category Slug",
|
||||||
console.error("Failed to delete category:", error);
|
dataIndex: "categoryslug",
|
||||||
MySwal.fire({
|
sorter: (a, b) => a.categoryslug.length - b.categoryslug.length,
|
||||||
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,
|
||||||
const renderTooltip = (props) => (
|
},
|
||||||
<Tooltip id="pdf-tooltip" {...props}>
|
{
|
||||||
Pdf
|
title: "Status",
|
||||||
</Tooltip>
|
dataIndex: "status",
|
||||||
);
|
render: (text) => (
|
||||||
const renderExcelTooltip = (props) => (
|
<span className="badge badge-linesuccess">
|
||||||
<Tooltip id="excel-tooltip" {...props}>
|
<Link to="#"> {text}</Link>
|
||||||
Excel
|
</span>
|
||||||
</Tooltip>
|
),
|
||||||
);
|
sorter: (a, b) => a.status.length - b.status.length,
|
||||||
const renderPrinterTooltip = (props) => (
|
},
|
||||||
<Tooltip id="printer-tooltip" {...props}>
|
{
|
||||||
Printer
|
title: 'Actions',
|
||||||
</Tooltip>
|
dataIndex: 'actions',
|
||||||
);
|
key: 'actions',
|
||||||
const renderRefreshTooltip = (props) => (
|
render: () => (
|
||||||
<Tooltip id="refresh-tooltip" {...props}>
|
<td className="action-table-data">
|
||||||
Refresh
|
<div className="edit-delete-action">
|
||||||
</Tooltip>
|
<Link className="me-2 p-2" to="#" data-bs-toggle="modal" data-bs-target="#edit-category">
|
||||||
);
|
<i data-feather="edit" className="feather-edit"></i>
|
||||||
const renderCollapseTooltip = (props) => (
|
</Link>
|
||||||
<Tooltip id="refresh-tooltip" {...props}>
|
<Link className="confirm-text p-2" to="#" >
|
||||||
Collapse
|
<i data-feather="trash-2" className="feather-trash-2" onClick={showConfirmationAlert}></i>
|
||||||
</Tooltip>
|
</Link>
|
||||||
);
|
|
||||||
|
|
||||||
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>
|
</div>
|
||||||
<p className="mt-2">Loading categories...</p>
|
</td>
|
||||||
</div>
|
)
|
||||||
) : error ? (
|
},
|
||||||
<div className="alert alert-danger" role="alert">
|
]
|
||||||
<strong>Error:</strong> {error}
|
const MySwal = withReactContent(Swal);
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-outline-danger ms-2"
|
|
||||||
onClick={() => dispatch(fetchCategories())}
|
|
||||||
>
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Table columns={columns} dataSource={dataSource} />
|
|
||||||
|
|
||||||
<CustomPagination
|
const showConfirmationAlert = () => {
|
||||||
currentPage={currentPage}
|
MySwal.fire({
|
||||||
pageSize={pageSize}
|
title: 'Are you sure?',
|
||||||
totalCount={totalRecords}
|
text: 'You won\'t be able to revert this!',
|
||||||
totalPages={actualTotalPages}
|
showCancelButton: true,
|
||||||
loading={loading}
|
confirmButtonColor: '#00ff00',
|
||||||
onPageChange={handlePageChange}
|
confirmButtonText: 'Yes, delete it!',
|
||||||
onPageSizeChange={handlePageSizeChange}
|
cancelButtonColor: '#ff0000',
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
cancelButtonText: 'Cancel',
|
||||||
showInfo={true}
|
}).then((result) => {
|
||||||
showPageSizeSelector={true}
|
if (result.isConfirmed) {
|
||||||
compact={false}
|
|
||||||
className="product-list-pagination"
|
MySwal.fire({
|
||||||
/>
|
title: 'Deleted!',
|
||||||
</>
|
text: 'Your file has been deleted.',
|
||||||
)}
|
className: "btn btn-success",
|
||||||
</div>
|
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>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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<AddCategoryList />
|
||||||
{/* /category list */}
|
<EditCategoryList />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
<AddCategoryList />
|
}
|
||||||
<EditCategoryList />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CategoryList;
|
export default CategoryList
|
||||||
|
|||||||
@ -1,388 +0,0 @@
|
|||||||
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,166 +1,115 @@
|
|||||||
import { useEffect, useState } from "react";
|
import React from 'react'
|
||||||
import { useParams } from "react-router-dom";
|
import ImageWithBasePath from '../../core/img/imagewithbasebath'
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
|
||||||
import productsApi from "../../services/productsApi";
|
|
||||||
import { formatRupiah } from "../../utils/currency";
|
|
||||||
|
|
||||||
const ProductDetail = () => {
|
const ProductDetail = () => {
|
||||||
const { id } = useParams();
|
return (
|
||||||
|
<div>
|
||||||
const [currentProduct, setCurrentProduct] = useState({});
|
<div className="page-wrapper">
|
||||||
|
<div className="content">
|
||||||
useEffect(() => {
|
<div className="page-header">
|
||||||
const fetchProduct = async () => {
|
<div className="page-title">
|
||||||
try {
|
<h4>Product Details</h4>
|
||||||
const response = await productsApi.getProductById(id);
|
<h6>Full details of a product</h6>
|
||||||
setCurrentProduct(response.data);
|
</div>
|
||||||
} 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>
|
{/* /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 */}
|
||||||
|
</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>
|
)
|
||||||
</div>
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProductDetail;
|
export default ProductDetail
|
||||||
|
|||||||
@ -1,33 +1,36 @@
|
|||||||
import { Select, Space } from "antd";
|
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
Edit,
|
Edit,
|
||||||
Eye,
|
Eye,
|
||||||
|
Filter,
|
||||||
|
GitMerge,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
|
Sliders,
|
||||||
|
StopCircle,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "feather-icons-react/build/IconComponents";
|
} from "feather-icons-react/build/IconComponents";
|
||||||
import { useEffect, useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
|
||||||
import { Download } from "react-feather";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Swal from "sweetalert2";
|
import Select from "react-select";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
|
||||||
import CustomPagination from "../../components/CustomPagination";
|
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
import Brand from "../../core/modals/inventory/brand";
|
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 Table from "../../core/pagination/datatable";
|
||||||
import { setToogleHeader } from "../../core/redux/action";
|
import { setToogleHeader } from "../../core/redux/action";
|
||||||
|
import { Download } from "react-feather";
|
||||||
import {
|
import {
|
||||||
clearProductError,
|
|
||||||
deleteProduct,
|
|
||||||
fetchProduct,
|
|
||||||
fetchProducts,
|
fetchProducts,
|
||||||
|
fetchProduct,
|
||||||
|
deleteProduct,
|
||||||
|
clearProductError
|
||||||
} from "../../core/redux/actions/productActions";
|
} from "../../core/redux/actions/productActions";
|
||||||
import { all_routes } from "../../Router/all_routes";
|
import CustomPagination from '../../components/CustomPagination';
|
||||||
import categoriesApi from "../../services/categoriesApi";
|
|
||||||
import { formatRupiah } from "../../utils/currency";
|
|
||||||
|
|
||||||
// Add CSS animations for beautiful UI
|
// Add CSS animations for beautiful UI
|
||||||
const shimmerKeyframes = `
|
const shimmerKeyframes = `
|
||||||
@ -59,16 +62,11 @@ const shimmerKeyframes = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Inject CSS into head if not already present
|
// Inject CSS into head if not already present
|
||||||
if (
|
if (typeof document !== 'undefined' && !document.getElementById('beautiful-pagination-styles')) {
|
||||||
typeof document !== "undefined" &&
|
const styleSheet = document.createElement('style');
|
||||||
!document.getElementById("beautiful-pagination-styles")
|
styleSheet.id = 'beautiful-pagination-styles';
|
||||||
) {
|
styleSheet.type = 'text/css';
|
||||||
const styleSheet = document.createElement("style");
|
styleSheet.innerText = shimmerKeyframes + `
|
||||||
styleSheet.id = "beautiful-pagination-styles";
|
|
||||||
styleSheet.type = "text/css";
|
|
||||||
styleSheet.innerText =
|
|
||||||
shimmerKeyframes +
|
|
||||||
`
|
|
||||||
/* Hide all Ant Design pagination elements */
|
/* Hide all Ant Design pagination elements */
|
||||||
.ant-pagination,
|
.ant-pagination,
|
||||||
.ant-pagination-item,
|
.ant-pagination-item,
|
||||||
@ -435,24 +433,40 @@ const ProductList = () => {
|
|||||||
totalProducts,
|
totalProducts,
|
||||||
totalPages,
|
totalPages,
|
||||||
pageSize: reduxPageSize,
|
pageSize: reduxPageSize,
|
||||||
currentPage: reduxCurrentPage,
|
currentPage: reduxCurrentPage
|
||||||
} = useSelector((state) => state.products);
|
} = useSelector((state) => state.products);
|
||||||
|
|
||||||
const dataSource = apiProducts?.length > 0 ? apiProducts : [];
|
// Fallback to legacy data if API data is not available
|
||||||
|
const legacyProducts = useSelector((state) => state.legacy?.product_list || []);
|
||||||
|
const dataSource = apiProducts.length > 0 ? apiProducts : legacyProducts;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const data = useSelector((state) => state.legacy?.toggle_header || false);
|
const data = useSelector((state) => state.legacy?.toggle_header || false);
|
||||||
|
|
||||||
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// State for pagination - sync with Redux
|
// State for pagination - sync with Redux
|
||||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
const [pageSize, setPageSize] = useState(reduxPageSize || 20);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
|
||||||
const [category, setCategory] = useState(null);
|
// State for filter values
|
||||||
|
const [filterValues, setFilterValues] = useState({
|
||||||
|
product: '',
|
||||||
|
category: '',
|
||||||
|
subCategory: '',
|
||||||
|
brand: '',
|
||||||
|
priceRange: ''
|
||||||
|
});
|
||||||
|
|
||||||
// Debounced search term
|
// Debounced search term
|
||||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||||
|
|
||||||
const [categoryOptions, setCategoryOptions] = useState([]);
|
const toggleFilterVisibility = () => {
|
||||||
|
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||||
|
};
|
||||||
|
|
||||||
const route = all_routes;
|
const route = all_routes;
|
||||||
|
|
||||||
@ -465,51 +479,29 @@ const ProductList = () => {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [searchTerm]);
|
}, [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
|
// Fetch products when debounced search term or pagination changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProducts = async () => {
|
const loadProducts = async () => {
|
||||||
try {
|
try {
|
||||||
const searchParams = {
|
const searchParams = {
|
||||||
page: currentPage,
|
Page: currentPage,
|
||||||
limit: pageSize,
|
PageSize: pageSize,
|
||||||
search: debouncedSearchTerm || "",
|
SearchTerm: debouncedSearchTerm || ''
|
||||||
category_id: category,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove empty parameters
|
// Remove empty parameters
|
||||||
const cleanParams = Object.fromEntries(
|
const cleanParams = Object.fromEntries(
|
||||||
Object.entries(searchParams).filter(([, value]) => value !== "")
|
Object.entries(searchParams).filter(([, value]) => value !== '')
|
||||||
);
|
);
|
||||||
|
|
||||||
await dispatch(fetchProducts(cleanParams));
|
await dispatch(fetchProducts(cleanParams));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load products:", error);
|
console.error('Failed to load products:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadProducts();
|
loadProducts();
|
||||||
}, [dispatch, currentPage, pageSize, debouncedSearchTerm, category]);
|
}, [dispatch, currentPage, pageSize, debouncedSearchTerm]);
|
||||||
|
|
||||||
// Handle product deletion
|
// Handle product deletion
|
||||||
const handleDeleteProduct = async (productId) => {
|
const handleDeleteProduct = async (productId) => {
|
||||||
@ -526,7 +518,7 @@ const ProductList = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete product:", error);
|
console.error('Failed to delete product:', error);
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
text: "Failed to delete product. Please try again.",
|
text: "Failed to delete product. Please try again.",
|
||||||
@ -550,12 +542,74 @@ const ProductList = () => {
|
|||||||
// Handle pagination
|
// Handle pagination
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = (page) => {
|
||||||
setCurrentPage(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
|
// Handle page size change
|
||||||
const handlePageSizeChange = (newPageSize) => {
|
const handlePageSizeChange = (newPageSize) => {
|
||||||
setPageSize(newPageSize);
|
setPageSize(newPageSize);
|
||||||
setCurrentPage(1); // Reset to first page when changing page size
|
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
|
// Calculate pagination info
|
||||||
@ -563,95 +617,85 @@ const ProductList = () => {
|
|||||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||||
|
|
||||||
|
// Debug logs removed for production
|
||||||
|
|
||||||
// Clear error when component unmounts
|
// Clear error when component unmounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(clearProductError());
|
dispatch(clearProductError());
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{ label: "Sort By: Last 7 Days", value: "last7days" },
|
{ value: "sortByDate", label: "Sort by Date" },
|
||||||
{ label: "Sort By: Last Month", value: "lastmonth" },
|
{ value: "140923", label: "14 09 23" },
|
||||||
{ label: "Sort By: Ascending", value: "ascending" },
|
{ value: "110923", label: "11 09 23" },
|
||||||
{ label: "Sort By: Descending", value: "descending" },
|
|
||||||
];
|
];
|
||||||
|
// Removed unused select option arrays since we're using simple inputs now
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "SKU",
|
title: "Sản phẩ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: "Product",
|
|
||||||
dataIndex: "product",
|
dataIndex: "product",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span className="productimgname">
|
<span className="productimgname">
|
||||||
<Link className="product-img stock-img">
|
<Link to="/profile" className="product-img stock-img">
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath
|
||||||
alt={"jpg"}
|
alt={record.name || text || "Product"}
|
||||||
src={record.image_url}
|
src={record.productImage || record.image || record.img}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="fw-medium text-black">{record.name}</Link>
|
<Link to="/profile">{text}</Link>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.product.length - b.product.length,
|
sorter: (a, b) => a.product.length - b.product.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Category",
|
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",
|
||||||
dataIndex: "category",
|
dataIndex: "category",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const category = record.category || record.categoryName || "-";
|
const category = record.category || record.categoryName || '-';
|
||||||
return <span>{category}</span>;
|
return <span>{category}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const catA = a.category || a.categoryName || "";
|
const catA = a.category || a.categoryName || '';
|
||||||
const catB = b.category || b.categoryName || "";
|
const catB = b.category || b.categoryName || '';
|
||||||
return catA.length - catB.length;
|
return catA.length - catB.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Business",
|
title: "Thương hiệu",
|
||||||
dataIndex: "business",
|
dataIndex: "brand",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const brand = record.business_type || record.brandName || "-";
|
const brand = record.brand || record.brandName || '-';
|
||||||
return <span>{brand}</span>;
|
return <span>{brand}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const brandA = a.brand || a.brandName || "";
|
const brandA = a.brand || a.brandName || '';
|
||||||
const brandB = b.brand || b.brandName || "";
|
const brandB = b.brand || b.brandName || '';
|
||||||
return brandA.length - brandB.length;
|
return brandA.length - brandB.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Printer",
|
title: "Giá",
|
||||||
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",
|
dataIndex: "price",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const price = record.price || record.salePrice || record.unitPrice || 0;
|
const price = record.price || record.salePrice || record.unitPrice || 0;
|
||||||
return <span>{formatRupiah(Number(price))}</span>;
|
return <span>${Number(price).toFixed(2)}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
|
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
|
||||||
@ -660,30 +704,54 @@ const ProductList = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Cost",
|
title: "Đơn vị",
|
||||||
dataIndex: "cost",
|
dataIndex: "unit",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const cost = record.cost || 0;
|
const unit = record.unit || record.unitOfMeasure || '-';
|
||||||
return <span>{formatRupiah(Number(cost))}</span>;
|
return <span>{unit}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const priceA = Number(a.price || a.salePrice || a.unitPrice || 0);
|
const unitA = a.unit || a.unitOfMeasure || '';
|
||||||
const priceB = Number(b.price || b.salePrice || b.unitPrice || 0);
|
const unitB = b.unit || b.unitOfMeasure || '';
|
||||||
return priceA - priceB;
|
return unitA.length - unitB.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Action",
|
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",
|
||||||
dataIndex: "action",
|
dataIndex: "action",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<td className="action-table-data">
|
<td className="action-table-data">
|
||||||
<div className="edit-delete-action">
|
<div className="edit-delete-action">
|
||||||
<div className="input-block add-lists"></div>
|
<div className="input-block add-lists"></div>
|
||||||
<Link
|
<Link className="me-2 p-2" to={route.productdetails}>
|
||||||
className="me-2 p-2"
|
<Eye className="feather-view" />
|
||||||
to={`${route.productdetails}/${record.id || record.key}`}
|
|
||||||
>
|
|
||||||
<Eye className="feather-edit" />
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
className="me-2 p-2"
|
className="me-2 p-2"
|
||||||
@ -727,6 +795,8 @@ const ProductList = () => {
|
|||||||
];
|
];
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
// Removed showConfirmationAlert as we handle confirmation inline
|
||||||
|
|
||||||
const renderTooltip = (props) => (
|
const renderTooltip = (props) => (
|
||||||
<Tooltip id="pdf-tooltip" {...props}>
|
<Tooltip id="pdf-tooltip" {...props}>
|
||||||
Pdf
|
Pdf
|
||||||
@ -752,15 +822,14 @@ const ProductList = () => {
|
|||||||
Collapse
|
Collapse
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<div className="add-item d-flex">
|
<div className="add-item d-flex">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
<h4>Product List</h4>
|
<h4>Danh sách sản phẩm</h4>
|
||||||
<h6>Manage your products</h6>
|
<h6>Quản lý sản phẩm</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul className="table-top-head">
|
<ul className="table-top-head">
|
||||||
@ -815,17 +884,18 @@ const ProductList = () => {
|
|||||||
<div className="page-btn">
|
<div className="page-btn">
|
||||||
<Link to={route.addproduct} className="btn btn-added">
|
<Link to={route.addproduct} className="btn btn-added">
|
||||||
<PlusCircle className="me-2 iconsize" />
|
<PlusCircle className="me-2 iconsize" />
|
||||||
Add New Product
|
Thêm mới
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-btn import">
|
<div className="page-btn import">
|
||||||
<Link
|
<Link
|
||||||
className="btn btn-outline-primary rounded-2"
|
to="#"
|
||||||
|
className="btn btn-added color"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#view-notes"
|
data-bs-target="#view-notes"
|
||||||
>
|
>
|
||||||
<Download className="me-2 iconsize" />
|
<Download className="me-2" />
|
||||||
Import Product
|
Nhập sản phẩm
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -847,28 +917,291 @@ const ProductList = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="search-path">
|
||||||
<Space warp>
|
<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
|
<Select
|
||||||
style={{ height: 36, width: 100 }}
|
className="select"
|
||||||
placeholder={"Category"}
|
|
||||||
options={categoryOptions}
|
|
||||||
value={
|
|
||||||
categoryOptions.find(
|
|
||||||
(option) => option.value === category
|
|
||||||
) || null
|
|
||||||
}
|
|
||||||
onChange={(selectedOption) => setCategory(selectedOption)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Select
|
|
||||||
style={{ height: 36 }}
|
|
||||||
defaultValue={options[0]?.value}
|
|
||||||
options={options}
|
options={options}
|
||||||
|
placeholder="14 09 23"
|
||||||
/>
|
/>
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* /Filter */}
|
||||||
|
<div
|
||||||
|
className={`card${isFilterVisible ? " visible" : ""}`}
|
||||||
|
id="filter_inputs"
|
||||||
|
style={{ display: isFilterVisible ? "block" : "none" }}
|
||||||
|
>
|
||||||
|
<div className="card-body pb-0">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-lg-12 col-sm-12">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-lg-2 col-sm-6 col-12">
|
||||||
|
<div className="input-blocks 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">
|
<div className="table-responsive">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-center p-4">
|
<div className="text-center p-4">
|
||||||
|
|||||||
@ -1,48 +1,16 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
|
||||||
import ImageWithBasePath from "../../../core/img/imagewithbasebath";
|
import ImageWithBasePath from "../../../core/img/imagewithbasebath";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { all_routes } from "../../../Router/all_routes";
|
import { all_routes } from "../../../Router/all_routes";
|
||||||
import authApi from "../../../services/authApi";
|
|
||||||
|
|
||||||
const Signin = () => {
|
const Signin = () => {
|
||||||
const route = all_routes;
|
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 (
|
return (
|
||||||
<div className="main-wrapper">
|
<div className="main-wrapper">
|
||||||
<div className="account-content">
|
<div className="account-content">
|
||||||
<div className="login-wrapper bg-img">
|
<div className="login-wrapper bg-img">
|
||||||
<div className="login-content">
|
<div className="login-content">
|
||||||
<form onSubmit={handleSubmit}>
|
<form action="index">
|
||||||
<div className="login-userset">
|
<div className="login-userset">
|
||||||
<div className="login-logo logo-normal">
|
<div className="login-logo logo-normal">
|
||||||
<ImageWithBasePath src="assets/img/logo.png" alt="img" />
|
<ImageWithBasePath src="assets/img/logo.png" alt="img" />
|
||||||
@ -56,22 +24,10 @@ const Signin = () => {
|
|||||||
Access the Dreamspos panel using your email and passcode.
|
Access the Dreamspos panel using your email and passcode.
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
|
||||||
<div className="alert alert-danger" role="alert">
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="form-login mb-3">
|
<div className="form-login mb-3">
|
||||||
<label className="form-label">Email Address</label>
|
<label className="form-label">Email Address</label>
|
||||||
<div className="form-addons">
|
<div className="form-addons">
|
||||||
<input
|
<input type="text" className="form- control" />
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath
|
||||||
src="assets/img/icons/mail.svg"
|
src="assets/img/icons/mail.svg"
|
||||||
alt="img"
|
alt="img"
|
||||||
@ -83,11 +39,7 @@ const Signin = () => {
|
|||||||
<div className="pass-group">
|
<div className="pass-group">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
|
||||||
value={formData.password}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
className="pass-input form-control"
|
className="pass-input form-control"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<span className="fas toggle-password fa-eye-slash" />
|
<span className="fas toggle-password fa-eye-slash" />
|
||||||
</div>
|
</div>
|
||||||
@ -111,13 +63,9 @@ const Signin = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-login">
|
<div className="form-login">
|
||||||
<button
|
<Link to={route.dashboard} className="btn btn-login">
|
||||||
type="submit"
|
Sign In
|
||||||
className="btn btn-login"
|
</Link>
|
||||||
disabled={authState.loading}
|
|
||||||
>
|
|
||||||
{authState.loading ? 'Signing In...' : 'Sign In'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="signinform">
|
<div className="signinform">
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const { user } = useSelector((state) => state.auth);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -30,19 +27,17 @@ const Profile = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="profileupload">
|
<div className="profileupload">
|
||||||
<input type="file" id="imgInp" />
|
<input type="file" id="imgInp" />
|
||||||
<Link to="#">
|
<Link to="#">
|
||||||
<ImageWithBasePath
|
<ImageWithBasePath src="assets/img/icons/edit-set.svg" alt="img" />
|
||||||
src="assets/img/icons/edit-set.svg"
|
|
||||||
alt="img"
|
|
||||||
/>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="profile-contentname">
|
<div className="profile-contentname">
|
||||||
<h2>{user?.name}</h2>
|
<h2>William Castillo</h2>
|
||||||
<h4>Updates Your Photo and Personal Details.</h4>
|
<h4>Updates Your Photo and Personal Details.</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -73,7 +68,6 @@ const Profile = () => {
|
|||||||
type="email"
|
type="email"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
defaultValue="william@example.com"
|
defaultValue="william@example.com"
|
||||||
value={user?.email}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -106,10 +100,10 @@ const Profile = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<Link to="#" className="btn btn-submit me-2">
|
<Link to="#" className="btn btn-submit me-2">
|
||||||
Submit
|
Submit
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="#" className="btn btn-cancel">
|
<Link to="#" className="btn btn-cancel">
|
||||||
Cancel
|
Cancel
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,181 +1,115 @@
|
|||||||
import { Tag } from "antd";
|
import React, { useState } from "react";
|
||||||
import { useEffect, useState } from "react";
|
import Breadcrumbs from "../../core/breadcrumbs";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
import { Filter, Sliders } from "react-feather";
|
||||||
import { Edit, Trash2 } from "react-feather";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import Select from "react-select";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
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 Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import CustomPagination from "../../components/CustomPagination";
|
|
||||||
import Breadcrumbs from "../../core/breadcrumbs";
|
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
|
||||||
import ManageStockModal from "../../core/modals/stocks/managestockModal";
|
|
||||||
import Table from "../../core/pagination/datatable";
|
import Table from "../../core/pagination/datatable";
|
||||||
import {
|
import { useSelector } from "react-redux";
|
||||||
deleteInventory,
|
|
||||||
fetchInventories,
|
|
||||||
INVENTORY_ACTIONS,
|
|
||||||
} from "../../core/redux/actions/inventoryActions";
|
|
||||||
import { formatDate } from "../../utils/date";
|
|
||||||
|
|
||||||
const Managestock = () => {
|
const Managestock = () => {
|
||||||
const {
|
const data = useSelector((state) => state.managestockdata);
|
||||||
inventories: apiInventories,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
totalInventories,
|
|
||||||
totalPages,
|
|
||||||
pageSize: reduxPageSize,
|
|
||||||
currentPage: reduxCurrentPage,
|
|
||||||
} = useSelector((state) => state.inventories);
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
const dataSource = apiInventories?.length > 0 ? apiInventories : [];
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
|
|
||||||
const [params, setParams] = useState({
|
const toggleFilterVisibility = () => {
|
||||||
page: reduxCurrentPage || 1,
|
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||||
limit: reduxPageSize || 10,
|
};
|
||||||
});
|
const handleDateChange = (date) => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
setSelectedDate(date);
|
||||||
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 handleSearch = (e) => {
|
const options = [
|
||||||
const value = e.target.value;
|
{ value: "sortByDate", label: "Sort by Date" },
|
||||||
setSearchTerm(value);
|
{ value: "140923", label: "14 09 23" },
|
||||||
};
|
{ value: "110923", label: "11 09 23" },
|
||||||
|
];
|
||||||
|
|
||||||
// Handle pagination
|
const warehouseOptions = [
|
||||||
const handlePageChange = (page) => {
|
{ value: "Choose Warehouse", label: "Choose Warehouse" },
|
||||||
handleSetParams("page", page);
|
{ 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 page size change
|
const productOptions = [
|
||||||
const handlePageSizeChange = (newPageSize) => {
|
{ value: "Choose Product", label: "Choose Product" },
|
||||||
handleSetParams("limit", newPageSize);
|
{ value: "Nike Jordan", label: "Nike Jordan" },
|
||||||
handleSetParams("page", 1);
|
{ value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" },
|
||||||
};
|
{ value: "Amazon Echo Dot", label: "Amazon Echo Dot" },
|
||||||
|
{ value: "Lobar Handy", label: "Lobar Handy" },
|
||||||
|
];
|
||||||
|
|
||||||
const handleDelete = async (id) => {
|
const personOptions = [
|
||||||
try {
|
{ value: "Choose Person", label: "Choose Person" },
|
||||||
await dispatch(deleteInventory(id));
|
{ value: "Steven", label: "Steven" },
|
||||||
|
{ value: "Gravely", label: "Gravely" },
|
||||||
MySwal.fire({
|
];
|
||||||
title: "Deleted!",
|
|
||||||
text: "Your stock has been deleted.",
|
|
||||||
className: "btn btn-success",
|
|
||||||
confirmButtonText: "OK",
|
|
||||||
customClass: {
|
|
||||||
confirmButton: "btn btn-success",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to delete inventory", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate pagination info
|
|
||||||
const totalRecords = totalInventories || dataSource.length;
|
|
||||||
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
|
||||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Outlet",
|
title: "Warehouse",
|
||||||
dataIndex: "outlet",
|
dataIndex: "Warehouse",
|
||||||
render: (_, record) => <span>{record.outlet || "-"}</span>,
|
|
||||||
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Shop",
|
||||||
|
dataIndex: "Shop",
|
||||||
|
sorter: (a, b) => a.Shop.length - b.Shop.length,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Product",
|
title: "Product",
|
||||||
dataIndex: "Product",
|
dataIndex: "Product",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span className="userimgname">
|
<span className="userimgname">
|
||||||
<Link to="#" className="product-img">
|
<Link to="#" className="product-img">
|
||||||
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
|
<ImageWithBasePath alt="img" src={record.Product.Image} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="#">{record.product_name || ""}</Link>
|
<Link to="#">{record.Product.Name}</Link>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Qty",
|
title: "Date",
|
||||||
dataIndex: "qty",
|
dataIndex: "Date",
|
||||||
render: (_, record) => <span>{record.quantity || 0}</span>,
|
|
||||||
sorter: (a, b) => a.Email.length - b.Email.length,
|
sorter: (a, b) => a.Email.length - b.Email.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Status",
|
title: "Person",
|
||||||
dataIndex: "status",
|
dataIndex: "Person",
|
||||||
render: (_, record) => (
|
render: (text, record) => (
|
||||||
<Tag color={record.is_low_stock ? "red" : "green"}>
|
<span className="userimgname">
|
||||||
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
|
<Link to="#" className="product-img">
|
||||||
</Tag>
|
<ImageWithBasePath alt="img" src={record.Person.Image} />
|
||||||
|
</Link>
|
||||||
|
<Link to="#">{record.Person.Name}</Link>
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Record Level",
|
title: "Quantity",
|
||||||
dataIndex: "recordlevel",
|
dataIndex: "Quantity",
|
||||||
render: (_, record) => <span>{record.record_level || "-"}</span>,
|
|
||||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Updated Date",
|
|
||||||
dataIndex: "updateddate",
|
|
||||||
render: (_, record) => (
|
|
||||||
<span>{formatDate(record.updated_at) || "-"}</span>
|
|
||||||
),
|
|
||||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Action",
|
title: "Action",
|
||||||
dataIndex: "action",
|
dataIndex: "action",
|
||||||
render: (_, record) => (
|
render: () => (
|
||||||
<td className="action-table-data">
|
<td className="action-table-data">
|
||||||
<div className="edit-delete-action">
|
<div className="edit-delete-action">
|
||||||
<div className="input-block add-lists"></div>
|
<div className="input-block add-lists"></div>
|
||||||
@ -185,12 +119,6 @@ const Managestock = () => {
|
|||||||
to="#"
|
to="#"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#edit-units"
|
data-bs-target="#edit-units"
|
||||||
onClick={() =>
|
|
||||||
dispatch({
|
|
||||||
type: INVENTORY_ACTIONS.FETCH_INVENTORY_SUCCESS,
|
|
||||||
payload: { data: record },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Edit className="feather-edit" />
|
<Edit className="feather-edit" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -198,7 +126,7 @@ const Managestock = () => {
|
|||||||
<Link
|
<Link
|
||||||
className="confirm-text p-2"
|
className="confirm-text p-2"
|
||||||
to="#"
|
to="#"
|
||||||
onClick={() => showConfirmationAlert(record.id)}
|
onClick={showConfirmationAlert}
|
||||||
>
|
>
|
||||||
<Trash2 className="feather-trash-2" />
|
<Trash2 className="feather-trash-2" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -211,7 +139,7 @@ const Managestock = () => {
|
|||||||
|
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
const showConfirmationAlert = (id) => {
|
const showConfirmationAlert = () => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Are you sure?",
|
title: "Are you sure?",
|
||||||
text: "You won't be able to revert this!",
|
text: "You won't be able to revert this!",
|
||||||
@ -222,13 +150,20 @@ const Managestock = () => {
|
|||||||
cancelButtonText: "Cancel",
|
cancelButtonText: "Cancel",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
handleDelete(id);
|
MySwal.fire({
|
||||||
|
title: "Deleted!",
|
||||||
|
text: "Your file has been deleted.",
|
||||||
|
className: "btn btn-success",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
confirmButton: "btn btn-success",
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
MySwal.close();
|
MySwal.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -247,58 +182,117 @@ const Managestock = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="form-control form-control-sm formsearch"
|
className="form-control form-control-sm formsearch"
|
||||||
value={searchTerm}
|
|
||||||
onChange={handleSearch}
|
|
||||||
/>
|
/>
|
||||||
<Link to className="btn btn-searchset">
|
<Link to className="btn btn-searchset">
|
||||||
<i data-feather="search" className="feather-search" />
|
<i data-feather="search" className="feather-search" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="search-path">
|
||||||
<div className="table-responsive">
|
<Link
|
||||||
{loading ? (
|
className={`btn btn-filter ${
|
||||||
<div className="text-center p-4">
|
isFilterVisible ? "setclose" : ""
|
||||||
<div className="spinner-border text-primary" role="status">
|
}`}
|
||||||
<span className="visually-hidden">Loading...</span>
|
id="filter_search"
|
||||||
</div>
|
>
|
||||||
<p className="mt-2">Loading products...</p>
|
<Filter
|
||||||
</div>
|
className="filter-icon"
|
||||||
) : error ? (
|
onClick={toggleFilterVisibility}
|
||||||
<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}
|
|
||||||
/>
|
/>
|
||||||
|
<span onClick={toggleFilterVisibility}>
|
||||||
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/closes.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="form-sort stylewidth">
|
||||||
|
<Sliders className="info-img" />
|
||||||
|
|
||||||
<CustomPagination
|
<Select
|
||||||
currentPage={params.page}
|
className="select "
|
||||||
pageSize={params.limit}
|
options={options}
|
||||||
totalCount={totalRecords}
|
placeholder="Sort by Date"
|
||||||
totalPages={actualTotalPages}
|
/>
|
||||||
loading={loading}
|
</div>
|
||||||
onPageChange={handlePageChange}
|
</div>
|
||||||
onPageSizeChange={handlePageSizeChange}
|
{/* /Filter */}
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
<div
|
||||||
showInfo={true}
|
className={`card${isFilterVisible ? " visible" : ""}`}
|
||||||
showPageSizeSelector={true}
|
id="filter_inputs"
|
||||||
compact={false}
|
style={{ display: isFilterVisible ? "block" : "none" }}
|
||||||
className="product-list-pagination"
|
>
|
||||||
/>
|
<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}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,153 +1,118 @@
|
|||||||
import { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
import Breadcrumbs from "../../core/breadcrumbs";
|
||||||
import { Edit, Trash2 } from "react-feather";
|
import { Filter, Sliders } from "react-feather";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
|
import Select from "react-select";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
import { Archive, Box, Calendar, User, Edit, Trash2 } from "react-feather";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import CustomPagination from "../../components/CustomPagination";
|
|
||||||
import Breadcrumbs from "../../core/breadcrumbs";
|
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
|
||||||
import StockadjustmentModal from "../../core/modals/stocks/stockadjustmentModal";
|
|
||||||
import Table from "../../core/pagination/datatable";
|
import Table from "../../core/pagination/datatable";
|
||||||
import { fetchInventories } from "../../core/redux/actions/inventoryActions";
|
import StockadjustmentModal from "../../core/modals/stocks/stockadjustmentModal";
|
||||||
import { formatDate } from "../../utils/date";
|
import { useSelector } from "react-redux";
|
||||||
import { Tag } from "antd";
|
|
||||||
|
|
||||||
const StockAdjustment = () => {
|
const StockAdjustment = () => {
|
||||||
const {
|
const data = useSelector((state) => state.managestockdata);
|
||||||
inventories: apiInventories,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
totalInventories,
|
|
||||||
totalPages,
|
|
||||||
pageSize: reduxPageSize,
|
|
||||||
currentPage: reduxCurrentPage,
|
|
||||||
} = useSelector((state) => state.inventories);
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
const dataSource = apiInventories?.length > 0 ? apiInventories : [];
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
|
|
||||||
const [params, setParams] = useState({
|
const toggleFilterVisibility = () => {
|
||||||
page: reduxCurrentPage || 1,
|
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||||
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 handleSearch = (e) => {
|
const handleDateChange = (date) => {
|
||||||
const value = e.target.value;
|
setSelectedDate(date);
|
||||||
setSearchTerm(value);
|
|
||||||
};
|
};
|
||||||
|
const options = [
|
||||||
|
{ value: "sortByDate", label: "Sort by Date" },
|
||||||
|
{ value: "140923", label: "14 09 23" },
|
||||||
|
{ value: "110923", label: "11 09 23" },
|
||||||
|
];
|
||||||
|
|
||||||
// Handle pagination
|
const warehouseOptions = [
|
||||||
const handlePageChange = (page) => {
|
{ value: "Choose Warehouse", label: "Choose Warehouse" },
|
||||||
handleSetParams("page", page);
|
{ 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 page size change
|
const productOptions = [
|
||||||
const handlePageSizeChange = (newPageSize) => {
|
{ value: "Choose Product", label: "Choose Product" },
|
||||||
handleSetParams("limit", newPageSize);
|
{ value: "Nike Jordan", label: "Nike Jordan" },
|
||||||
handleSetParams("page", 1);
|
{ value: "Apple Series 5 Watch", label: "Apple Series 5 Watch" },
|
||||||
};
|
{ value: "Amazon Echo Dot", label: "Amazon Echo Dot" },
|
||||||
|
{ value: "Lobar Handy", label: "Lobar Handy" },
|
||||||
|
];
|
||||||
|
|
||||||
// Calculate pagination info
|
const personOptions = [
|
||||||
const totalRecords = totalInventories || dataSource.length;
|
{ value: "Choose Person", label: "Choose Person" },
|
||||||
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
{ value: "Steven", label: "Steven" },
|
||||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
{ value: "Gravely", label: "Gravely" },
|
||||||
|
];
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Outlet",
|
title: "Warehouse",
|
||||||
dataIndex: "outlet",
|
dataIndex: "Warehouse",
|
||||||
render: (_, record) => <span>{record.outlet || "-"}</span>,
|
|
||||||
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
sorter: (a, b) => a.Warehouse.length - b.Warehouse.length,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Shop",
|
||||||
|
dataIndex: "Shop",
|
||||||
|
sorter: (a, b) => a.Shop.length - b.Shop.length,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Product",
|
title: "Product",
|
||||||
dataIndex: "Product",
|
dataIndex: "Product",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span className="userimgname">
|
<span className="userimgname">
|
||||||
<Link to="#" className="product-img">
|
<Link to="#" className="product-img">
|
||||||
<ImageWithBasePath alt="img" src={record.product_image_url || ""} />
|
<ImageWithBasePath alt="img" src={record.Product.Image} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="#">{record.product_name || ""}</Link>
|
<Link to="#">{record.Product.Name}</Link>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
sorter: (a, b) => a.Product.Name.length - b.Product.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Qty",
|
title: "Date",
|
||||||
dataIndex: "qty",
|
dataIndex: "Date",
|
||||||
render: (_, record) => <span>{record.quantity || 0}</span>,
|
|
||||||
sorter: (a, b) => a.Email.length - b.Email.length,
|
sorter: (a, b) => a.Email.length - b.Email.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Status",
|
title: "Person",
|
||||||
dataIndex: "status",
|
dataIndex: "Person",
|
||||||
render: (_, record) => (
|
render: (text, record) => (
|
||||||
<Tag color={record.is_low_stock ? "red" : "green"}>
|
<span className="userimgname">
|
||||||
{record.is_low_stock ? "Low Stock" : "Normal Stock"}
|
<Link to="#" className="product-img">
|
||||||
</Tag>
|
<ImageWithBasePath alt="img" src={record.Person.Image} />
|
||||||
|
</Link>
|
||||||
|
<Link to="#">{record.Person.Name}</Link>
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
sorter: (a, b) => a.Person.Name.length - b.Person.Name.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Record Level",
|
title: "Notes",
|
||||||
dataIndex: "recordlevel",
|
// dataIndex: "Quantity",
|
||||||
render: (_, record) => <span>{record.record_level || "-"}</span>,
|
render: () => (
|
||||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
<Link
|
||||||
},
|
to="#"
|
||||||
{
|
className="view-note"
|
||||||
title: "Updated Date",
|
data-bs-toggle="modal"
|
||||||
dataIndex: "updateddate",
|
data-bs-target="#view-notes"
|
||||||
render: (_, record) => (
|
>
|
||||||
<span>{formatDate(record.updated_at) || "-"}</span>
|
View Note
|
||||||
|
</Link>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.Quantity.length - b.Quantity.length,
|
sorter: (a, b) => a.Notes.length - b.Notes.length,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -226,58 +191,115 @@ const StockAdjustment = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="form-control form-control-sm formsearch"
|
className="form-control form-control-sm formsearch"
|
||||||
name="search"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={handleSearch}
|
|
||||||
/>
|
/>
|
||||||
<Link to className="btn btn-searchset">
|
<Link to className="btn btn-searchset">
|
||||||
<i data-feather="search" className="feather-search" />
|
<i data-feather="search" className="feather-search" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="search-path">
|
||||||
<div className="table-responsive">
|
<Link
|
||||||
{loading ? (
|
className={`btn btn-filter ${
|
||||||
<div className="text-center p-4">
|
isFilterVisible ? "setclose" : ""
|
||||||
<div className="spinner-border text-primary" role="status">
|
}`}
|
||||||
<span className="visually-hidden">Loading...</span>
|
id="filter_search"
|
||||||
</div>
|
>
|
||||||
<p className="mt-2">Loading products...</p>
|
<Filter
|
||||||
</div>
|
className="filter-icon"
|
||||||
) : error ? (
|
onClick={toggleFilterVisibility}
|
||||||
<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}
|
|
||||||
/>
|
/>
|
||||||
|
<span onClick={toggleFilterVisibility}>
|
||||||
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/closes.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="form-sort stylewidth">
|
||||||
|
<Sliders className="info-img" />
|
||||||
|
|
||||||
<CustomPagination
|
<Select
|
||||||
currentPage={params.page}
|
className="select "
|
||||||
pageSize={params.limit}
|
options={options}
|
||||||
totalCount={totalRecords}
|
placeholder="Sort by Date"
|
||||||
totalPages={actualTotalPages}
|
/>
|
||||||
loading={loading}
|
</div>
|
||||||
onPageChange={handlePageChange}
|
</div>
|
||||||
onPageSizeChange={handlePageSizeChange}
|
{/* /Filter */}
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
<div
|
||||||
showInfo={true}
|
className={`card${isFilterVisible ? " visible" : ""}`}
|
||||||
showPageSizeSelector={true}
|
id="filter_inputs"
|
||||||
compact={false}
|
style={{ display: isFilterVisible ? "block" : "none" }}
|
||||||
className="product-list-pagination"
|
>
|
||||||
/>
|
<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}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,396 +0,0 @@
|
|||||||
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,396 +1,354 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import ImageWithBasePath from '../../core/img/imagewithbasebath';
|
||||||
import { ChevronUp, RotateCcw } from "feather-icons-react/build/IconComponents";
|
import { ChevronUp, RotateCcw } from 'feather-icons-react/build/IconComponents';
|
||||||
import { setToogleHeader } from "../../core/redux/action";
|
import { setToogleHeader } from '../../core/redux/action';
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import {
|
import { Filter, PlusCircle, Sliders, StopCircle, User, Zap } from 'react-feather';
|
||||||
Filter,
|
import Select from 'react-select';
|
||||||
PlusCircle,
|
import withReactContent from 'sweetalert2-react-content';
|
||||||
Sliders,
|
import Swal from 'sweetalert2';
|
||||||
StopCircle,
|
import Table from '../../core/pagination/datatable'
|
||||||
User,
|
import AddUsers from '../../core/modals/usermanagement/addusers';
|
||||||
Zap,
|
import EditUser from '../../core/modals/usermanagement/edituser';
|
||||||
} 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 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 dispatch = useDispatch();
|
const oldandlatestvalue = [
|
||||||
const data = useSelector((state) => state.toggle_header);
|
{ value: 'date', label: 'Sort by Date' },
|
||||||
const [dataSource, setDataSource] = useState([])
|
{ value: 'newest', label: 'Newest' },
|
||||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
{ value: 'oldest', label: 'Oldest' },
|
||||||
const toggleFilterVisibility = () => {
|
];
|
||||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
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' },
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadUsers = async () => {
|
|
||||||
try {
|
|
||||||
const response = await usersApi.getAllUsers();
|
|
||||||
setDataSource(response)
|
|
||||||
|
|
||||||
console.log('response', response)
|
const dispatch = useDispatch();
|
||||||
} catch (error) {
|
const data = useSelector((state) => state.toggle_header);
|
||||||
console.error("Failed to fetch users", error);
|
const dataSource = useSelector((state) => state.userlist_data);
|
||||||
}
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
|
const toggleFilterVisibility = () => {
|
||||||
|
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadUsers();
|
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 renderTooltip = (props) => (
|
const columns = [
|
||||||
<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 columns = [
|
{
|
||||||
{
|
title: "User Name",
|
||||||
title: "User Name",
|
dataIndex: "username",
|
||||||
dataIndex: "username",
|
render: (text, record) => (
|
||||||
render: (text, record) => (
|
<span className="userimgname">
|
||||||
<span className="userimgname">
|
<Link to="#" className="userslist-img bg-img">
|
||||||
<Link to="#" className="userslist-img bg-img">
|
<ImageWithBasePath alt="" src={record.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 (
|
|
||||||
<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>
|
</Link>
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<Link to="#">{text}</Link>
|
||||||
<div className="search-path">
|
</div>
|
||||||
<Link
|
</span>
|
||||||
className={`btn btn-filter ${
|
),
|
||||||
isFilterVisible ? "setclose" : ""
|
sorter: (a, b) => a.username.length - b.username.length,
|
||||||
}`}
|
},
|
||||||
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"
|
title: "Phone",
|
||||||
options={users}
|
dataIndex: "phone",
|
||||||
placeholder="Newest"
|
sorter: (a, b) => a.phone.length - b.phone.length,
|
||||||
/>
|
},
|
||||||
</div>
|
{
|
||||||
</div>
|
title: "Email",
|
||||||
<div className="col-lg-3 col-sm-6 col-12">
|
dataIndex: "email",
|
||||||
<div className="input-blocks">
|
sorter: (a, b) => a.email.length - b.email.length,
|
||||||
<StopCircle className="info-img" />
|
},
|
||||||
|
{
|
||||||
<Select
|
title: "Role",
|
||||||
className="select"
|
dataIndex: "role",
|
||||||
options={status}
|
sorter: (a, b) => a.role.length - b.role.length,
|
||||||
placeholder="Choose Status"
|
},
|
||||||
/>
|
{
|
||||||
</div>
|
title: "Created On",
|
||||||
</div>
|
dataIndex: "createdon",
|
||||||
<div className="col-lg-3 col-sm-6 col-12">
|
sorter: (a, b) => a.createdon.length - b.createdon.length,
|
||||||
<div className="input-blocks">
|
},
|
||||||
<Zap className="info-img" />
|
{
|
||||||
|
title: "Status",
|
||||||
<Select
|
dataIndex: "status",
|
||||||
className="select"
|
render: (text) => (
|
||||||
options={role}
|
<div>
|
||||||
placeholder="Choose Role"
|
{text === "Active" && (
|
||||||
/>
|
<span className="badge badge-linesuccess">{text}</span>
|
||||||
</div>
|
)}
|
||||||
</div>
|
{text === "Inactive" && (
|
||||||
<div className="col-lg-3 col-sm-6 col-12">
|
<span className="badge badge-linedanger">{text}</span>
|
||||||
<div className="input-blocks">
|
)}
|
||||||
<a className="btn btn-filters ms-auto">
|
|
||||||
{" "}
|
</div>
|
||||||
<i
|
),
|
||||||
data-feather="search"
|
sorter: (a, b) => a.status.length - b.status.length,
|
||||||
className="feather-search"
|
},
|
||||||
/>{" "}
|
{
|
||||||
Search{" "}
|
title: 'Actions',
|
||||||
</a>
|
dataIndex: 'actions',
|
||||||
</div>
|
key: 'actions',
|
||||||
</div>
|
render: () => (
|
||||||
</div>
|
<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 (
|
||||||
|
<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>
|
||||||
</div>
|
|
||||||
{/* /Filter */}
|
|
||||||
<div className="table-responsive">
|
|
||||||
<Table columns={columns} dataSource={dataSource} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<AddUsers/>
|
||||||
{/* /product list */}
|
<EditUser/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
<AddUsers />
|
}
|
||||||
<EditUser />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Users;
|
export default Users
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import '../src/style/icons/fontawesome/css/all.min.css'
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import store from "./core/redux/store.jsx";
|
import store from "./core/redux/store.jsx";
|
||||||
import AllRoutes from "./Router/router.jsx";
|
import AllRoutes from "./Router/router.jsx";
|
||||||
import authApi from "./services/authApi";
|
|
||||||
|
|
||||||
const rootElement = document.getElementById('root');
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
@ -50,9 +49,6 @@ const initializeTheme = () => {
|
|||||||
// Initialize theme before rendering
|
// Initialize theme before rendering
|
||||||
initializeTheme();
|
initializeTheme();
|
||||||
|
|
||||||
// Check authentication status on app startup
|
|
||||||
authApi.checkAuthStatus();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (rootElement) {
|
if (rootElement) {
|
||||||
|
|||||||
@ -41,7 +41,6 @@ api.interceptors.response.use(
|
|||||||
// Unauthorized - redirect to login or refresh token
|
// Unauthorized - redirect to login or refresh token
|
||||||
localStorage.removeItem('authToken');
|
localStorage.removeItem('authToken');
|
||||||
// You can add redirect logic here
|
// You can add redirect logic here
|
||||||
window.location.href = '/signin';
|
|
||||||
} else if (error.response?.status === 500) {
|
} else if (error.response?.status === 500) {
|
||||||
// Server error
|
// Server error
|
||||||
console.error('Server Error:', error.response.data);
|
console.error('Server Error:', error.response.data);
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
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
|
// Products API endpoints
|
||||||
const ENDPOINTS = {
|
const ENDPOINTS = {
|
||||||
PRODUCTS: 'products',
|
PRODUCTS: 'Products',
|
||||||
PRODUCT_BY_ID: (id) => `products/${id}`,
|
PRODUCT_BY_ID: (id) => `Products/${id}`,
|
||||||
CATEGORIES: 'products/categories',
|
CATEGORIES: 'Products/categories',
|
||||||
BRANDS: 'products/brands',
|
BRANDS: 'Products/brands',
|
||||||
SEARCH: 'products/search',
|
SEARCH: 'Products/search',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Products API service
|
// Products API service
|
||||||
|
|||||||
@ -1,228 +0,0 @@
|
|||||||
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,11 +708,4 @@ button {
|
|||||||
box-shadow: 0 3px 12px rgba($dark, .2);
|
box-shadow: 0 3px 12px rgba($dark, .2);
|
||||||
border-color: $dark;
|
border-color: $dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.center-vertical {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 1%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
@ -25,9 +25,4 @@
|
|||||||
@include margin-padding(5px, null);
|
@include margin-padding(5px, null);
|
||||||
@include box-shadow(null, 0, 2px, 3px, null, rgb(215, 197, 255));
|
@include box-shadow(null, 0, 2px, 3px, null, rgb(215, 197, 255));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.icon-small {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
}
|
||||||
@ -1,192 +1,171 @@
|
|||||||
.page-link {
|
.page-link {
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
border: 1px solid $border-color;
|
border: 1px solid $border-color;
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: $light;
|
background-color: $light;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
background-color: $light;
|
background-color: $light;
|
||||||
border-color: $border-color;
|
border-color: $border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.page-item.active .page-link {
|
.page-item.active .page-link {
|
||||||
color: $white;
|
color: $white;
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
border-color: $primary;
|
border-color: $primary;
|
||||||
}
|
}
|
||||||
.disabled > .page-link,
|
.disabled>.page-link, .page-link.disabled {
|
||||||
.page-link.disabled {
|
color: $text-color;
|
||||||
color: $text-color;
|
background-color: $white;
|
||||||
background-color: $white;
|
border-color: $border-color;
|
||||||
border-color: $border-color;
|
opacity: 0.7;
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[dir="rtl"] {
|
[dir="rtl"] {
|
||||||
.pagination {
|
.pagination{
|
||||||
.page-link {
|
.page-link{
|
||||||
.bx-chevron-left::before {
|
.bx-chevron-left::before{
|
||||||
content: "\ea50";
|
content: "\ea50";
|
||||||
}
|
}
|
||||||
.bx-chevron-right::before {
|
.bx-chevron-right::before{
|
||||||
content: "\ea4d";
|
content: "\ea4d";
|
||||||
}
|
}
|
||||||
.ri-arrow-right-s-line:before {
|
.ri-arrow-right-s-line:before{
|
||||||
content: "\ea64";
|
content: "\ea64";
|
||||||
}
|
}
|
||||||
.ri-arrow-left-s-line:before {
|
.ri-arrow-left-s-line:before{
|
||||||
content: "\ea6e";
|
content: "\ea6e";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pagination-style-1 .pagination {
|
.pagination-style-1 .pagination {
|
||||||
.page-item {
|
.page-item {
|
||||||
margin: 0 0.25rem;
|
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 {
|
.page-link {
|
||||||
border-radius: $border-radius;
|
border: 0;
|
||||||
background-color: $primary;
|
border-radius: $border-radius;
|
||||||
color: $white;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
.page-link {
|
|
||||||
background-color: $light;
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pagination-style-2 .pagination {
|
.pagination-style-2 .pagination {
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
.page-item {
|
.page-item {
|
||||||
margin: 0 0.25rem;
|
margin: 0 0.25rem;
|
||||||
.page-link {
|
.page-link {
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
.page-link {
|
.page-link {
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
color: $primary;
|
color: $primary;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
&:before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
inset-block-end: 0;
|
inset-block-end: 0;
|
||||||
inset-inline-start: 0;
|
inset-inline-start: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0.1rem;
|
height: 0.1rem;
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.page-link {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
.page-link {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pagination-style-3 .pagination {
|
.pagination-style-3 .pagination {
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.page-item {
|
.page-item {
|
||||||
margin: 0 0.25rem;
|
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 {
|
.page-link {
|
||||||
background-color: $primary;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
.page-link {
|
|
||||||
background-color: $light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pagination-style-4 .pagination {
|
.pagination-style-4 .pagination {
|
||||||
.page-item {
|
.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 {
|
.page-link {
|
||||||
background-color: $primary;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&: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 {
|
a {
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-size: $font-size-14;
|
font-size: $font-size-14;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
@ -533,10 +533,10 @@ caption {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.table-top {
|
.table-top {
|
||||||
padding: 18px 18px 0;
|
padding: 24px 24px 0;
|
||||||
}
|
}
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
padding: 18px;
|
padding: 24px;
|
||||||
border-top: 1px solid $gray-400;
|
border-top: 1px solid $gray-400;
|
||||||
.dataTables_wrapper {
|
.dataTables_wrapper {
|
||||||
border: 0;
|
border: 0;
|
||||||
@ -545,7 +545,7 @@ caption {
|
|||||||
}
|
}
|
||||||
.tabs-set {
|
.tabs-set {
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
padding: 18px 18px 0;
|
padding: 24px 24px 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,12 +674,3 @@ 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{
|
h3{
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-size: $font-size-18;
|
font-size: $font-size-18;
|
||||||
@include respond-below(custom991) {
|
@include respond-below(custom991) {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
@ -120,7 +120,7 @@
|
|||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-size: $font-size-18;
|
font-size: $font-size-18;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
@include respond-below(custom991) {
|
@include respond-below(custom991) {
|
||||||
@ -536,7 +536,7 @@ th,td {
|
|||||||
h4 {
|
h4 {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
color: $primary
|
color: $secondary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.image-upload-new{
|
&.image-upload-new{
|
||||||
@ -652,7 +652,7 @@ th,td {
|
|||||||
}
|
}
|
||||||
label{
|
label{
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
color: $primary
|
color: #FE9F43
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
@ -66,21 +66,21 @@
|
|||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
background: rgba(254, 159, 67, 0.08);
|
background: rgba(254, 159, 67, 0.08);
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
img {
|
img {
|
||||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||||
}
|
}
|
||||||
span{
|
span{
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
}
|
}
|
||||||
svg{
|
svg{
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.active{
|
&.active{
|
||||||
background: rgba(254, 159, 67, 0.08);
|
background: rgba(254, 159, 67, 0.08);
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
svg{
|
svg{
|
||||||
color: $white;
|
color: $white;
|
||||||
@ -89,12 +89,12 @@
|
|||||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||||
}
|
}
|
||||||
span{
|
span{
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
}
|
}
|
||||||
.menu-arrow{
|
.menu-arrow{
|
||||||
background: #FFEDDC;
|
background: #FFEDDC;
|
||||||
&::before{
|
&::before{
|
||||||
border-color: $primary;
|
border-color: #FE9F43;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@
|
|||||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||||
}
|
}
|
||||||
span{
|
span{
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,8 +132,8 @@
|
|||||||
&.active{
|
&.active{
|
||||||
color: $secondary;
|
color: $secondary;
|
||||||
&:after{
|
&:after{
|
||||||
background: $primary;
|
background: #FE9F43;
|
||||||
border: 2px solid $secondary;
|
border: 2px solid #FDB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&::after{
|
&::after{
|
||||||
@ -148,8 +148,8 @@
|
|||||||
&:hover{
|
&:hover{
|
||||||
color:$primary;
|
color:$primary;
|
||||||
&:after{
|
&:after{
|
||||||
background: $primary;
|
background: #FE9F43;
|
||||||
border: 2px solid $secondary;
|
border: 2px solid #FDB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,15 +188,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:after{
|
&:after{
|
||||||
background: $primary;
|
background: #FE9F43;
|
||||||
border: 2px solid $secondary;
|
border: 2px solid #FDB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
color:$primary;
|
color:$primary;
|
||||||
&:after{
|
&:after{
|
||||||
background: $primary;
|
background: #FE9F43;
|
||||||
border: 2px solid $secondary;
|
border: 2px solid #FDB;
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
color:$primary;
|
color:$primary;
|
||||||
@ -209,7 +209,7 @@
|
|||||||
&.active a{
|
&.active a{
|
||||||
background: rgba(254, 159, 67, 0.08);
|
background: rgba(254, 159, 67, 0.08);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
span {
|
span {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
@ -240,25 +240,25 @@
|
|||||||
li {
|
li {
|
||||||
&.active{
|
&.active{
|
||||||
a{
|
a{
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
color: $primary
|
color: #FE9F43
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.submenu > {
|
.submenu > {
|
||||||
a {
|
a {
|
||||||
&.active{
|
&.active{
|
||||||
background: $secondary;
|
background: rgba(254, 159, 67, 0.08);
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
span{
|
span{
|
||||||
color: $primary;
|
color: #FE9F43;
|
||||||
}
|
}
|
||||||
.menu-arrow{
|
.menu-arrow{
|
||||||
// background: #FFEDDC;
|
background: #FFEDDC;
|
||||||
&::before{
|
&::before{
|
||||||
border-color: $primary;
|
border-color: #FE9F43;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,8 +321,8 @@
|
|||||||
&:hover{
|
&:hover{
|
||||||
color:$primary;
|
color:$primary;
|
||||||
&:after{
|
&:after{
|
||||||
background: $primary;
|
background: #FE9F43;
|
||||||
border: 2px solid $secondary;
|
border: 2px solid #FDB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -854,10 +854,10 @@ $__basecolor: #2c3038;
|
|||||||
|
|
||||||
// Badge styling in options
|
// Badge styling in options
|
||||||
.badge {
|
.badge {
|
||||||
// display: inline-block;
|
display: inline-block;
|
||||||
// width: 8px;
|
width: 8px;
|
||||||
// height: 8px;
|
height: 8px;
|
||||||
// border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
&.badge-blue { background: #007bff; }
|
&.badge-blue { background: #007bff; }
|
||||||
&.badge-green { background: #28a745; }
|
&.badge-green { background: #28a745; }
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
margin-top: 50px !important;
|
margin-top: 50px !important;
|
||||||
p {
|
p {
|
||||||
font-size: $font-size-14;
|
font-size: $font-size-14;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@
|
|||||||
h4 {
|
h4 {
|
||||||
font-size: $font-size-15;
|
font-size: $font-size-15;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
.verfy-mail-content {
|
.verfy-mail-content {
|
||||||
@ -132,7 +132,7 @@
|
|||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
label {
|
label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
margin-bottom:10px;
|
margin-bottom:10px;
|
||||||
font-size: $font-size-15;
|
font-size: $font-size-15;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
@ -209,12 +209,12 @@
|
|||||||
h4{
|
h4{
|
||||||
font-size: $font-size-15;
|
font-size: $font-size-15;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
@include respond-below(custom575) {
|
@include respond-below(custom575) {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
}
|
}
|
||||||
a{
|
a{
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
font-size: $font-size-14;
|
font-size: $font-size-14;
|
||||||
}
|
}
|
||||||
@ -339,7 +339,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid rgba(145, 158, 171, 0.23);
|
border: 1px solid rgba(145, 158, 171, 0.23);
|
||||||
background: $white;
|
background: $white;
|
||||||
|
|||||||
@ -182,7 +182,7 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@ -246,14 +246,14 @@
|
|||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
h6 {
|
h6 {
|
||||||
font-size: $font-size-13;
|
font-size: $font-size-13;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +409,7 @@ span {
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border: 1px solid var(--Stroke, rgba(145, 158, 171, 0.30));
|
border: 1px solid var(--Stroke, rgba(145, 158, 171, 0.30));
|
||||||
background: $primary;
|
background: $secondary;
|
||||||
@include rounded(8px);
|
@include rounded(8px);
|
||||||
@include respond-below(custom575) {
|
@include respond-below(custom575) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -256,12 +256,13 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.custom-modal-header {
|
.custom-modal-header {
|
||||||
|
background: $body-bg;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
.page-title {
|
.page-title {
|
||||||
h4 {
|
h4 {
|
||||||
font-size: $font-size-18;
|
font-size: $font-size-18;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
color: $primary;
|
color: $secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,9 +16,9 @@ $font-family-secondary: "Poppins", sans-serif;
|
|||||||
$font-awesome: "Fontawesome";
|
$font-awesome: "Fontawesome";
|
||||||
|
|
||||||
// Theme Colors Variables
|
// Theme Colors Variables
|
||||||
$primary: #36175e;
|
$primary: #FF9F43;
|
||||||
$primary-hover: darken($primary, 10%);
|
$primary-hover: darken($primary, 10%);
|
||||||
$secondary: #f1eaf9;
|
$secondary: #092C4C;
|
||||||
$secondary-hover: darken($secondary, 10%);
|
$secondary-hover: darken($secondary, 10%);
|
||||||
$success: #28C76F;
|
$success: #28C76F;
|
||||||
$success-hover: darken($success, 10%);
|
$success-hover: darken($success, 10%);
|
||||||
@ -29,7 +29,7 @@ $warning-hover: darken($warning, 10%);
|
|||||||
$danger: #FF0000;
|
$danger: #FF0000;
|
||||||
$danger-hover: darken($danger, 10%);
|
$danger-hover: darken($danger, 10%);
|
||||||
$dark: #29344a;
|
$dark: #29344a;
|
||||||
$light: #FAFBFE;
|
$light: #f8f9fa;
|
||||||
$white: #ffffff;
|
$white: #ffffff;
|
||||||
$black: #000000;
|
$black: #000000;
|
||||||
$purple: #7367F0;
|
$purple: #7367F0;
|
||||||
@ -44,26 +44,26 @@ $indigo: #4d5ddb;
|
|||||||
$yellow: #ffff00;
|
$yellow: #ffff00;
|
||||||
|
|
||||||
// Primary
|
// Primary
|
||||||
$primary-100: #ede7f3;
|
$primary-100: #FFF5EC;
|
||||||
$primary-200: #d3c2e3;
|
$primary-200: #FFECD9;
|
||||||
$primary-300: #b99cd3;
|
$primary-300: #FFE2C7;
|
||||||
$primary-400: #9f76c3;
|
$primary-400: #FFD9B4;
|
||||||
$primary-500: #8551b3;
|
$primary-500: #FFCFA1;
|
||||||
$primary-600: #6e3f98;
|
$primary-600: #FFC58E;
|
||||||
$primary-700: #57317a;
|
$primary-700: #FFBC7B;
|
||||||
$primary-800: #40225c;
|
$primary-800: #FFB269;
|
||||||
$primary-900: #2a153d;
|
$primary-900: #FFA956;
|
||||||
|
|
||||||
// Secondary
|
// Secondary
|
||||||
$secondary-100: #fdfbff;
|
$secondary-100: #E6EAED;
|
||||||
$secondary-200: #f8f4fc;
|
$secondary-200: #CED5DB;
|
||||||
$secondary-300: #f1eaf9;
|
$secondary-300: #B5C0C9;
|
||||||
$secondary-400: #e0d3f1;
|
$secondary-400: #9DABB7;
|
||||||
$secondary-500: #c8b0e6;
|
$secondary-500: #8496A6;
|
||||||
$secondary-600: #af8ddb;
|
$secondary-600: #6B8094;
|
||||||
$secondary-700: #9369cc;
|
$secondary-700: #536B82;
|
||||||
$secondary-800: #7547aa;
|
$secondary-800: #3A5670;
|
||||||
$secondary-900: #563180;
|
$secondary-900: #22415E;
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
$success-100: #EAF9F1;
|
$success-100: #EAF9F1;
|
||||||
@ -158,7 +158,7 @@ $theme-colors: (
|
|||||||
"black": $black,
|
"black": $black,
|
||||||
"purple": $purple,
|
"purple": $purple,
|
||||||
"yellow": $yellow,
|
"yellow": $yellow,
|
||||||
"teal": $teal,
|
"teal": $teal
|
||||||
);
|
);
|
||||||
|
|
||||||
$text-color: #5B6670;
|
$text-color: #5B6670;
|
||||||
@ -282,4 +282,4 @@ $h2-font-size: $font-size-base * 2;
|
|||||||
$h3-font-size: $font-size-base * 1.75;
|
$h3-font-size: $font-size-base * 1.75;
|
||||||
$h4-font-size: $font-size-base * 1.5;
|
$h4-font-size: $font-size-base * 1.5;
|
||||||
$h5-font-size: $font-size-base * 1.25;
|
$h5-font-size: $font-size-base * 1.25;
|
||||||
$h6-font-size: $font-size-base;
|
$h6-font-size: $font-size-base;
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
const formatRupiah = (angka) => {
|
|
||||||
return new Intl.NumberFormat("id-ID", {
|
|
||||||
style: "currency",
|
|
||||||
currency: "IDR",
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
}).format(angka);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { formatRupiah };
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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 };
|
|
||||||