update 3 modules
3
.env
@ -1,3 +1,4 @@
|
|||||||
REACT_APP_API_BASE_URL=https://trantran.zenstores.com.vn/api/
|
# REACT_APP_API_BASE_URL=https://trantran.zenstores.com.vn/api/
|
||||||
|
REACT_APP_API_BASE_URL=http://localhost:4000/api/v1
|
||||||
# CORS Proxy for development (uncomment if needed)
|
# 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,6 +8,7 @@
|
|||||||
"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",
|
||||||
@ -142,13 +143,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ant-design/icons": {
|
"node_modules/@ant-design/icons": {
|
||||||
"version": "5.3.3",
|
"version": "5.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
|
||||||
"integrity": "sha512-Zfci1s4f4+vfpVD6ksmmPuBv00SB/slpUAQlsBlMeRJdSleVVkgTUdlBM4j/vGzqYfMh2hF8/Poa1VSh542w0Q==",
|
"integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
|
||||||
|
"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.11.2",
|
"@babel/runtime": "^7.24.8",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"rc-util": "^5.31.1"
|
"rc-util": "^5.31.1"
|
||||||
},
|
},
|
||||||
@ -2042,12 +2044,10 @@
|
|||||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.24.0",
|
"version": "7.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
|
||||||
"integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==",
|
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"regenerator-runtime": "^0.14.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@ -21112,11 +21112,6 @@
|
|||||||
"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,6 +4,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
BIN
public/assets/img/_logo.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/assets/img/authentication/_login-img.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 31 KiB |
26
public/assets/img/logo.svg
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
@ -1,16 +1,25 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import FeatherIcon from "feather-icons-react";
|
import 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;
|
||||||
};
|
};
|
||||||
@ -616,8 +625,8 @@ const Header = () => {
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="user-detail">
|
<span className="user-detail">
|
||||||
<span className="user-name">John Smilga</span>
|
<span className="user-name">{authState.user?.name || 'User'}</span>
|
||||||
<span className="user-role">Super Admin</span>
|
<span className="user-role">{authState.user?.role || 'Admin'}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@ -632,12 +641,12 @@ const Header = () => {
|
|||||||
<span className="status online" />
|
<span className="status online" />
|
||||||
</span>
|
</span>
|
||||||
<div className="profilesets">
|
<div className="profilesets">
|
||||||
<h6>John Smilga</h6>
|
<h6>{authState.user?.name || 'User'}</h6>
|
||||||
<h5>Super Admin</h5>
|
<h5>{authState.user?.role || 'Admin'}</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
<Link className="dropdown-item" to={route.route}>
|
<Link className="dropdown-item" to={route.profile}>
|
||||||
<i className="me-2" data-feather="user" /> My Profile
|
<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}>
|
||||||
@ -645,14 +654,18 @@ const Header = () => {
|
|||||||
Settings
|
Settings
|
||||||
</Link>
|
</Link>
|
||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
<Link className="dropdown-item logout pb-0" to="/signin">
|
<button
|
||||||
|
className="dropdown-item logout pb-0"
|
||||||
|
onClick={handleLogout}
|
||||||
|
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left' }}
|
||||||
|
>
|
||||||
<ImageWithBasePath
|
<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
|
||||||
</Link>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -675,9 +688,13 @@ const Header = () => {
|
|||||||
<Link className="dropdown-item" to="generalsettings">
|
<Link className="dropdown-item" to="generalsettings">
|
||||||
Settings
|
Settings
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="dropdown-item" to="signin">
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={handleLogout}
|
||||||
|
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left' }}
|
||||||
|
>
|
||||||
Logout
|
Logout
|
||||||
</Link>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* /Mobile Menu */}
|
{/* /Mobile Menu */}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
|||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import ThemeSettings from "../InitialPage/themeSettings";
|
import ThemeSettings from "../InitialPage/themeSettings";
|
||||||
|
import ProtectedRoute from "../components/ProtectedRoute";
|
||||||
// import CollapsedSidebar from "../InitialPage/Sidebar/collapsedSidebar";
|
// import CollapsedSidebar from "../InitialPage/Sidebar/collapsedSidebar";
|
||||||
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";
|
||||||
@ -48,8 +49,6 @@ const AllRoutes = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(publicRoutes, "dashboard");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Routes>
|
<Routes>
|
||||||
@ -58,7 +57,14 @@ 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
|
||||||
|
path={"/"}
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<HeaderLayout />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
>
|
||||||
{publicRoutes.map((route, id) => (
|
{publicRoutes.map((route, id) => (
|
||||||
<Route path={route.path} element={route.element} key={id} />
|
<Route path={route.path} element={route.element} key={id} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1404,6 +1404,13 @@ 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,
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import {
|
||||||
import { useSelector } from 'react-redux';
|
DoubleLeftOutlined,
|
||||||
|
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,
|
||||||
@ -13,28 +19,32 @@ 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 = document.documentElement.getAttribute('data-layout-mode') === 'dark_mode';
|
const documentTheme =
|
||||||
|
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 (mutation.type === 'attributes' && mutation.attributeName === 'data-layout-mode') {
|
if (
|
||||||
|
mutation.type === "attributes" &&
|
||||||
|
mutation.attributeName === "data-layout-mode"
|
||||||
|
) {
|
||||||
handleThemeChange();
|
handleThemeChange();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -42,27 +52,25 @@ 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);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Calculate pagination info
|
console.log(totalCount);
|
||||||
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) => {
|
||||||
@ -81,190 +89,191 @@ 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 ? '0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(52, 152, 219, 0.1)' : '0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)')
|
? compact
|
||||||
: (compact ? '0 1px 6px rgba(0, 0, 0, 0.06)' : '0 2px 12px rgba(0, 0, 0, 0.08)'),
|
? "0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(52, 152, 219, 0.1)"
|
||||||
backdropFilter: isDarkMode ? (compact ? 'blur(8px)' : 'blur(10px)') : 'none',
|
: "0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)"
|
||||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease',
|
: compact
|
||||||
position: 'relative',
|
? "0 1px 6px rgba(0, 0, 0, 0.06)"
|
||||||
overflow: 'hidden',
|
: "0 2px 12px rgba(0, 0, 0, 0.08)",
|
||||||
padding: compact ? '8px 16px' : '16px 24px',
|
backdropFilter: isDarkMode
|
||||||
margin: compact ? '8px 0' : '16px 0',
|
? compact
|
||||||
fontSize: compact ? '13px' : '14px'
|
? "blur(8px)"
|
||||||
|
: "blur(10px)"
|
||||||
|
: "none",
|
||||||
|
transition: compact ? "all 0.2s ease" : "all 0.3s ease",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
padding: compact ? "8px 16px" : "16px 24px",
|
||||||
|
margin: compact ? "8px 0" : "16px 0",
|
||||||
|
fontSize: compact ? "13px" : "14px",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Button styles
|
|
||||||
const getButtonStyles = (isActive) => ({
|
|
||||||
background: loading
|
|
||||||
? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
|
||||||
: isActive
|
|
||||||
? (isDarkMode ? 'linear-gradient(45deg, #f39c12, #e67e22)' : 'linear-gradient(135deg, #007bff, #0056b3)')
|
|
||||||
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
|
||||||
border: isActive
|
|
||||||
? (isDarkMode ? (compact ? '1px solid #f39c12' : '2px solid #f39c12') : (compact ? '1px solid #007bff' : '2px solid #007bff'))
|
|
||||||
: (isDarkMode ? '1px solid rgba(52, 152, 219, 0.3)' : '1px solid #dee2e6'),
|
|
||||||
borderRadius: '50%',
|
|
||||||
width: compact ? '24px' : '32px',
|
|
||||||
height: compact ? '24px' : '32px',
|
|
||||||
color: isActive
|
|
||||||
? '#ffffff'
|
|
||||||
: (isDarkMode ? '#ffffff' : '#495057'),
|
|
||||||
fontSize: compact ? '11px' : '14px',
|
|
||||||
fontWeight: compact ? '600' : '700',
|
|
||||||
cursor: loading ? 'not-allowed' : 'pointer',
|
|
||||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease',
|
|
||||||
boxShadow: loading
|
|
||||||
? 'none'
|
|
||||||
: isActive
|
|
||||||
? (isDarkMode ? (compact ? '0 2px 6px rgba(243, 156, 18, 0.3)' : '0 4px 12px rgba(243, 156, 18, 0.4)') : (compact ? '0 2px 4px rgba(0, 123, 255, 0.2)' : '0 3px 8px rgba(0, 123, 255, 0.3)'))
|
|
||||||
: (isDarkMode ? (compact ? '0 1px 4px rgba(52, 73, 94, 0.2)' : '0 2px 8px rgba(52, 73, 94, 0.3)') : (compact ? '0 1px 2px rgba(0, 0, 0, 0.08)' : '0 1px 3px rgba(0, 0, 0, 0.1)')),
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
opacity: loading ? 0.6 : 1
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`pagination-${themeKey}`}
|
key={`pagination-${themeKey}`}
|
||||||
className={`custom-pagination-container ${isDarkMode ? '' : 'light-mode'} ${className}`}
|
className={`custom-pagination-container ${
|
||||||
|
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' : '16px',
|
marginBottom: compact ? "8px" : "0px",
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
gap: compact ? '8px' : '12px'
|
gap: compact ? "8px" : "12px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showPageSizeSelector && (
|
{showPageSizeSelector && (
|
||||||
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
<div
|
||||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>Số hàng mỗi trang</span>
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: compact ? "6px" : "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: isDarkMode ? "#bdc3c7" : "#2c3e50",
|
||||||
|
fontSize: compact ? "12px" : "14px",
|
||||||
|
fontWeight: "500",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Number of rows per page
|
||||||
|
</span>
|
||||||
<select
|
<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 ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
? isDarkMode
|
||||||
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
? "linear-gradient(45deg, #7f8c8d, #95a5a6)"
|
||||||
|
: "linear-gradient(135deg, #f8f9fa, #e9ecef)"
|
||||||
|
: isDarkMode
|
||||||
|
? "linear-gradient(45deg, #34495e, #2c3e50)"
|
||||||
|
: "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 #dee2e6',
|
: "1px solid #dee2e6",
|
||||||
borderRadius: compact ? '4px' : '6px',
|
borderRadius: compact ? "4px" : "6px",
|
||||||
color: isDarkMode ? '#ffffff' : '#495057',
|
color: isDarkMode ? "#ffffff" : "#495057",
|
||||||
padding: compact ? '2px 6px' : '4px 8px',
|
padding: compact ? "2px 6px" : "4px 8px",
|
||||||
fontSize: compact ? '12px' : '14px',
|
fontSize: compact ? "12px" : "14px",
|
||||||
cursor: loading ? 'not-allowed' : 'pointer',
|
cursor: loading ? "not-allowed" : "pointer",
|
||||||
opacity: loading ? 0.7 : 1,
|
opacity: loading ? 0.7 : 1,
|
||||||
boxShadow: isDarkMode ? 'none' : (compact ? '0 1px 2px rgba(0, 0, 0, 0.05)' : '0 1px 3px rgba(0, 0, 0, 0.1)')
|
boxShadow: isDarkMode
|
||||||
|
? "none"
|
||||||
|
: compact
|
||||||
|
? "0 1px 2px rgba(0, 0, 0, 0.05)"
|
||||||
|
: "0 1px 3px rgba(0, 0, 0, 0.1)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pageSizeOptions.map(option => (
|
{pageSizeOptions.map((option) => (
|
||||||
<option
|
<option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}
|
style={{
|
||||||
|
background: isDarkMode ? "#2c3e50" : "#ffffff",
|
||||||
|
color: isDarkMode ? "#ffffff" : "#495057",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{option}
|
{option}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>bản ghi</span>
|
<span
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
|
||||||
<div
|
|
||||||
style={{
|
style={{
|
||||||
background: isDarkMode
|
color: isDarkMode ? "#bdc3c7" : "#2c3e50",
|
||||||
? 'linear-gradient(45deg, #3498db, #2ecc71)'
|
fontSize: compact ? "12px" : "14px",
|
||||||
: 'linear-gradient(45deg, #007bff, #28a745)',
|
fontWeight: "500",
|
||||||
borderRadius: '50%',
|
|
||||||
width: compact ? '16px' : '24px',
|
|
||||||
height: compact ? '16px' : '24px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: compact ? '8px' : '12px',
|
|
||||||
boxShadow: isDarkMode
|
|
||||||
? (compact ? '0 1px 4px rgba(52, 152, 219, 0.3)' : '0 2px 8px rgba(52, 152, 219, 0.3)')
|
|
||||||
: (compact ? '0 1px 4px rgba(0, 123, 255, 0.2)' : '0 2px 8px rgba(0, 123, 255, 0.2)'),
|
|
||||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
📊
|
records
|
||||||
</div>
|
|
||||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>
|
|
||||||
Xem <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{startRecord}</strong> đến <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{endRecord}</strong> của <strong style={{color: isDarkMode ? '#e74c3c' : '#dc3545'}}>{totalCount}</strong> bản
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pagination Buttons */}
|
{/* Pagination Buttons */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: "relative",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gap: compact ? '4px' : '8px'
|
gap: compact ? "4px" : "8px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Array.from({ length: totalPages }, (_, i) => {
|
<nav aria-label="Custom pagination">
|
||||||
const pageNum = i + 1;
|
<ul className="pagination justify-content-center custom-pagination">
|
||||||
const isActive = currentPage === pageNum;
|
<li className="page-item">
|
||||||
|
<a
|
||||||
return (
|
onClick={() => handlePageClick(1)}
|
||||||
<button
|
className="page-link"
|
||||||
key={pageNum}
|
aria-label="First"
|
||||||
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}
|
<DoubleLeftOutlined
|
||||||
</button>
|
style={{ fontSize: "12px", marginRight: "-10px" }}
|
||||||
);
|
/>
|
||||||
})}
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (currentPage > 1) handlePageClick(currentPage - 1);
|
||||||
|
}}
|
||||||
|
className={`page-item ${currentPage === 1 ? "disabled" : ""}`}
|
||||||
|
>
|
||||||
|
<a className="page-link">
|
||||||
|
<LeftOutlined style={{ fontSize: "12px" }} />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="page-item active bg-primary text-secondary rounded-5">
|
||||||
|
<span className="page-link">{currentPage}</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (currentPage < totalPages)
|
||||||
|
handlePageClick(currentPage + 1);
|
||||||
|
}}
|
||||||
|
className={`page-item ${
|
||||||
|
currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<a className="page-link" aria-label="Next">
|
||||||
|
<RightOutlined style={{ fontSize: "12px" }} />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="page-item">
|
||||||
|
<a
|
||||||
|
onClick={() => handlePageClick(totalPages)}
|
||||||
|
className="page-link"
|
||||||
|
aria-label="Last"
|
||||||
|
>
|
||||||
|
<DoubleRightOutlined
|
||||||
|
style={{ fontSize: "12px", marginLeft: "-10px" }}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
// Color variants
|
// Color variants
|
||||||
.primary {
|
.primary {
|
||||||
--loader-color: #ff9f43;
|
--loader-color: #36175e;
|
||||||
--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: #ff9f43;
|
background: #36175e;
|
||||||
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: #ff9f43;
|
color: #36175e;
|
||||||
border: 2px solid #ff9f43;
|
border: 2px solid #36175e;
|
||||||
|
|
||||||
&:hover:not(.loading):not(.disabled) {
|
&:hover:not(.loading):not(.disabled) {
|
||||||
background: #ff9f43;
|
background: #36175e;
|
||||||
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);
|
||||||
|
|||||||
23
src/components/ProtectedRoute.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
const ProtectedRoute = ({ children }) => {
|
||||||
|
// Check if user is authenticated using Redux state
|
||||||
|
const authState = useSelector((state) => state.auth);
|
||||||
|
const isAuthenticated = authState?.isAuthenticated || authState?.token;
|
||||||
|
|
||||||
|
// Fallback to localStorage check
|
||||||
|
const localStorageAuth = localStorage.getItem('authToken') || localStorage.getItem('user');
|
||||||
|
const isUserAuthenticated = isAuthenticated || localStorageAuth;
|
||||||
|
|
||||||
|
if (!isUserAuthenticated) {
|
||||||
|
// Redirect to login page if not authenticated
|
||||||
|
return <Navigate to="/signin" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If authenticated, render the protected component
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProtectedRoute;
|
||||||
@ -60,22 +60,22 @@ export const SidebarData = [
|
|||||||
submenuHdr: "Inventory",
|
submenuHdr: "Inventory",
|
||||||
|
|
||||||
submenuItems: [
|
submenuItems: [
|
||||||
{ label: "Tiến độ dự án", link: "/project-tracker",icon: <Icon.Layers />,showSubRoute: false},
|
{ label: "Project Progress", link: "/project-tracker",icon: <Icon.Layers />,showSubRoute: false},
|
||||||
{ label: "Sản phẩm", link: "/product-list", icon:<Icon.Box />,showSubRoute: false,submenu: false },
|
{ label: "Products", link: "/product-list", icon:<Icon.Box />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Nhập kho", link: "/product-list-2", icon:<Icon.Package />,showSubRoute: false,submenu: false },
|
{ label: "Stock In", link: "/product-list-2", icon:<Icon.Package />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Tồn kho", link: "/product-list-3", icon:<Icon.Archive />,showSubRoute: false,submenu: false },
|
{ label: "Stock On Hand", link: "/product-list-3", icon:<Icon.Archive />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Create Product", link: "/add-product", icon: <Icon.PlusSquare />,showSubRoute: false, submenu: false },
|
{ label: "Create Product", link: "/add-product", icon: <Icon.PlusSquare />,showSubRoute: false, submenu: false },
|
||||||
{ label: "Sản phẩm hết hạn", link: "/expired-products", icon: <Icon.Codesandbox />,showSubRoute: false,submenu: false },
|
{ label: "Expired Products", link: "/expired-products", icon: <Icon.Codesandbox />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Hàng tồn kho", link: "/low-stocks", icon: <Icon.TrendingDown />,showSubRoute: false,submenu: false },
|
{ label: "Low Stock Items", link: "/low-stocks", icon: <Icon.TrendingDown />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Danh mục", link: "/category-list", icon: <Icon.Codepen />,showSubRoute: false,submenu: false },
|
{ label: "Category", link: "/category-list", icon: <Icon.Codepen />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Sub Category", link: "/sub-categories", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
|
{ label: "Sub Category", link: "/sub-categories", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Thương hiệu", link: "/brand-list", icon: <Icon.Tag />,showSubRoute: false,submenu: false },
|
{ label: "Brands", link: "/brand-list", icon: <Icon.Tag />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Units", link: "/units", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
|
{ label: "Units", link: "/units", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Variant Attributes", link: "/variant-attributes", icon: <Icon.Layers />,showSubRoute: false,submenu: false },
|
{ label: "Variant Attributes", link: "/variant-attributes", icon: <Icon.Layers />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Bảo hành", link: "/warranty", icon: <Icon.Bookmark />,showSubRoute: false,submenu: false },
|
{ label: "Warranty", link: "/warranty", icon: <Icon.Bookmark />,showSubRoute: false,submenu: false },
|
||||||
{ label: "In Barcode", link: "/barcode", icon: <Icon.AlignJustify />, showSubRoute: false,submenu: false },
|
{ label: "In Barcode", link: "/barcode", icon: <Icon.AlignJustify />, showSubRoute: false,submenu: false },
|
||||||
{ label: "In QR Code", link: "/qrcode", icon: <Icon.Maximize />,showSubRoute: false,submenu: false },
|
{ label: "In QR Code", link: "/qrcode", icon: <Icon.Maximize />,showSubRoute: false,submenu: false },
|
||||||
{ label: "Khách mời đám cưới", link: "/wedding-guest-list", icon: <Icon.Heart />,showSubRoute: false,submenu: false }
|
{ label: "Wedding Guests", link: "/wedding-guest-list", icon: <Icon.Heart />,showSubRoute: false,submenu: false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,57 @@
|
|||||||
import React from 'react'
|
import { useState } from "react";
|
||||||
import { Link } from 'react-router-dom'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import {
|
||||||
|
createCategory,
|
||||||
|
fetchCategories,
|
||||||
|
} from "../../redux/actions/categoryActions";
|
||||||
|
|
||||||
const AddCategoryList = () => {
|
const AddCategoryList = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { creating } = useSelector((state) => state.categories);
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await dispatch(createCategory(formData));
|
||||||
|
|
||||||
|
await dispatch(fetchCategories());
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Success!",
|
||||||
|
text: "Category created successfully!",
|
||||||
|
icon: "success",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500,
|
||||||
|
}).then(() => {
|
||||||
|
const closeButton = document.querySelector("#add-category .btn-close");
|
||||||
|
closeButton.click();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating category:", error);
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error!",
|
||||||
|
text: error.message || "Failed to create category. Please try again.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Add Category */}
|
{/* Add Category */}
|
||||||
@ -16,22 +66,33 @@ const AddCategoryList = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="close"
|
className="btn-close btn-close-danger"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
></button>
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body custom-modal-body">
|
<div className="modal-body custom-modal-body">
|
||||||
<form>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Category</label>
|
<label className="form-label">Category</label>
|
||||||
<input type="text" className="form-control" />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Category Slug</label>
|
<label className="form-label">Description</label>
|
||||||
<input type="text" className="form-control" />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-0">
|
<div className="mb-0">
|
||||||
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
<div className="status-toggle modal-status d-flex justify-content-between align-items-center">
|
||||||
@ -48,14 +109,29 @@ const AddCategoryList = () => {
|
|||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-cancel me-2"
|
className="btn btn-secondary me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<Link to="#" className="btn btn-submit">
|
<button
|
||||||
Create Category
|
type="submit"
|
||||||
</Link>
|
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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -66,7 +142,7 @@ const AddCategoryList = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* /Add Category */}
|
{/* /Add Category */}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AddCategoryList
|
export default AddCategoryList;
|
||||||
|
|||||||
@ -1,7 +1,69 @@
|
|||||||
import React from 'react'
|
import { useEffect, useState } from "react";
|
||||||
import { Link } from 'react-router-dom'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import {
|
||||||
|
fetchCategories,
|
||||||
|
updateCategory,
|
||||||
|
} from "../../redux/actions/categoryActions";
|
||||||
|
|
||||||
const EditCategoryList = () => {
|
const EditCategoryList = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const { currentCategory, updating, currentPage, pageSize } = useSelector(
|
||||||
|
(state) => state.categories
|
||||||
|
);
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentCategory) {
|
||||||
|
setFormData({
|
||||||
|
name: currentCategory.name,
|
||||||
|
description: currentCategory.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentCategory]);
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await dispatch(updateCategory(currentCategory?.id, formData));
|
||||||
|
|
||||||
|
await dispatch(fetchCategories({ page: currentPage, limit: pageSize }));
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Success!",
|
||||||
|
text: "Category updated successfully!",
|
||||||
|
icon: "success",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500,
|
||||||
|
}).then(() => {
|
||||||
|
const closeButton = document.querySelector("#edit-category .btn-close");
|
||||||
|
closeButton.click();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating category:", error);
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error!",
|
||||||
|
text: error.message || "Failed to update category. Please try again.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Edit Category */}
|
{/* Edit Category */}
|
||||||
@ -16,29 +78,32 @@ const EditCategoryList = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="close"
|
className="btn-close"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
></button>
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body custom-modal-body">
|
<div className="modal-body custom-modal-body">
|
||||||
<form>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Category</label>
|
<label className="form-label">Category</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
defaultValue="Laptop"
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Category Slug</label>
|
<label className="form-label">Description</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
defaultValue="laptop"
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-0">
|
<div className="mb-0">
|
||||||
@ -56,14 +121,29 @@ const EditCategoryList = () => {
|
|||||||
<div className="modal-footer-btn">
|
<div className="modal-footer-btn">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-cancel me-2"
|
className="btn btn-secondary me-2"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<Link to="#" className="btn btn-submit">
|
<button
|
||||||
Save Changes
|
type="submit"
|
||||||
</Link>
|
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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +154,7 @@ const EditCategoryList = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* /Edit Category */}
|
{/* /Edit Category */}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default EditCategoryList
|
export default EditCategoryList;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ 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);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -15,15 +14,18 @@ const Datatable = ({ props, columns, dataSource }) => {
|
|||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
key={props}
|
key={props}
|
||||||
className="table datanew dataTable no-footer"
|
className="custom-table table datanew dataTable"
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
|
|
||||||
rowKey={(record) => record.id}
|
rowKey={(record) => record.id}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 100
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
195
src/core/redux/actions/categoryActions.js
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { categoriesApi } from '../../../services/categoriesApi';
|
||||||
|
|
||||||
|
// Action Types
|
||||||
|
export const CATEGORY_ACTIONS = {
|
||||||
|
// Fetch Categories
|
||||||
|
FETCH_CATEGORIES_REQUEST: 'FETCH_CATEGORIES_REQUEST',
|
||||||
|
FETCH_CATEGORIES_SUCCESS: 'FETCH_CATEGORIES_SUCCESS',
|
||||||
|
FETCH_CATEGORIES_FAILURE: 'FETCH_CATEGORIES_FAILURE',
|
||||||
|
|
||||||
|
// Fetch Single Category
|
||||||
|
FETCH_CATEGORY_REQUEST: 'FETCH_CATEGORY_REQUEST',
|
||||||
|
FETCH_CATEGORY_SUCCESS: 'FETCH_CATEGORY_SUCCESS',
|
||||||
|
FETCH_CATEGORY_FAILURE: 'FETCH_CATEGORY_FAILURE',
|
||||||
|
|
||||||
|
// Create Category
|
||||||
|
CREATE_CATEGORY_REQUEST: 'CREATE_CATEGORY_REQUEST',
|
||||||
|
CREATE_CATEGORY_SUCCESS: 'CREATE_CATEGORY_SUCCESS',
|
||||||
|
CREATE_CATEGORY_FAILURE: 'CREATE_CATEGORY_FAILURE',
|
||||||
|
|
||||||
|
// Update Category
|
||||||
|
UPDATE_CATEGORY_REQUEST: 'UPDATE_CATEGORY_REQUEST',
|
||||||
|
UPDATE_CATEGORY_SUCCESS: 'UPDATE_CATEGORY_SUCCESS',
|
||||||
|
UPDATE_CATEGORY_FAILURE: 'UPDATE_CATEGORY_FAILURE',
|
||||||
|
|
||||||
|
// Delete Category
|
||||||
|
DELETE_CATEGORY_REQUEST: 'DELETE_CATEGORY_REQUEST',
|
||||||
|
DELETE_CATEGORY_SUCCESS: 'DELETE_CATEGORY_SUCCESS',
|
||||||
|
DELETE_CATEGORY_FAILURE: 'DELETE_CATEGORY_FAILURE',
|
||||||
|
|
||||||
|
// Search Categories
|
||||||
|
SEARCH_CATEGORIES_REQUEST: 'SEARCH_CATEGORIES_REQUEST',
|
||||||
|
SEARCH_CATEGORIES_SUCCESS: 'SEARCH_CATEGORIES_SUCCESS',
|
||||||
|
SEARCH_CATEGORIES_FAILURE: 'SEARCH_CATEGORIES_FAILURE',
|
||||||
|
|
||||||
|
// Get Category Products
|
||||||
|
FETCH_CATEGORY_PRODUCTS_REQUEST: 'FETCH_CATEGORY_PRODUCTS_REQUEST',
|
||||||
|
FETCH_CATEGORY_PRODUCTS_SUCCESS: 'FETCH_CATEGORY_PRODUCTS_SUCCESS',
|
||||||
|
FETCH_CATEGORY_PRODUCTS_FAILURE: 'FETCH_CATEGORY_PRODUCTS_FAILURE',
|
||||||
|
|
||||||
|
// Clear States
|
||||||
|
CLEAR_CATEGORY_ERROR: 'CLEAR_CATEGORY_ERROR',
|
||||||
|
CLEAR_CURRENT_CATEGORY: 'CLEAR_CURRENT_CATEGORY',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
// Fetch all categories
|
||||||
|
export const fetchCategories = (params = {}) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.FETCH_CATEGORIES_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await categoriesApi.getAllCategories(params);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.FETCH_CATEGORIES_SUCCESS,
|
||||||
|
payload: data,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.FETCH_CATEGORIES_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to fetch categories',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch single category
|
||||||
|
export const fetchCategory = (id) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.FETCH_CATEGORY_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await categoriesApi.getCategoryById(id);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.FETCH_CATEGORY_SUCCESS,
|
||||||
|
payload: data,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.FETCH_CATEGORY_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to fetch category',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create category
|
||||||
|
export const createCategory = (categoryData) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.CREATE_CATEGORY_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await categoriesApi.createCategory(categoryData);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.CREATE_CATEGORY_SUCCESS,
|
||||||
|
payload: data,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.CREATE_CATEGORY_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to create category',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update category
|
||||||
|
export const updateCategory = (id, categoryData) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.UPDATE_CATEGORY_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await categoriesApi.updateCategory(id, categoryData);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.UPDATE_CATEGORY_SUCCESS,
|
||||||
|
payload: { id, data },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.UPDATE_CATEGORY_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to update category',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete category
|
||||||
|
export const deleteCategory = (id) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.DELETE_CATEGORY_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await categoriesApi.deleteCategory(id);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.DELETE_CATEGORY_SUCCESS,
|
||||||
|
payload: id,
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.DELETE_CATEGORY_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to delete category',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search categories
|
||||||
|
export const searchCategories = (query, params = {}) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.SEARCH_CATEGORIES_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await categoriesApi.searchCategories(query, params);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.SEARCH_CATEGORIES_SUCCESS,
|
||||||
|
payload: data,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.SEARCH_CATEGORIES_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to search categories',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch category products
|
||||||
|
export const fetchCategoryProducts = (id, params = {}) => async (dispatch) => {
|
||||||
|
dispatch({ type: CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_REQUEST });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await categoriesApi.getCategoryProducts(id, params);
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_SUCCESS,
|
||||||
|
payload: { id, data },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to fetch category products',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear error
|
||||||
|
export const clearCategoryError = () => ({
|
||||||
|
type: CATEGORY_ACTIONS.CLEAR_CATEGORY_ERROR,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear current category
|
||||||
|
export const clearCurrentCategory = () => ({
|
||||||
|
type: CATEGORY_ACTIONS.CLEAR_CURRENT_CATEGORY,
|
||||||
|
});
|
||||||
138
src/core/redux/actions/orderActions.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { ordersApi } from '../../../services/ordersApi';
|
||||||
|
|
||||||
|
// Action Types
|
||||||
|
export const ORDER_ACTIONS = {
|
||||||
|
// Fetch Orders
|
||||||
|
FETCH_ORDERS_REQUEST: 'FETCH_ORDERS_REQUEST',
|
||||||
|
FETCH_ORDERS_SUCCESS: 'FETCH_ORDERS_SUCCESS',
|
||||||
|
FETCH_ORDERS_FAILURE: 'FETCH_ORDERS_FAILURE',
|
||||||
|
|
||||||
|
// Fetch Single Order
|
||||||
|
FETCH_ORDER_REQUEST: 'FETCH_ORDER_REQUEST',
|
||||||
|
FETCH_ORDER_SUCCESS: 'FETCH_ORDER_SUCCESS',
|
||||||
|
FETCH_ORDER_FAILURE: 'FETCH_ORDER_FAILURE',
|
||||||
|
|
||||||
|
// Create Order
|
||||||
|
CREATE_ORDER_REQUEST: 'CREATE_ORDER_REQUEST',
|
||||||
|
CREATE_ORDER_SUCCESS: 'CREATE_ORDER_SUCCESS',
|
||||||
|
CREATE_ORDER_FAILURE: 'CREATE_ORDER_FAILURE',
|
||||||
|
|
||||||
|
// Update Order
|
||||||
|
UPDATE_ORDER_REQUEST: 'UPDATE_ORDER_REQUEST',
|
||||||
|
UPDATE_ORDER_SUCCESS: 'UPDATE_ORDER_SUCCESS',
|
||||||
|
UPDATE_ORDER_FAILURE: 'UPDATE_ORDER_FAILURE',
|
||||||
|
|
||||||
|
// Delete Order
|
||||||
|
DELETE_ORDER_REQUEST: 'DELETE_ORDER_REQUEST',
|
||||||
|
DELETE_ORDER_SUCCESS: 'DELETE_ORDER_SUCCESS',
|
||||||
|
DELETE_ORDER_FAILURE: 'DELETE_ORDER_FAILURE',
|
||||||
|
|
||||||
|
// Search Orders
|
||||||
|
SEARCH_ORDERS_REQUEST: 'SEARCH_ORDERS_REQUEST',
|
||||||
|
SEARCH_ORDERS_SUCCESS: 'SEARCH_ORDERS_SUCCESS',
|
||||||
|
SEARCH_ORDERS_FAILURE: 'SEARCH_ORDERS_FAILURE',
|
||||||
|
|
||||||
|
// Clear States
|
||||||
|
CLEAR_ORDER_ERROR: 'CLEAR_ORDER_ERROR',
|
||||||
|
CLEAR_CURRENT_ORDER: 'CLEAR_CURRENT_ORDER',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchOrders = (params = {}) => async (dispatch) => {
|
||||||
|
dispatch({ type: ORDER_ACTIONS.FETCH_ORDERS_REQUEST });
|
||||||
|
try {
|
||||||
|
const data = await ordersApi.getAllOrders(params);
|
||||||
|
dispatch({ type: ORDER_ACTIONS.FETCH_ORDERS_SUCCESS, payload: data });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: ORDER_ACTIONS.FETCH_ORDERS_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to fetch orders',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchOrder = (id) => async (dispatch) => {
|
||||||
|
dispatch({ type: ORDER_ACTIONS.FETCH_ORDER_REQUEST });
|
||||||
|
try {
|
||||||
|
const data = await ordersApi.getOrderById(id);
|
||||||
|
dispatch({ type: ORDER_ACTIONS.FETCH_ORDER_SUCCESS, payload: data });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: ORDER_ACTIONS.FETCH_ORDER_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to fetch order',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createOrder = (orderData) => async (dispatch) => {
|
||||||
|
dispatch({ type: ORDER_ACTIONS.CREATE_ORDER_REQUEST });
|
||||||
|
try {
|
||||||
|
const data = await ordersApi.createOrder(orderData);
|
||||||
|
dispatch({ type: ORDER_ACTIONS.CREATE_ORDER_SUCCESS, payload: data });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: ORDER_ACTIONS.CREATE_ORDER_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to create order',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateOrder = (id, orderData) => async (dispatch) => {
|
||||||
|
dispatch({ type: ORDER_ACTIONS.UPDATE_ORDER_REQUEST });
|
||||||
|
try {
|
||||||
|
const data = await ordersApi.updateOrder(id, orderData);
|
||||||
|
dispatch({ type: ORDER_ACTIONS.UPDATE_ORDER_SUCCESS, payload: { id, data } });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: ORDER_ACTIONS.UPDATE_ORDER_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to update order',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteOrder = (id) => async (dispatch) => {
|
||||||
|
dispatch({ type: ORDER_ACTIONS.DELETE_ORDER_REQUEST });
|
||||||
|
try {
|
||||||
|
await ordersApi.deleteOrder(id);
|
||||||
|
dispatch({ type: ORDER_ACTIONS.DELETE_ORDER_SUCCESS, payload: id });
|
||||||
|
return id;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: ORDER_ACTIONS.DELETE_ORDER_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to delete order',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchOrders = (query, params = {}) => async (dispatch) => {
|
||||||
|
dispatch({ type: ORDER_ACTIONS.SEARCH_ORDERS_REQUEST });
|
||||||
|
try {
|
||||||
|
const data = await ordersApi.searchOrders(query, params);
|
||||||
|
dispatch({ type: ORDER_ACTIONS.SEARCH_ORDERS_SUCCESS, payload: data });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: ORDER_ACTIONS.SEARCH_ORDERS_FAILURE,
|
||||||
|
payload: error.response?.data?.message || error.message || 'Failed to search orders',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearOrderError = () => ({
|
||||||
|
type: ORDER_ACTIONS.CLEAR_ORDER_ERROR,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clearCurrentOrder = () => ({
|
||||||
|
type: ORDER_ACTIONS.CLEAR_CURRENT_ORDER,
|
||||||
|
});
|
||||||
@ -89,6 +89,7 @@ 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,
|
||||||
@ -163,21 +164,6 @@ export const searchProducts = (query, params = {}) => async (dispatch) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch categories
|
|
||||||
export const fetchCategories = () => async (dispatch) => {
|
|
||||||
try {
|
|
||||||
const data = await productsApi.getCategories();
|
|
||||||
dispatch({
|
|
||||||
type: PRODUCT_ACTIONS.FETCH_CATEGORIES_SUCCESS,
|
|
||||||
payload: data,
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch categories:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch brands
|
// Fetch brands
|
||||||
export const fetchBrands = () => async (dispatch) => {
|
export const fetchBrands = () => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
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';
|
||||||
|
|
||||||
// Legacy reducer for existing functionality
|
// Legacy reducer for existing functionality
|
||||||
const legacyReducer = (state = initialState, action) => {
|
const legacyReducer = (state = initialState, action) => {
|
||||||
@ -73,6 +76,9 @@ 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,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|||||||
59
src/core/redux/reducers/authReducer.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
isAuthenticated: false,
|
||||||
|
user: null,
|
||||||
|
token: localStorage.getItem('authToken') || null,
|
||||||
|
loading: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const authSlice = createSlice({
|
||||||
|
name: 'auth',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
loginStart: (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
state.error = null;
|
||||||
|
},
|
||||||
|
loginSuccess: (state, action) => {
|
||||||
|
state.isAuthenticated = true;
|
||||||
|
state.user = action.payload.user;
|
||||||
|
state.token = action.payload.token;
|
||||||
|
state.loading = false;
|
||||||
|
state.error = null;
|
||||||
|
// Store token in localStorage
|
||||||
|
localStorage.setItem('authToken', action.payload.token);
|
||||||
|
localStorage.setItem('user', JSON.stringify(action.payload.user));
|
||||||
|
},
|
||||||
|
loginFailure: (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.payload;
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
state.user = null;
|
||||||
|
state.token = null;
|
||||||
|
},
|
||||||
|
logout: (state) => {
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
state.user = null;
|
||||||
|
state.token = null;
|
||||||
|
state.loading = false;
|
||||||
|
state.error = null;
|
||||||
|
// Clear localStorage
|
||||||
|
localStorage.removeItem('authToken');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
},
|
||||||
|
checkAuth: (state) => {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
const user = localStorage.getItem('user');
|
||||||
|
if (token && user) {
|
||||||
|
state.isAuthenticated = true;
|
||||||
|
state.token = token;
|
||||||
|
state.user = JSON.parse(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { loginStart, loginSuccess, loginFailure, logout, checkAuth } = authSlice.actions;
|
||||||
|
export default authSlice.reducer;
|
||||||
245
src/core/redux/reducers/categoryReducer.js
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
import { CATEGORY_ACTIONS } from '../actions/categoryActions';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
// Categories list
|
||||||
|
categories: [],
|
||||||
|
totalCategories: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
hasPrevious: false,
|
||||||
|
hasNext: false,
|
||||||
|
|
||||||
|
// Current category (for edit/view)
|
||||||
|
currentCategory: null,
|
||||||
|
|
||||||
|
// Search results
|
||||||
|
searchResults: [],
|
||||||
|
searchQuery: '',
|
||||||
|
|
||||||
|
// Category products
|
||||||
|
categoryProducts: [],
|
||||||
|
categoryProductsLoading: false,
|
||||||
|
categoryProductsError: null,
|
||||||
|
|
||||||
|
// Loading states
|
||||||
|
loading: false,
|
||||||
|
categoryLoading: false,
|
||||||
|
searchLoading: false,
|
||||||
|
|
||||||
|
// Error states
|
||||||
|
error: null,
|
||||||
|
categoryError: null,
|
||||||
|
searchError: null,
|
||||||
|
|
||||||
|
// Operation states
|
||||||
|
creating: false,
|
||||||
|
updating: false,
|
||||||
|
deleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const categoryReducer = (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
// Fetch Categories
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORIES_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORIES_SUCCESS: {
|
||||||
|
// Handle different API response structures
|
||||||
|
const { categories, total_count, page, total_pages, limit } =
|
||||||
|
action.payload.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
categories: categories,
|
||||||
|
totalCategories: total_count || categories.length,
|
||||||
|
currentPage: page || 1,
|
||||||
|
totalPages: total_pages || 1,
|
||||||
|
pageSize: limit || 10,
|
||||||
|
hasPrevious: false,
|
||||||
|
hasNext: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORIES_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch Single Category
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORY_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categoryLoading: true,
|
||||||
|
categoryError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORY_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categoryLoading: false,
|
||||||
|
currentCategory: action.payload.data,
|
||||||
|
categoryError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORY_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categoryLoading: false,
|
||||||
|
categoryError: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Category
|
||||||
|
case CATEGORY_ACTIONS.CREATE_CATEGORY_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
creating: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.CREATE_CATEGORY_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
creating: false,
|
||||||
|
categories: [action.payload.data, ...state.categories],
|
||||||
|
totalCategories: state.totalCategories + 1,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.CREATE_CATEGORY_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
creating: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update Category
|
||||||
|
case CATEGORY_ACTIONS.UPDATE_CATEGORY_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updating: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.UPDATE_CATEGORY_SUCCESS:
|
||||||
|
console.log('state', state)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updating: false,
|
||||||
|
categories: state.categories.map(category =>
|
||||||
|
category.id === action.payload.data.id ? action.payload.data : category
|
||||||
|
),
|
||||||
|
currentCategory: action.payload.data,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.UPDATE_CATEGORY_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updating: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete Category
|
||||||
|
case CATEGORY_ACTIONS.DELETE_CATEGORY_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleting: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.DELETE_CATEGORY_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleting: false,
|
||||||
|
categories: state.categories.filter(category => category.id !== action.payload),
|
||||||
|
totalCategories: state.totalCategories - 1,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.DELETE_CATEGORY_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleting: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search Categories
|
||||||
|
case CATEGORY_ACTIONS.SEARCH_CATEGORIES_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
searchLoading: true,
|
||||||
|
searchError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.SEARCH_CATEGORIES_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
searchLoading: false,
|
||||||
|
searchResults: action.payload.data || action.payload,
|
||||||
|
searchQuery: action.payload.query || '',
|
||||||
|
searchError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.SEARCH_CATEGORIES_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
searchLoading: false,
|
||||||
|
searchError: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Category Products
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categoryProductsLoading: true,
|
||||||
|
categoryProductsError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categoryProductsLoading: false,
|
||||||
|
categoryProducts: action.payload.data || action.payload,
|
||||||
|
categoryProductsError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.FETCH_CATEGORY_PRODUCTS_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categoryProductsLoading: false,
|
||||||
|
categoryProductsError: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear States
|
||||||
|
case CATEGORY_ACTIONS.CLEAR_CATEGORY_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: null,
|
||||||
|
categoryError: null,
|
||||||
|
searchError: null,
|
||||||
|
categoryProductsError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case CATEGORY_ACTIONS.CLEAR_CURRENT_CATEGORY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentCategory: null,
|
||||||
|
categoryError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default categoryReducer;
|
||||||
211
src/core/redux/reducers/orderReducer.js
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { ORDER_ACTIONS } from '../actions/orderActions';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
// Orders list
|
||||||
|
orders: [],
|
||||||
|
totalOrders: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
hasPrevious: false,
|
||||||
|
hasNext: false,
|
||||||
|
|
||||||
|
// Current order (for detail/edit)
|
||||||
|
currentOrder: null,
|
||||||
|
|
||||||
|
// Search results
|
||||||
|
searchResults: [],
|
||||||
|
searchQuery: '',
|
||||||
|
|
||||||
|
// Loading states
|
||||||
|
loading: false,
|
||||||
|
orderLoading: false,
|
||||||
|
searchLoading: false,
|
||||||
|
|
||||||
|
// Error states
|
||||||
|
error: null,
|
||||||
|
orderError: null,
|
||||||
|
searchError: null,
|
||||||
|
|
||||||
|
// Operation states
|
||||||
|
creating: false,
|
||||||
|
updating: false,
|
||||||
|
deleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const orderReducer = (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
// Fetch Orders
|
||||||
|
case ORDER_ACTIONS.FETCH_ORDERS_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.FETCH_ORDERS_SUCCESS: {
|
||||||
|
const { orders, total_count, page, total_pages, limit } = action.payload.data;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
orders: orders,
|
||||||
|
totalOrders: total_count || orders.length,
|
||||||
|
currentPage: page || 1,
|
||||||
|
totalPages: total_pages || 1,
|
||||||
|
pageSize: limit || 10,
|
||||||
|
hasPrevious: page > 1,
|
||||||
|
hasNext: page < total_pages,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.FETCH_ORDERS_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch Single Order
|
||||||
|
case ORDER_ACTIONS.FETCH_ORDER_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
orderLoading: true,
|
||||||
|
orderError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.FETCH_ORDER_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
orderLoading: false,
|
||||||
|
currentOrder: action.payload.data,
|
||||||
|
orderError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.FETCH_ORDER_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
orderLoading: false,
|
||||||
|
orderError: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Order
|
||||||
|
case ORDER_ACTIONS.CREATE_ORDER_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
creating: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.CREATE_ORDER_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
creating: false,
|
||||||
|
orders: [action.payload.data, ...state.orders],
|
||||||
|
totalOrders: state.totalOrders + 1,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.CREATE_ORDER_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
creating: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update Order
|
||||||
|
case ORDER_ACTIONS.UPDATE_ORDER_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updating: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.UPDATE_ORDER_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updating: false,
|
||||||
|
orders: state.orders.map(order =>
|
||||||
|
order.id === action.payload.data.id ? action.payload.data : order
|
||||||
|
),
|
||||||
|
currentOrder: action.payload.data,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.UPDATE_ORDER_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updating: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete Order
|
||||||
|
case ORDER_ACTIONS.DELETE_ORDER_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleting: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.DELETE_ORDER_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleting: false,
|
||||||
|
orders: state.orders.filter(order => order.id !== action.payload),
|
||||||
|
totalOrders: state.totalOrders - 1,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.DELETE_ORDER_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleting: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search Orders
|
||||||
|
case ORDER_ACTIONS.SEARCH_ORDERS_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
searchLoading: true,
|
||||||
|
searchError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.SEARCH_ORDERS_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
searchLoading: false,
|
||||||
|
searchResults: action.payload.data || action.payload,
|
||||||
|
searchQuery: action.payload.query || '',
|
||||||
|
searchError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.SEARCH_ORDERS_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
searchLoading: false,
|
||||||
|
searchError: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear States
|
||||||
|
case ORDER_ACTIONS.CLEAR_ORDER_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: null,
|
||||||
|
orderError: null,
|
||||||
|
searchError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ORDER_ACTIONS.CLEAR_CURRENT_ORDER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentOrder: null,
|
||||||
|
orderError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default orderReducer;
|
||||||
@ -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: 20,
|
pageSize: 10,
|
||||||
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,20 +49,19 @@ 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 isArrayResponse = Array.isArray(action.payload);
|
const { products, total_count, page, total_pages, limit } =
|
||||||
const products = isArrayResponse ? action.payload : (action.payload.data || action.payload.items || []);
|
action.payload.data;
|
||||||
const pagination = action.payload.pagination || {};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
loading: false,
|
||||||
products: products,
|
products: products,
|
||||||
totalProducts: pagination.totalCount || action.payload.total || products.length,
|
totalProducts: total_count,
|
||||||
currentPage: pagination.currentPage || action.payload.currentPage || 1,
|
currentPage: page,
|
||||||
totalPages: pagination.totalPages || action.payload.totalPages || 1,
|
totalPages: total_pages,
|
||||||
pageSize: pagination.pageSize || 20,
|
pageSize: limit,
|
||||||
hasPrevious: pagination.hasPrevious || false,
|
hasPrevious: false,
|
||||||
hasNext: pagination.hasNext || false,
|
hasNext: false,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -86,7 +85,7 @@ const productReducer = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
productLoading: false,
|
productLoading: false,
|
||||||
currentProduct: action.payload,
|
currentProduct: action.payload.data,
|
||||||
productError: null,
|
productError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,10 +105,11 @@ 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,7 +159,9 @@ const productReducer = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
products: state.products.filter(product => product.id !== action.payload),
|
products: state.products.filter(
|
||||||
|
(product) => product.id !== action.payload
|
||||||
|
),
|
||||||
totalProducts: state.totalProducts - 1,
|
totalProducts: state.totalProducts - 1,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
@ -184,7 +186,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,4 +5,5 @@ const store = configureStore({
|
|||||||
reducer: rootReducer,
|
reducer: rootReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export { store };
|
||||||
export default store;
|
export default store;
|
||||||
|
|||||||
@ -1,50 +1,131 @@
|
|||||||
import React, { useState } from 'react'
|
import { Select, Tag } from "antd";
|
||||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
import {
|
||||||
import ImageWithBasePath from '../../core/img/imagewithbasebath';
|
ChevronUp,
|
||||||
import { Link } from 'react-router-dom';
|
PlusCircle,
|
||||||
import { ChevronUp, Filter, PlusCircle, RotateCcw, Sliders, StopCircle, Zap } from 'feather-icons-react/build/IconComponents';
|
RotateCcw,
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
Trash2,
|
||||||
import { setToogleHeader } from '../../core/redux/action';
|
} from "feather-icons-react/build/IconComponents";
|
||||||
import Select from 'react-select';
|
import { useEffect, useState } from "react";
|
||||||
import { DatePicker } from 'antd';
|
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
import AddCategoryList from '../../core/modals/inventory/addcategorylist';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import EditCategoryList from '../../core/modals/inventory/editcategorylist';
|
import { Link } from "react-router-dom";
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
import Swal from "sweetalert2";
|
||||||
import Swal from 'sweetalert2';
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import Table from '../../core/pagination/datatable'
|
import CustomPagination from "../../components/CustomPagination";
|
||||||
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
|
import AddCategoryList from "../../core/modals/inventory/addcategorylist";
|
||||||
|
import EditCategoryList from "../../core/modals/inventory/editcategorylist";
|
||||||
|
import Table from "../../core/pagination/datatable";
|
||||||
|
import { setToogleHeader } from "../../core/redux/action";
|
||||||
|
import {
|
||||||
|
deleteCategory,
|
||||||
|
fetchCategories,
|
||||||
|
fetchCategory,
|
||||||
|
} from "../../core/redux/actions/categoryActions";
|
||||||
|
import { formatDate } from "../../utils/date";
|
||||||
|
|
||||||
const CategoryList = () => {
|
const 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 = useSelector((state) => state.categotylist_data);
|
const dataSource = apiCategories?.length > 0 ? apiCategories : [];
|
||||||
|
|
||||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||||
const toggleFilterVisibility = () => {
|
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
};
|
|
||||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||||
const handleDateChange = (date) => {
|
|
||||||
setSelectedDate(date);
|
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 !== "")
|
||||||
|
);
|
||||||
|
|
||||||
const oldandlatestvalue = [
|
await dispatch(fetchCategories(cleanParams));
|
||||||
{ value: 'date', label: 'Sort by Date' },
|
} catch (error) {
|
||||||
{ value: 'newest', label: 'Newest' },
|
console.error("Failed to load categories", error);
|
||||||
{ value: 'oldest', label: 'Oldest' },
|
}
|
||||||
];
|
};
|
||||||
const category = [
|
|
||||||
{ value: 'chooseCategory', label: 'Choose Category' },
|
loadCategories();
|
||||||
{ value: 'laptop', label: 'Laptop' },
|
}, [dispatch, currentPage, pageSize, debouncedSearchTerm]);
|
||||||
{ value: 'electronics', label: 'Electronics' },
|
|
||||||
{ value: 'shoe', label: 'Shoe' },
|
// Debounce search term
|
||||||
];
|
useEffect(() => {
|
||||||
const status = [
|
const timer = setTimeout(() => {
|
||||||
{ value: 'chooseStatus', label: 'Choose Status' },
|
setDebouncedSearchTerm(searchTerm);
|
||||||
{ value: 'active', label: 'Active' },
|
}, 500); // 500ms delay
|
||||||
{ value: 'inactive', label: 'Inactive' },
|
|
||||||
];
|
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) => (
|
const renderTooltip = (props) => (
|
||||||
<Tooltip id="pdf-tooltip" {...props}>
|
<Tooltip id="pdf-tooltip" {...props}>
|
||||||
@ -70,82 +151,108 @@ const CategoryList = () => {
|
|||||||
<Tooltip id="refresh-tooltip" {...props}>
|
<Tooltip id="refresh-tooltip" {...props}>
|
||||||
Collapse
|
Collapse
|
||||||
</Tooltip>
|
</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 = [
|
const columns = [
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Category",
|
title: "Category",
|
||||||
dataIndex: "category",
|
dataIndex: "category",
|
||||||
|
render: (_, record) => {
|
||||||
|
return <span>{record.name}</span>;
|
||||||
|
},
|
||||||
sorter: (a, b) => a.category.length - b.category.length,
|
sorter: (a, b) => a.category.length - b.category.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Category Slug",
|
title: "Category Slug",
|
||||||
dataIndex: "categoryslug",
|
dataIndex: "categoryslug",
|
||||||
|
render: (_, record) => {
|
||||||
|
return <span>{record?.name?.toLowerCase()}</span>;
|
||||||
|
},
|
||||||
sorter: (a, b) => a.categoryslug.length - b.categoryslug.length,
|
sorter: (a, b) => a.categoryslug.length - b.categoryslug.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Created On",
|
title: "Created On",
|
||||||
dataIndex: "createdon",
|
dataIndex: "createdon",
|
||||||
|
render: (_, record) => {
|
||||||
|
return <span>{formatDate(record.created_at)}</span>;
|
||||||
|
},
|
||||||
sorter: (a, b) => a.createdon.length - b.createdon.length,
|
sorter: (a, b) => a.createdon.length - b.createdon.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Status",
|
title: "Status",
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
render: (text) => (
|
render: () => <Tag color="#87d068">active</Tag>,
|
||||||
<span className="badge badge-linesuccess">
|
|
||||||
<Link to="#"> {text}</Link>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
sorter: (a, b) => a.status.length - b.status.length,
|
sorter: (a, b) => a.status.length - b.status.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Actions',
|
title: "Actions",
|
||||||
dataIndex: 'actions',
|
dataIndex: "actions",
|
||||||
key: 'actions',
|
key: "actions",
|
||||||
render: () => (
|
render: (_, record) => (
|
||||||
<td className="action-table-data">
|
<td className="action-table-data">
|
||||||
<div className="edit-delete-action">
|
<div className="edit-delete-action">
|
||||||
<Link className="me-2 p-2" to="#" data-bs-toggle="modal" data-bs-target="#edit-category">
|
<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>
|
<i data-feather="edit" className="feather-edit"></i>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="confirm-text p-2" to="#" >
|
<Link
|
||||||
<i data-feather="trash-2" className="feather-trash-2" onClick={showConfirmationAlert}></i>
|
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>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
const showConfirmationAlert = () => {
|
// 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!",
|
||||||
showCancelButton: true,
|
// showCancelButton: true,
|
||||||
confirmButtonColor: '#00ff00',
|
// confirmButtonColor: "#00ff00",
|
||||||
confirmButtonText: 'Yes, delete it!',
|
// confirmButtonText: "Yes, delete it!",
|
||||||
cancelButtonColor: '#ff0000',
|
// cancelButtonColor: "#ff0000",
|
||||||
cancelButtonText: 'Cancel',
|
// cancelButtonText: "Cancel",
|
||||||
}).then((result) => {
|
// }).then((result) => {
|
||||||
if (result.isConfirmed) {
|
// if (result.isConfirmed) {
|
||||||
|
// handleDeleteCategory();
|
||||||
MySwal.fire({
|
// } else {
|
||||||
title: 'Deleted!',
|
// MySwal.close();
|
||||||
text: 'Your file has been deleted.',
|
// }
|
||||||
className: "btn btn-success",
|
// });
|
||||||
confirmButtonText: 'OK',
|
// };
|
||||||
customClass: {
|
|
||||||
confirmButton: 'btn btn-success',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
MySwal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
@ -161,20 +268,25 @@ const CategoryList = () => {
|
|||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderTooltip}>
|
<OverlayTrigger placement="top" overlay={renderTooltip}>
|
||||||
<Link>
|
<Link>
|
||||||
<ImageWithBasePath src="assets/img/icons/pdf.svg" alt="img" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/pdf.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
|
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
|
||||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||||
<ImageWithBasePath src="assets/img/icons/excel.svg" alt="img" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/excel.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
|
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
|
||||||
|
|
||||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||||
<i data-feather="printer" className="feather-printer" />
|
<i data-feather="printer" className="feather-printer" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -182,7 +294,6 @@ const CategoryList = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
|
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
|
||||||
|
|
||||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||||
<RotateCcw />
|
<RotateCcw />
|
||||||
</Link>
|
</Link>
|
||||||
@ -190,13 +301,14 @@ const CategoryList = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
|
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
data-bs-toggle="tooltip"
|
data-bs-toggle="tooltip"
|
||||||
data-bs-placement="top"
|
data-bs-placement="top"
|
||||||
id="collapse-header"
|
id="collapse-header"
|
||||||
className={data ? "active" : ""}
|
className={data ? "active" : ""}
|
||||||
onClick={() => { dispatch(setToogleHeader(!data)) }}
|
onClick={() => {
|
||||||
|
dispatch(setToogleHeader(!data));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ChevronUp />
|
<ChevronUp />
|
||||||
</Link>
|
</Link>
|
||||||
@ -225,100 +337,69 @@ const CategoryList = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="form-control form-control-sm formsearch"
|
className="form-control form-control-sm formsearch"
|
||||||
|
onChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
<Link to className="btn btn-searchset">
|
<Link to className="btn btn-searchset">
|
||||||
<i data-feather="search" className="feather-search" />
|
<i data-feather="search" className="feather-search" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="search-path">
|
|
||||||
<Link className={`btn btn-filter ${isFilterVisible ? "setclose" : ""}`} id="filter_search">
|
|
||||||
<Filter
|
|
||||||
className="filter-icon"
|
|
||||||
onClick={toggleFilterVisibility}
|
|
||||||
/>
|
|
||||||
<span onClick={toggleFilterVisibility}>
|
|
||||||
<ImageWithBasePath src="assets/img/icons/closes.svg" alt="img" />
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="form-sort">
|
|
||||||
<Sliders className="info-img" />
|
|
||||||
<Select
|
<Select
|
||||||
className="select"
|
style={{ height: 36 }}
|
||||||
options={oldandlatestvalue}
|
defaultValue={dateOptions[0]?.value}
|
||||||
placeholder="Newest"
|
options={dateOptions}
|
||||||
/>
|
/>
|
||||||
</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-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">
|
<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} />
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* /product list */}
|
{/* /category list */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddCategoryList />
|
<AddCategoryList />
|
||||||
<EditCategoryList />
|
<EditCategoryList />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CategoryList
|
export default CategoryList;
|
||||||
|
|||||||
@ -1,7 +1,29 @@
|
|||||||
import React from 'react'
|
import { useEffect, useState } from "react";
|
||||||
import ImageWithBasePath from '../../core/img/imagewithbasebath'
|
import { useParams } from "react-router-dom";
|
||||||
|
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||||
|
import productsApi from "../../services/productsApi";
|
||||||
|
import { formatRupiah } from "../../utils/currency";
|
||||||
|
|
||||||
const ProductDetail = () => {
|
const ProductDetail = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const [currentProduct, setCurrentProduct] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchProduct = async () => {
|
||||||
|
try {
|
||||||
|
const response = await productsApi.getProductById(id);
|
||||||
|
setCurrentProduct(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching product:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
fetchProduct();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-wrapper">
|
<div className="page-wrapper">
|
||||||
@ -18,28 +40,30 @@ const ProductDetail = () => {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="bar-code-view">
|
<div className="bar-code-view">
|
||||||
<ImageWithBasePath src="assets/img/barcode/barcode1.png" alt="barcode" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/barcode/barcode1.png"
|
||||||
|
alt="barcode"
|
||||||
|
/>
|
||||||
<a className="printimg">
|
<a className="printimg">
|
||||||
<ImageWithBasePath src="assets/img/icons/printer.svg" alt="print" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/printer.svg"
|
||||||
|
alt="print"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="productdetails">
|
<div className="productdetails">
|
||||||
<ul className="product-bar">
|
<ul className="product-bar">
|
||||||
<li>
|
<li>
|
||||||
<h4>Product</h4>
|
<h4>Product</h4>
|
||||||
<h6>Macbook pro </h6>
|
<h6>{currentProduct?.name} </h6>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Category</h4>
|
<h4>Category</h4>
|
||||||
<h6>Computers</h6>
|
<h6>{currentProduct?.category ?? "-"}</h6>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Sub Category</h4>
|
<h4>Business</h4>
|
||||||
<h6>None</h6>
|
<h6>{currentProduct?.business_type ?? "-"}</h6>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>Brand</h4>
|
|
||||||
<h6>None</h6>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Unit</h4>
|
<h4>Unit</h4>
|
||||||
@ -47,56 +71,82 @@ const ProductDetail = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>SKU</h4>
|
<h4>SKU</h4>
|
||||||
<h6>PT0001</h6>
|
<h6>{currentProduct?.sku ?? "-"}</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>
|
||||||
<li>
|
<li>
|
||||||
<h4>Price</h4>
|
<h4>Price</h4>
|
||||||
<h6>1500.00</h6>
|
<h6>{formatRupiah(Number(currentProduct?.price))}</h6>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Status</h4>
|
<h4>Status</h4>
|
||||||
<h6>Active</h6>
|
<h6>
|
||||||
|
{currentProduct?.is_active ? (
|
||||||
|
<span className="badge text-bg-success">
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Inactive"
|
||||||
|
)}
|
||||||
|
</h6>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h4>Description</h4>
|
<h4>Description</h4>
|
||||||
<h6>
|
<h6>{currentProduct?.description ?? "-"}</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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{Array.isArray(currentProduct?.variants) &&
|
||||||
|
currentProduct.variants.map((variant, index) => (
|
||||||
|
<div key={index} className="card">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="productdetails">
|
||||||
|
<ul className="product-bar">
|
||||||
|
<li>
|
||||||
|
<h4>Variant</h4>
|
||||||
|
<h6>{variant?.name} </h6>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Price</h4>
|
||||||
|
<h6>
|
||||||
|
{formatRupiah(Number(variant.price_modifier))}
|
||||||
|
</h6>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Cost</h4>
|
||||||
|
<h6>{formatRupiah(Number(variant.cost))}</h6>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-lg-4 col-sm-12">
|
<div className="col-lg-4 col-sm-12">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="slider-product-details">
|
<div className="slider-product-details">
|
||||||
<div className="owl-carousel owl-theme product-slide">
|
<div className="owl-carousel owl-theme product-slide">
|
||||||
<div className="slider-product">
|
<div className="slider-product">
|
||||||
<ImageWithBasePath src="assets/img/products/product69.jpg" alt="img" />
|
<ImageWithBasePath
|
||||||
<h4>macbookpro.jpg</h4>
|
src="assets/img/products/product69.jpg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
|
<h4>{currentProduct?.name}</h4>
|
||||||
<h6>581kb</h6>
|
<h6>581kb</h6>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -106,10 +156,8 @@ const ProductDetail = () => {
|
|||||||
{/* /add */}
|
{/* /add */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProductDetail
|
export default ProductDetail;
|
||||||
|
|||||||
@ -1,36 +1,33 @@
|
|||||||
|
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 React, { useState, useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
|
import { Download } from "react-feather";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Select from "react-select";
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import CustomPagination from "../../components/CustomPagination";
|
||||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
import 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 {
|
||||||
fetchProducts,
|
clearProductError,
|
||||||
fetchProduct,
|
|
||||||
deleteProduct,
|
deleteProduct,
|
||||||
clearProductError
|
fetchProduct,
|
||||||
|
fetchProducts,
|
||||||
} from "../../core/redux/actions/productActions";
|
} from "../../core/redux/actions/productActions";
|
||||||
import CustomPagination from '../../components/CustomPagination';
|
import { all_routes } from "../../Router/all_routes";
|
||||||
|
import categoriesApi from "../../services/categoriesApi";
|
||||||
|
import { formatRupiah } from "../../utils/currency";
|
||||||
|
|
||||||
// Add CSS animations for beautiful UI
|
// Add CSS animations for beautiful UI
|
||||||
const shimmerKeyframes = `
|
const shimmerKeyframes = `
|
||||||
@ -62,11 +59,16 @@ const shimmerKeyframes = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Inject CSS into head if not already present
|
// Inject CSS into head if not already present
|
||||||
if (typeof document !== 'undefined' && !document.getElementById('beautiful-pagination-styles')) {
|
if (
|
||||||
const styleSheet = document.createElement('style');
|
typeof document !== "undefined" &&
|
||||||
styleSheet.id = 'beautiful-pagination-styles';
|
!document.getElementById("beautiful-pagination-styles")
|
||||||
styleSheet.type = 'text/css';
|
) {
|
||||||
styleSheet.innerText = shimmerKeyframes + `
|
const styleSheet = document.createElement("style");
|
||||||
|
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,
|
||||||
@ -433,40 +435,24 @@ const ProductList = () => {
|
|||||||
totalProducts,
|
totalProducts,
|
||||||
totalPages,
|
totalPages,
|
||||||
pageSize: reduxPageSize,
|
pageSize: reduxPageSize,
|
||||||
currentPage: reduxCurrentPage
|
currentPage: reduxCurrentPage,
|
||||||
} = useSelector((state) => state.products);
|
} = useSelector((state) => state.products);
|
||||||
|
|
||||||
// Fallback to legacy data if API data is not available
|
const dataSource = apiProducts?.length > 0 ? apiProducts : [];
|
||||||
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 || 20);
|
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
// State for filter values
|
const [category, setCategory] = useState(null);
|
||||||
const [filterValues, setFilterValues] = useState({
|
|
||||||
product: '',
|
|
||||||
category: '',
|
|
||||||
subCategory: '',
|
|
||||||
brand: '',
|
|
||||||
priceRange: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// Debounced search term
|
// Debounced search term
|
||||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||||
|
|
||||||
const toggleFilterVisibility = () => {
|
const [categoryOptions, setCategoryOptions] = useState([]);
|
||||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
|
||||||
};
|
|
||||||
|
|
||||||
const route = all_routes;
|
const route = all_routes;
|
||||||
|
|
||||||
@ -479,29 +465,51 @@ 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,
|
||||||
PageSize: pageSize,
|
limit: pageSize,
|
||||||
SearchTerm: debouncedSearchTerm || ''
|
search: 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]);
|
}, [dispatch, currentPage, pageSize, debouncedSearchTerm, category]);
|
||||||
|
|
||||||
// Handle product deletion
|
// Handle product deletion
|
||||||
const handleDeleteProduct = async (productId) => {
|
const handleDeleteProduct = async (productId) => {
|
||||||
@ -518,7 +526,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.",
|
||||||
@ -542,74 +550,12 @@ 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
|
||||||
@ -617,85 +563,73 @@ 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 = [
|
||||||
{ value: "sortByDate", label: "Sort by Date" },
|
{ label: "Sort By: Last 7 Days", value: "last7days" },
|
||||||
{ value: "140923", label: "14 09 23" },
|
{ label: "Sort By: Last Month", value: "lastmonth" },
|
||||||
{ value: "110923", label: "11 09 23" },
|
{ label: "Sort By: Ascending", value: "ascending" },
|
||||||
|
{ label: "Sort By: Descending", value: "descending" },
|
||||||
];
|
];
|
||||||
// Removed unused select option arrays since we're using simple inputs now
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Sản phẩm",
|
title: "SKU",
|
||||||
dataIndex: "product",
|
|
||||||
render: (text, record) => (
|
|
||||||
<span className="productimgname">
|
|
||||||
<Link to="/profile" className="product-img stock-img">
|
|
||||||
<ImageWithBasePath
|
|
||||||
alt={record.name || text || "Product"}
|
|
||||||
src={record.productImage || record.image || record.img}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<Link to="/profile">{text}</Link>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
sorter: (a, b) => a.product.length - b.product.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Mã",
|
|
||||||
dataIndex: "sku",
|
dataIndex: "sku",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const sku = record.sku || record.code || record.productCode || '-';
|
const sku = record.sku || record.code || record.productCode || "-";
|
||||||
return <span>{sku}</span>;
|
return <span>{sku}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const skuA = a.sku || a.code || a.productCode || '';
|
const skuA = a.sku || a.code || a.productCode || "";
|
||||||
const skuB = b.sku || b.code || b.productCode || '';
|
const skuB = b.sku || b.code || b.productCode || "";
|
||||||
return skuA.length - skuB.length;
|
return skuA.length - skuB.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Danh mục",
|
title: "Product",
|
||||||
|
dataIndex: "product",
|
||||||
|
render: (text, record) => <span className="fw-medium">{record.name}</span>,
|
||||||
|
sorter: (a, b) => a.product.length - b.product.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Category",
|
||||||
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: "Thương hiệu",
|
title: "Business",
|
||||||
dataIndex: "brand",
|
dataIndex: "business",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const brand = record.brand || record.brandName || '-';
|
const brand = record.business_type || 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: "Giá",
|
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>${Number(price).toFixed(2)}</span>;
|
return <span>{formatRupiah(Number(price))}</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);
|
||||||
@ -704,15 +638,15 @@ const ProductList = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Đơn vị",
|
title: "Unit",
|
||||||
dataIndex: "unit",
|
dataIndex: "unit",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const unit = record.unit || record.unitOfMeasure || '-';
|
const unit = record.unit || record.unitOfMeasure || "-";
|
||||||
return <span>{unit}</span>;
|
return <span>{unit}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const unitA = a.unit || a.unitOfMeasure || '';
|
const unitA = a.unit || a.unitOfMeasure || "";
|
||||||
const unitB = b.unit || b.unitOfMeasure || '';
|
const unitB = b.unit || b.unitOfMeasure || "";
|
||||||
return unitA.length - unitB.length;
|
return unitA.length - unitB.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -721,7 +655,12 @@ const ProductList = () => {
|
|||||||
dataIndex: "qty",
|
dataIndex: "qty",
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
// Try multiple possible field names for quantity
|
// Try multiple possible field names for quantity
|
||||||
const quantity = record.qty || record.quantity || record.stock || record.stockQuantity || 0;
|
const quantity =
|
||||||
|
record.qty ||
|
||||||
|
record.quantity ||
|
||||||
|
record.stock ||
|
||||||
|
record.stockQuantity ||
|
||||||
|
0;
|
||||||
return <span>{quantity}</span>;
|
return <span>{quantity}</span>;
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
@ -732,11 +671,14 @@ const ProductList = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Người tạo",
|
title: "Created By",
|
||||||
dataIndex: "createdby",
|
dataIndex: "createdby",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span className="created-by-text">
|
<span className="created-by-text">
|
||||||
<Link to="/profile" style={{ color: '#3498db', textDecoration: 'none' }}>
|
<Link
|
||||||
|
to="/profile"
|
||||||
|
style={{ color: "#3498db", textDecoration: "none" }}
|
||||||
|
>
|
||||||
{record.createdBy || text || "Admin"}
|
{record.createdBy || text || "Admin"}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
@ -744,14 +686,17 @@ const ProductList = () => {
|
|||||||
sorter: (a, b) => a.createdby.length - b.createdby.length,
|
sorter: (a, b) => a.createdby.length - b.createdby.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Thao tác",
|
title: "Action",
|
||||||
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 className="me-2 p-2" to={route.productdetails}>
|
<Link
|
||||||
<Eye className="feather-view" />
|
className="me-2 p-2"
|
||||||
|
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"
|
||||||
@ -795,8 +740,6 @@ 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
|
||||||
@ -822,14 +765,15 @@ 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>Danh sách sản phẩm</h4>
|
<h4>Product List</h4>
|
||||||
<h6>Quản lý sản phẩm</h6>
|
<h6>Manage your products</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul className="table-top-head">
|
<ul className="table-top-head">
|
||||||
@ -884,18 +828,17 @@ 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" />
|
||||||
Thêm mới
|
Add New Product
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-btn import">
|
<div className="page-btn import">
|
||||||
<Link
|
<Link
|
||||||
to="#"
|
className="btn btn-outline-primary rounded-2"
|
||||||
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" />
|
<Download className="me-2 iconsize" />
|
||||||
Nhập sản phẩm
|
Import Product
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -917,291 +860,28 @@ const ProductList = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="search-path">
|
|
||||||
<Link
|
<Space warp>
|
||||||
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
|
||||||
className="select"
|
style={{ height: 36, width: 100 }}
|
||||||
|
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,16 +1,47 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import ImageWithBasePath from "../../../core/img/imagewithbasebath";
|
import 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 action="index">
|
<form onSubmit={handleSubmit}>
|
||||||
<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" />
|
||||||
@ -24,10 +55,22 @@ 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 type="text" className="form- control" />
|
<input
|
||||||
|
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"
|
||||||
@ -39,7 +82,11 @@ 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>
|
||||||
@ -63,9 +110,13 @@ const Signin = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-login">
|
<div className="form-login">
|
||||||
<Link to={route.dashboard} className="btn btn-login">
|
<button
|
||||||
Sign In
|
type="submit"
|
||||||
</Link>
|
className="btn btn-login"
|
||||||
|
disabled={authState.loading}
|
||||||
|
>
|
||||||
|
{authState.loading ? 'Signing In...' : 'Sign In'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="signinform">
|
<div className="signinform">
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
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">
|
||||||
@ -28,16 +31,18 @@ const Profile = () => {
|
|||||||
<div className="profileupload">
|
<div className="profileupload">
|
||||||
<input type="file" id="imgInp" />
|
<input type="file" id="imgInp" />
|
||||||
<Link to="#">
|
<Link to="#">
|
||||||
<ImageWithBasePath src="assets/img/icons/edit-set.svg" alt="img" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/edit-set.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="profile-contentname">
|
<div className="profile-contentname">
|
||||||
<h2>William Castillo</h2>
|
<h2>{user?.name}</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">
|
||||||
@ -68,6 +73,7 @@ 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>
|
||||||
|
|||||||
@ -1,51 +1,71 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useEffect, 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 { Filter, PlusCircle, Sliders, StopCircle, User, Zap } from 'react-feather';
|
import {
|
||||||
import Select from 'react-select';
|
Filter,
|
||||||
import withReactContent from 'sweetalert2-react-content';
|
PlusCircle,
|
||||||
import Swal from 'sweetalert2';
|
Sliders,
|
||||||
import Table from '../../core/pagination/datatable'
|
StopCircle,
|
||||||
import AddUsers from '../../core/modals/usermanagement/addusers';
|
User,
|
||||||
import EditUser from '../../core/modals/usermanagement/edituser';
|
Zap,
|
||||||
|
} from "react-feather";
|
||||||
|
import Select from "react-select";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import Table from "../../core/pagination/datatable";
|
||||||
|
import AddUsers from "../../core/modals/usermanagement/addusers";
|
||||||
|
import EditUser from "../../core/modals/usermanagement/edituser";
|
||||||
|
import usersApi from "../../services/usersApi";
|
||||||
|
|
||||||
const Users = () => {
|
const Users = () => {
|
||||||
|
|
||||||
const oldandlatestvalue = [
|
const oldandlatestvalue = [
|
||||||
{ value: 'date', label: 'Sort by Date' },
|
{ value: "date", label: "Sort by Date" },
|
||||||
{ value: 'newest', label: 'Newest' },
|
{ value: "newest", label: "Newest" },
|
||||||
{ value: 'oldest', label: 'Oldest' },
|
{ value: "oldest", label: "Oldest" },
|
||||||
];
|
];
|
||||||
const users = [
|
const users = [
|
||||||
{ value: 'Choose Name', label: 'Choose Name' },
|
{ value: "Choose Name", label: "Choose Name" },
|
||||||
{ value: 'Lilly', label: 'Lilly' },
|
{ value: "Lilly", label: "Lilly" },
|
||||||
{ value: 'Benjamin', label: 'Benjamin' },
|
{ value: "Benjamin", label: "Benjamin" },
|
||||||
];
|
];
|
||||||
const status = [
|
const status = [
|
||||||
{ value: 'Choose Name', label: 'Choose Status' },
|
{ value: "Choose Name", label: "Choose Status" },
|
||||||
{ value: 'Active', label: 'Active' },
|
{ value: "Active", label: "Active" },
|
||||||
{ value: 'InActive', label: 'InActive' },
|
{ value: "InActive", label: "InActive" },
|
||||||
];
|
];
|
||||||
const role = [
|
const role = [
|
||||||
{ value: 'Choose Role', label: 'Choose Role' },
|
{ value: "Choose Role", label: "Choose Role" },
|
||||||
{ value: 'AcStore Keeper', label: 'Store Keeper' },
|
{ value: "AcStore Keeper", label: "Store Keeper" },
|
||||||
{ value: 'Salesman', label: 'Salesman' },
|
{ value: "Salesman", label: "Salesman" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const data = useSelector((state) => state.toggle_header);
|
const data = useSelector((state) => state.toggle_header);
|
||||||
const dataSource = useSelector((state) => state.userlist_data);
|
const [dataSource, setDataSource] = useState([])
|
||||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
const toggleFilterVisibility = () => {
|
const toggleFilterVisibility = () => {
|
||||||
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
setIsFilterVisible((prevVisibility) => !prevVisibility);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadUsers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await usersApi.getAllUsers();
|
||||||
|
setDataSource(response)
|
||||||
|
|
||||||
|
console.log('response', response)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch users", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadUsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const renderTooltip = (props) => (
|
const renderTooltip = (props) => (
|
||||||
<Tooltip id="pdf-tooltip" {...props}>
|
<Tooltip id="pdf-tooltip" {...props}>
|
||||||
Pdf
|
Pdf
|
||||||
@ -70,10 +90,9 @@ const Users = () => {
|
|||||||
<Tooltip id="refresh-tooltip" {...props}>
|
<Tooltip id="refresh-tooltip" {...props}>
|
||||||
Collapse
|
Collapse
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "User Name",
|
title: "User Name",
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
@ -121,60 +140,68 @@ const Users = () => {
|
|||||||
{text === "Inactive" && (
|
{text === "Inactive" && (
|
||||||
<span className="badge badge-linedanger">{text}</span>
|
<span className="badge badge-linedanger">{text}</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
sorter: (a, b) => a.status.length - b.status.length,
|
sorter: (a, b) => a.status.length - b.status.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Actions',
|
title: "Actions",
|
||||||
dataIndex: 'actions',
|
dataIndex: "actions",
|
||||||
key: 'actions',
|
key: "actions",
|
||||||
render: () => (
|
render: () => (
|
||||||
<td className="action-table-data">
|
<td className="action-table-data">
|
||||||
<div className="edit-delete-action">
|
<div className="edit-delete-action">
|
||||||
|
|
||||||
<Link className="me-2 p-2" to="#">
|
<Link className="me-2 p-2" to="#">
|
||||||
<i data-feather="eye" className="feather feather-eye action-eye"></i>
|
<i
|
||||||
|
data-feather="eye"
|
||||||
|
className="feather feather-eye action-eye"
|
||||||
|
></i>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="me-2 p-2" to="#" data-bs-toggle="modal" data-bs-target="#edit-units">
|
<Link
|
||||||
|
className="me-2 p-2"
|
||||||
|
to="#"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#edit-units"
|
||||||
|
>
|
||||||
<i data-feather="edit" className="feather-edit"></i>
|
<i data-feather="edit" className="feather-edit"></i>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="confirm-text p-2" to="#">
|
<Link className="confirm-text p-2" to="#">
|
||||||
<i data-feather="trash-2" className="feather-trash-2" onClick={showConfirmationAlert}></i>
|
<i
|
||||||
|
data-feather="trash-2"
|
||||||
|
className="feather-trash-2"
|
||||||
|
onClick={showConfirmationAlert}
|
||||||
|
></i>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
const showConfirmationAlert = () => {
|
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!",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
confirmButtonColor: '#00ff00',
|
confirmButtonColor: "#00ff00",
|
||||||
confirmButtonText: 'Yes, delete it!',
|
confirmButtonText: "Yes, delete it!",
|
||||||
cancelButtonColor: '#ff0000',
|
cancelButtonColor: "#ff0000",
|
||||||
cancelButtonText: 'Cancel',
|
cancelButtonText: "Cancel",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: 'Deleted!',
|
title: "Deleted!",
|
||||||
text: 'Your file has been deleted.',
|
text: "Your file has been deleted.",
|
||||||
className: "btn btn-success",
|
className: "btn btn-success",
|
||||||
confirmButtonText: 'OK',
|
confirmButtonText: "OK",
|
||||||
customClass: {
|
customClass: {
|
||||||
confirmButton: 'btn btn-success',
|
confirmButton: "btn btn-success",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
MySwal.close();
|
MySwal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@ -192,20 +219,25 @@ const Users = () => {
|
|||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderTooltip}>
|
<OverlayTrigger placement="top" overlay={renderTooltip}>
|
||||||
<Link>
|
<Link>
|
||||||
<ImageWithBasePath src="assets/img/icons/pdf.svg" alt="img" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/pdf.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
|
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
|
||||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||||
<ImageWithBasePath src="assets/img/icons/excel.svg" alt="img" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/excel.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
|
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
|
||||||
|
|
||||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||||
<i data-feather="printer" className="feather-printer" />
|
<i data-feather="printer" className="feather-printer" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -213,7 +245,6 @@ const Users = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
|
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
|
||||||
|
|
||||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||||
<RotateCcw />
|
<RotateCcw />
|
||||||
</Link>
|
</Link>
|
||||||
@ -221,13 +252,14 @@ const Users = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
|
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
data-bs-toggle="tooltip"
|
data-bs-toggle="tooltip"
|
||||||
data-bs-placement="top"
|
data-bs-placement="top"
|
||||||
id="collapse-header"
|
id="collapse-header"
|
||||||
className={data ? "active" : ""}
|
className={data ? "active" : ""}
|
||||||
onClick={() => { dispatch(setToogleHeader(!data)) }}
|
onClick={() => {
|
||||||
|
dispatch(setToogleHeader(!data));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ChevronUp />
|
<ChevronUp />
|
||||||
</Link>
|
</Link>
|
||||||
@ -263,13 +295,21 @@ const Users = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="search-path">
|
<div className="search-path">
|
||||||
<Link className={`btn btn-filter ${isFilterVisible ? "setclose" : ""}`} id="filter_search">
|
<Link
|
||||||
|
className={`btn btn-filter ${
|
||||||
|
isFilterVisible ? "setclose" : ""
|
||||||
|
}`}
|
||||||
|
id="filter_search"
|
||||||
|
>
|
||||||
<Filter
|
<Filter
|
||||||
className="filter-icon"
|
className="filter-icon"
|
||||||
onClick={toggleFilterVisibility}
|
onClick={toggleFilterVisibility}
|
||||||
/>
|
/>
|
||||||
<span onClick={toggleFilterVisibility}>
|
<span onClick={toggleFilterVisibility}>
|
||||||
<ImageWithBasePath src="assets/img/icons/closes.svg" alt="img" />
|
<ImageWithBasePath
|
||||||
|
src="assets/img/icons/closes.svg"
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -284,9 +324,9 @@ const Users = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* /Filter */}
|
{/* /Filter */}
|
||||||
<div
|
<div
|
||||||
className={`card${isFilterVisible ? ' visible' : ''}`}
|
className={`card${isFilterVisible ? " visible" : ""}`}
|
||||||
id="filter_inputs"
|
id="filter_inputs"
|
||||||
style={{ display: isFilterVisible ? 'block' : 'none' }}
|
style={{ display: isFilterVisible ? "block" : "none" }}
|
||||||
>
|
>
|
||||||
<div className="card-body pb-0">
|
<div className="card-body pb-0">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -327,7 +367,10 @@ const Users = () => {
|
|||||||
<div className="input-blocks">
|
<div className="input-blocks">
|
||||||
<a className="btn btn-filters ms-auto">
|
<a className="btn btn-filters ms-auto">
|
||||||
{" "}
|
{" "}
|
||||||
<i data-feather="search" className="feather-search" />{" "}
|
<i
|
||||||
|
data-feather="search"
|
||||||
|
className="feather-search"
|
||||||
|
/>{" "}
|
||||||
Search{" "}
|
Search{" "}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -338,7 +381,6 @@ const Users = () => {
|
|||||||
{/* /Filter */}
|
{/* /Filter */}
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<Table columns={columns} dataSource={dataSource} />
|
<Table columns={columns} dataSource={dataSource} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -348,7 +390,7 @@ const Users = () => {
|
|||||||
<AddUsers />
|
<AddUsers />
|
||||||
<EditUser />
|
<EditUser />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Users
|
export default Users;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ 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');
|
||||||
|
|
||||||
@ -49,6 +50,9 @@ 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,6 +41,7 @@ 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);
|
||||||
|
|||||||
64
src/services/authApi.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { store } from '../core/redux/store';
|
||||||
|
import { loginStart, loginSuccess, loginFailure, logout, checkAuth } from '../core/redux/reducers/authReducer';
|
||||||
|
import api from './api';
|
||||||
|
|
||||||
|
const ENDPOINTS = {
|
||||||
|
LOGIN: 'auth/login',
|
||||||
|
LOGOUT: 'auth/logout',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authApi = {
|
||||||
|
// Check if user is authenticated
|
||||||
|
isAuthenticated() {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
const user = localStorage.getItem('user');
|
||||||
|
return !!(token && user);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get current user
|
||||||
|
getCurrentUser() {
|
||||||
|
const user = localStorage.getItem('user');
|
||||||
|
return user ? JSON.parse(user) : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get auth token
|
||||||
|
getToken() {
|
||||||
|
return localStorage.getItem('authToken');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Login function
|
||||||
|
async login(credentials) {
|
||||||
|
try {
|
||||||
|
store.dispatch(loginStart());
|
||||||
|
|
||||||
|
const response = await api.post(ENDPOINTS.LOGIN, credentials);
|
||||||
|
|
||||||
|
store.dispatch(loginSuccess(response.data));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
store.dispatch(loginFailure(error.message));
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Logout function
|
||||||
|
logout() {
|
||||||
|
store.dispatch(logout());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check authentication status on app start
|
||||||
|
checkAuthStatus() {
|
||||||
|
store.dispatch(checkAuth());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set auth headers for API requests
|
||||||
|
getAuthHeaders() {
|
||||||
|
const token = this.getToken();
|
||||||
|
return {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default authApi
|
||||||
116
src/services/categoriesApi.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import api from './api';
|
||||||
|
|
||||||
|
// Categories API endpoints
|
||||||
|
const ENDPOINTS = {
|
||||||
|
CATEGORIES: 'categories',
|
||||||
|
CATEGORY_BY_ID: (id) => `categories/${id}`,
|
||||||
|
CATEGORY_PRODUCTS: (id) => `categories/${id}/products`,
|
||||||
|
SEARCH: 'categories/search',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search categories
|
||||||
|
searchCategories: async (query, params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.SEARCH, {
|
||||||
|
params: { q: query, ...params }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching categories:', 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;
|
||||||
104
src/services/ordersApi.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import api from './api';
|
||||||
|
|
||||||
|
// Orders API endpoints
|
||||||
|
const ENDPOINTS = {
|
||||||
|
ORDERS: 'orders',
|
||||||
|
ORDER_BY_ID: (id) => `orders/${id}`,
|
||||||
|
SEARCH: 'orders/search',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Orders API service
|
||||||
|
export const ordersApi = {
|
||||||
|
// Get all orders
|
||||||
|
getAllOrders: async (params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.ORDERS, { params });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching orders:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get order by ID
|
||||||
|
getOrderById: async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.ORDER_BY_ID(id));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching order ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create new order
|
||||||
|
createOrder: async (orderData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post(ENDPOINTS.ORDERS, orderData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating order:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update order
|
||||||
|
updateOrder: async (id, orderData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(ENDPOINTS.ORDER_BY_ID(id), orderData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating order ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete order
|
||||||
|
deleteOrder: async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await api.delete(ENDPOINTS.ORDER_BY_ID(id));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting order ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search orders
|
||||||
|
searchOrders: async (query, params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.SEARCH, {
|
||||||
|
params: { q: query, ...params }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching orders:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bulk operations
|
||||||
|
bulkUpdateOrders: async (orders) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(`${ENDPOINTS.ORDERS}/bulk`, { orders });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error bulk updating orders:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bulkDeleteOrders: async (orderIds) => {
|
||||||
|
try {
|
||||||
|
const response = await api.delete(`${ENDPOINTS.ORDERS}/bulk`, {
|
||||||
|
data: { ids: orderIds }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error bulk deleting orders:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ordersApi;
|
||||||
@ -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
|
||||||
|
|||||||
228
src/services/usersApi.js
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import api from './api';
|
||||||
|
|
||||||
|
// Users API endpoints
|
||||||
|
const ENDPOINTS = {
|
||||||
|
USERS: 'users',
|
||||||
|
USER_BY_ID: (id) => `users/${id}`,
|
||||||
|
USER_PROFILE: 'users/profile',
|
||||||
|
USER_PERMISSIONS: (id) => `users/${id}/permissions`,
|
||||||
|
USER_ROLES: (id) => `users/${id}/roles`,
|
||||||
|
SEARCH: 'users/search',
|
||||||
|
BULK_OPERATIONS: 'users/bulk',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Users API service
|
||||||
|
export const usersApi = {
|
||||||
|
// Get all users
|
||||||
|
getAllUsers: async (params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.USERS, { params });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get user by ID
|
||||||
|
getUserById: async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.USER_BY_ID(id));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching user ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create new user
|
||||||
|
createUser: async (userData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post(ENDPOINTS.USERS, userData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update user
|
||||||
|
updateUser: async (id, userData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(ENDPOINTS.USER_BY_ID(id), userData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating user ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
deleteUser: async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await api.delete(ENDPOINTS.USER_BY_ID(id));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting user ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search users
|
||||||
|
searchUsers: async (query, params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.SEARCH, {
|
||||||
|
params: { q: query, ...params }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get user profile
|
||||||
|
getUserProfile: async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.USER_PROFILE);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user profile:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update user profile
|
||||||
|
updateUserProfile: async (profileData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(ENDPOINTS.USER_PROFILE, profileData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating user profile:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get user permissions
|
||||||
|
getUserPermissions: async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.USER_PERMISSIONS(id));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching user permissions ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update user permissions
|
||||||
|
updateUserPermissions: async (id, permissions) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(ENDPOINTS.USER_PERMISSIONS(id), { permissions });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating user permissions ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get user roles
|
||||||
|
getUserRoles: async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(ENDPOINTS.USER_ROLES(id));
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching user roles ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update user roles
|
||||||
|
updateUserRoles: async (id, roles) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(ENDPOINTS.USER_ROLES(id), { roles });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating user roles ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Change user password
|
||||||
|
changeUserPassword: async (id, passwordData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(`${ENDPOINTS.USER_BY_ID(id)}/password`, passwordData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error changing user password ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Activate/Deactivate user
|
||||||
|
toggleUserStatus: async (id, status) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(`${ENDPOINTS.USER_BY_ID(id)}/status`, { status });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error toggling user status ${id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bulk operations
|
||||||
|
bulkUpdateUsers: async (users) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put(ENDPOINTS.BULK_OPERATIONS, { users });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error bulk updating users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bulkDeleteUsers: async (userIds) => {
|
||||||
|
try {
|
||||||
|
const response = await api.delete(ENDPOINTS.BULK_OPERATIONS, {
|
||||||
|
data: { ids: userIds }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error bulk deleting users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Export users
|
||||||
|
exportUsers: async (params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`${ENDPOINTS.USERS}/export`, {
|
||||||
|
params,
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Import users
|
||||||
|
importUsers: async (fileData) => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', fileData);
|
||||||
|
|
||||||
|
const response = await api.post(`${ENDPOINTS.USERS}/import`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error importing users:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default usersApi;
|
||||||
@ -709,3 +709,10 @@ button {
|
|||||||
border-color: $dark;
|
border-color: $dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center-vertical {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 1%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|||||||
@ -26,3 +26,8 @@
|
|||||||
@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;
|
||||||
|
}
|
||||||
@ -17,7 +17,8 @@
|
|||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
border-color: $primary;
|
border-color: $primary;
|
||||||
}
|
}
|
||||||
.disabled>.page-link, .page-link.disabled {
|
.disabled > .page-link,
|
||||||
|
.page-link.disabled {
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
border-color: $border-color;
|
border-color: $border-color;
|
||||||
@ -169,3 +170,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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: $secondary;
|
color: $primary;
|
||||||
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: 24px 24px 0;
|
padding: 18px 18px 0;
|
||||||
}
|
}
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
padding: 24px;
|
padding: 18px;
|
||||||
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: 24px 24px 0;
|
padding: 18px 18px 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,3 +674,17 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table .ant-table-tbody > tr > td {
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
|||||||
@ -112,7 +112,7 @@
|
|||||||
}
|
}
|
||||||
h3{
|
h3{
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
color: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
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: $secondary
|
color: $primary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.image-upload-new{
|
&.image-upload-new{
|
||||||
@ -652,7 +652,7 @@ th,td {
|
|||||||
}
|
}
|
||||||
label{
|
label{
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: $secondary;
|
color: $primary;
|
||||||
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: #FE9F43
|
color: $primary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
@ -66,21 +66,21 @@
|
|||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
background: rgba(254, 159, 67, 0.08);
|
background: rgba(254, 159, 67, 0.08);
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
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: #FE9F43;
|
color: $primary;
|
||||||
}
|
}
|
||||||
svg{
|
svg{
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.active{
|
&.active{
|
||||||
background: rgba(254, 159, 67, 0.08);
|
background: rgba(254, 159, 67, 0.08);
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
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: #FE9F43;
|
color: $primary;
|
||||||
}
|
}
|
||||||
.menu-arrow{
|
.menu-arrow{
|
||||||
background: #FFEDDC;
|
background: #FFEDDC;
|
||||||
&::before{
|
&::before{
|
||||||
border-color: #FE9F43;
|
border-color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@
|
|||||||
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
filter: invert(72%) sepia(76%) saturate(1430%) hue-rotate(327deg) brightness(103%) contrast(101%);
|
||||||
}
|
}
|
||||||
span{
|
span{
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,8 +132,8 @@
|
|||||||
&.active{
|
&.active{
|
||||||
color: $secondary;
|
color: $secondary;
|
||||||
&:after{
|
&:after{
|
||||||
background: #FE9F43;
|
background: $primary;
|
||||||
border: 2px solid #FDB;
|
border: 2px solid $secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&::after{
|
&::after{
|
||||||
@ -148,8 +148,8 @@
|
|||||||
&:hover{
|
&:hover{
|
||||||
color:$primary;
|
color:$primary;
|
||||||
&:after{
|
&:after{
|
||||||
background: #FE9F43;
|
background: $primary;
|
||||||
border: 2px solid #FDB;
|
border: 2px solid $secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,15 +188,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:after{
|
&:after{
|
||||||
background: #FE9F43;
|
background: $primary;
|
||||||
border: 2px solid #FDB;
|
border: 2px solid $secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
color:$primary;
|
color:$primary;
|
||||||
&:after{
|
&:after{
|
||||||
background: #FE9F43;
|
background: $primary;
|
||||||
border: 2px solid #FDB;
|
border: 2px solid $secondary;
|
||||||
}
|
}
|
||||||
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: #FE9F43;
|
color: $primary;
|
||||||
span {
|
span {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
@ -240,25 +240,25 @@
|
|||||||
li {
|
li {
|
||||||
&.active{
|
&.active{
|
||||||
a{
|
a{
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
color: #FE9F43
|
color: $primary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.submenu > {
|
.submenu > {
|
||||||
a {
|
a {
|
||||||
&.active{
|
&.active{
|
||||||
background: rgba(254, 159, 67, 0.08);
|
background: $secondary;
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
span{
|
span{
|
||||||
color: #FE9F43;
|
color: $primary;
|
||||||
}
|
}
|
||||||
.menu-arrow{
|
.menu-arrow{
|
||||||
background: #FFEDDC;
|
// background: #FFEDDC;
|
||||||
&::before{
|
&::before{
|
||||||
border-color: #FE9F43;
|
border-color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,8 +321,8 @@
|
|||||||
&:hover{
|
&:hover{
|
||||||
color:$primary;
|
color:$primary;
|
||||||
&:after{
|
&:after{
|
||||||
background: #FE9F43;
|
background: $primary;
|
||||||
border: 2px solid #FDB;
|
border: 2px solid $secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
@include respond-below(custom575) {
|
@include respond-below(custom575) {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
}
|
}
|
||||||
a{
|
a{
|
||||||
color: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
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: $secondary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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: $secondary;
|
background: $primary;
|
||||||
@include rounded(8px);
|
@include rounded(8px);
|
||||||
@include respond-below(custom575) {
|
@include respond-below(custom575) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -16,9 +16,9 @@ $font-family-secondary: "Poppins", sans-serif;
|
|||||||
$font-awesome: "Fontawesome";
|
$font-awesome: "Fontawesome";
|
||||||
|
|
||||||
// Theme Colors Variables
|
// Theme Colors Variables
|
||||||
$primary: #FF9F43;
|
$primary: #36175e;
|
||||||
$primary-hover: darken($primary, 10%);
|
$primary-hover: darken($primary, 10%);
|
||||||
$secondary: #092C4C;
|
$secondary: #f1eaf9;
|
||||||
$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: #f8f9fa;
|
$light: #FAFBFE;
|
||||||
$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: #FFF5EC;
|
$primary-100: #ede7f3;
|
||||||
$primary-200: #FFECD9;
|
$primary-200: #d3c2e3;
|
||||||
$primary-300: #FFE2C7;
|
$primary-300: #b99cd3;
|
||||||
$primary-400: #FFD9B4;
|
$primary-400: #9f76c3;
|
||||||
$primary-500: #FFCFA1;
|
$primary-500: #8551b3;
|
||||||
$primary-600: #FFC58E;
|
$primary-600: #6e3f98;
|
||||||
$primary-700: #FFBC7B;
|
$primary-700: #57317a;
|
||||||
$primary-800: #FFB269;
|
$primary-800: #40225c;
|
||||||
$primary-900: #FFA956;
|
$primary-900: #2a153d;
|
||||||
|
|
||||||
// Secondary
|
// Secondary
|
||||||
$secondary-100: #E6EAED;
|
$secondary-100: #fdfbff;
|
||||||
$secondary-200: #CED5DB;
|
$secondary-200: #f8f4fc;
|
||||||
$secondary-300: #B5C0C9;
|
$secondary-300: #f1eaf9;
|
||||||
$secondary-400: #9DABB7;
|
$secondary-400: #e0d3f1;
|
||||||
$secondary-500: #8496A6;
|
$secondary-500: #c8b0e6;
|
||||||
$secondary-600: #6B8094;
|
$secondary-600: #af8ddb;
|
||||||
$secondary-700: #536B82;
|
$secondary-700: #9369cc;
|
||||||
$secondary-800: #3A5670;
|
$secondary-800: #7547aa;
|
||||||
$secondary-900: #22415E;
|
$secondary-900: #563180;
|
||||||
|
|
||||||
// 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;
|
||||||
|
|||||||
9
src/utils/currency.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const formatRupiah = (angka) => {
|
||||||
|
return new Intl.NumberFormat("id-ID", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "IDR",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
}).format(angka);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { formatRupiah };
|
||||||
13
src/utils/date.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
export { formatDate };
|
||||||