add superadmin authentication
This commit is contained in:
parent
a40a1994e5
commit
0656cda2ec
@ -1,19 +1,16 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import Scrollbars from "react-custom-scrollbars-2";
|
||||
// import { useSelector } from "react-redux";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { SidebarData } from "../../core/json/siderbar_data";
|
||||
import HorizontalSidebar from "./horizontalSidebar";
|
||||
import CollapsedSidebar from "./collapsedSidebar";
|
||||
import HorizontalSidebar from "./horizontalSidebar";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const Sidebar = () => {
|
||||
// const SidebarData = useSelector((state) => state.sidebar_data);
|
||||
// console.log(sidebarData, "sidebar");
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
const Location = useLocation();
|
||||
|
||||
console.log("Location.pathname", Location.pathname);
|
||||
|
||||
const [subOpen, setSubopen] = useState("");
|
||||
const [subsidebar, setSubsidebar] = useState("");
|
||||
|
||||
@ -46,6 +43,12 @@ const Sidebar = () => {
|
||||
|
||||
<ul>
|
||||
{mainLabel?.submenuItems?.map((title, i) => {
|
||||
if (title?.role) {
|
||||
if (user?.role !== title?.role) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let link_array = [];
|
||||
title?.submenuItems?.map((link) => {
|
||||
link_array?.push(link?.link);
|
||||
|
||||
@ -54,7 +54,7 @@ export const all_routes = {
|
||||
floatinglabel: "/form-floating-labels",
|
||||
formvalidation: "/form-validation",
|
||||
select2: "/form-select2",
|
||||
|
||||
companylist: "/company-list",
|
||||
toasts: "/ui-toasts",
|
||||
video: "/ui-video",
|
||||
sweetalerts: "/ui-sweetalerts",
|
||||
@ -81,7 +81,7 @@ export const all_routes = {
|
||||
typicons: "/icon-typicon",
|
||||
flagicons: "/icon-flag",
|
||||
ribbon: "/ui-ribbon",
|
||||
|
||||
paymentmethodlist: "/payment-method-list",
|
||||
chat: "/chat",
|
||||
videocall: "/video-call",
|
||||
audiocall: "/audio-call",
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Outlet, Route, Routes } from "react-router-dom";
|
||||
import Header from "../InitialPage/Sidebar/Header";
|
||||
import Sidebar from "../InitialPage/Sidebar/Sidebar";
|
||||
import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import ThemeSettings from "../InitialPage/themeSettings";
|
||||
import ProtectedRoute from "../components/ProtectedRoute";
|
||||
import { pagesRoute, posRoutes, publicRoutes } from "./router.link";
|
||||
// import CollapsedSidebar from "../InitialPage/Sidebar/collapsedSidebar";
|
||||
import GuestRoute from "../components/GuestRoute";
|
||||
import Loader from "../feature-module/loader/loader";
|
||||
// import HorizontalSidebar from "../InitialPage/Sidebar/horizontalSidebar";
|
||||
//import LoadingSpinner from "../InitialPage/Sidebar/LoadingSpinner";
|
||||
|
||||
const AllRoutes = () => {
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
// const layoutStyles = useSelector((state) => state.layoutstyledata);
|
||||
const HeaderLayout = () => (
|
||||
@ -57,6 +57,7 @@ const AllRoutes = () => {
|
||||
<Route path={route.path} element={route.element} key={id} />
|
||||
))}
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path={"/"}
|
||||
element={
|
||||
@ -65,12 +66,20 @@ const AllRoutes = () => {
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
{publicRoutes.map((route, id) => (
|
||||
<Route path={route.path} element={route.element} key={id} />
|
||||
))}
|
||||
{publicRoutes.map((route, id) => {
|
||||
if (route?.role && route?.role !== user?.role) return null;
|
||||
return <Route path={route.path} element={route.element} key={id} />;
|
||||
})}
|
||||
</Route>
|
||||
|
||||
<Route path={"/"} element={<Authpages />}>
|
||||
<Route
|
||||
path={"/"}
|
||||
element={
|
||||
<GuestRoute>
|
||||
<Authpages />
|
||||
</GuestRoute>
|
||||
}
|
||||
>
|
||||
{pagesRoute.map((route, id) => (
|
||||
<Route path={route.path} element={route.element} key={id} />
|
||||
))}
|
||||
|
||||
@ -204,6 +204,9 @@ import AddWeddingGuest from "../feature-module/inventory/addWeddingGuest";
|
||||
import ProductList2 from "../feature-module/inventory/productlist2";
|
||||
import ProductList3 from "../feature-module/inventory/productlist3";
|
||||
import { all_routes } from "./all_routes";
|
||||
import PaymentMethodList from "../feature-module/FinanceAccounts/paymentmethodlist";
|
||||
import CompanyList from "../feature-module/superadmin/companylist";
|
||||
|
||||
export const publicRoutes = [
|
||||
{
|
||||
id: 1,
|
||||
@ -1488,7 +1491,23 @@ export const publicRoutes = [
|
||||
element: <Navigate to="/" />,
|
||||
route: Route,
|
||||
},
|
||||
{
|
||||
id: 120,
|
||||
path: routes.paymentmethodlist,
|
||||
name: "paymendMethodList",
|
||||
element: <PaymentMethodList /> ,
|
||||
route: Route,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
path: routes.companylist,
|
||||
name: "companies",
|
||||
element: <CompanyList />,
|
||||
route: Route,
|
||||
role: 'superadmin'
|
||||
}
|
||||
];
|
||||
|
||||
export const posRoutes = [
|
||||
{
|
||||
id: 1,
|
||||
|
||||
15
src/components/GuestRoute.jsx
Normal file
15
src/components/GuestRoute.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const GuestRoute = ({ children }) => {
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const isAuthenticated = authState?.isAuthenticated || authState?.token;
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default GuestRoute;
|
||||
@ -1,17 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const ProtectedRoute = ({ children }) => {
|
||||
// Check if user is authenticated using Redux state
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const isAuthenticated = authState?.isAuthenticated || authState?.token;
|
||||
|
||||
// Fallback to localStorage check
|
||||
const localStorageAuth = localStorage.getItem('authToken') || localStorage.getItem('user');
|
||||
const isUserAuthenticated = isAuthenticated || localStorageAuth;
|
||||
|
||||
if (!isUserAuthenticated) {
|
||||
if (!isAuthenticated) {
|
||||
// Redirect to login page if not authenticated
|
||||
return <Navigate to="/signin" replace />;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
132
src/core/redux/actions/organizationActions.js
Normal file
132
src/core/redux/actions/organizationActions.js
Normal file
@ -0,0 +1,132 @@
|
||||
import { organizationsApi } from '../../../services/organizationsApi';
|
||||
|
||||
// Action Types
|
||||
export const ORGANIZATION_ACTIONS = {
|
||||
FETCH_ORGANIZATIONS_REQUEST: 'FETCH_ORGANIZATIONS_REQUEST',
|
||||
FETCH_ORGANIZATIONS_SUCCESS: 'FETCH_ORGANIZATIONS_SUCCESS',
|
||||
FETCH_ORGANIZATIONS_FAILURE: 'FETCH_ORGANIZATIONS_FAILURE',
|
||||
|
||||
FETCH_ORGANIZATION_REQUEST: 'FETCH_ORGANIZATION_REQUEST',
|
||||
FETCH_ORGANIZATION_SUCCESS: 'FETCH_ORGANIZATION_SUCCESS',
|
||||
FETCH_ORGANIZATION_FAILURE: 'FETCH_ORGANIZATION_FAILURE',
|
||||
|
||||
CREATE_ORGANIZATION_REQUEST: 'CREATE_ORGANIZATION_REQUEST',
|
||||
CREATE_ORGANIZATION_SUCCESS: 'CREATE_ORGANIZATION_SUCCESS',
|
||||
CREATE_ORGANIZATION_FAILURE: 'CREATE_ORGANIZATION_FAILURE',
|
||||
|
||||
UPDATE_ORGANIZATION_REQUEST: 'UPDATE_ORGANIZATION_REQUEST',
|
||||
UPDATE_ORGANIZATION_SUCCESS: 'UPDATE_ORGANIZATION_SUCCESS',
|
||||
UPDATE_ORGANIZATION_FAILURE: 'UPDATE_ORGANIZATION_FAILURE',
|
||||
|
||||
DELETE_ORGANIZATION_REQUEST: 'DELETE_ORGANIZATION_REQUEST',
|
||||
DELETE_ORGANIZATION_SUCCESS: 'DELETE_ORGANIZATION_SUCCESS',
|
||||
DELETE_ORGANIZATION_FAILURE: 'DELETE_ORGANIZATION_FAILURE',
|
||||
|
||||
CLEAR_ORGANIZATION_ERROR: 'CLEAR_ORGANIZATION_ERROR',
|
||||
CLEAR_CURRENT_ORGANIZATION: 'CLEAR_CURRENT_ORGANIZATION',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const fetchOrganizations = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.getAllOrganizations(params);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch organizations',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchOrganization = (id) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.getOrganizationById(id);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createOrganization = (organizationData) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.createOrganization(organizationData);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to create organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOrganization = (id, organizationData) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await organizationsApi.updateOrganization(id, organizationData);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to update organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteOrganization = (id) => async (dispatch) => {
|
||||
dispatch({ type: ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_REQUEST });
|
||||
|
||||
try {
|
||||
await organizationsApi.deleteOrganization(id);
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_SUCCESS,
|
||||
payload: id,
|
||||
});
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to delete organization',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const clearOrganizationError = () => ({
|
||||
type: ORGANIZATION_ACTIONS.CLEAR_ORGANIZATION_ERROR,
|
||||
});
|
||||
|
||||
export const clearCurrentOrganization = () => ({
|
||||
type: ORGANIZATION_ACTIONS.CLEAR_CURRENT_ORGANIZATION,
|
||||
});
|
||||
126
src/core/redux/actions/paymentMethodActions.js
Normal file
126
src/core/redux/actions/paymentMethodActions.js
Normal file
@ -0,0 +1,126 @@
|
||||
import { paymentMethodsApi } from '../../../services/paymentMethodsApi';
|
||||
|
||||
export const PAYMENT_METHOD_ACTIONS = {
|
||||
FETCH_PAYMENT_METHODS_REQUEST: 'FETCH_PAYMENT_METHODS_REQUEST',
|
||||
FETCH_PAYMENT_METHODS_SUCCESS: 'FETCH_PAYMENT_METHODS_SUCCESS',
|
||||
FETCH_PAYMENT_METHODS_FAILURE: 'FETCH_PAYMENT_METHODS_FAILURE',
|
||||
|
||||
FETCH_PAYMENT_METHOD_REQUEST: 'FETCH_PAYMENT_METHOD_REQUEST',
|
||||
FETCH_PAYMENT_METHOD_SUCCESS: 'FETCH_PAYMENT_METHOD_SUCCESS',
|
||||
FETCH_PAYMENT_METHOD_FAILURE: 'FETCH_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
CREATE_PAYMENT_METHOD_REQUEST: 'CREATE_PAYMENT_METHOD_REQUEST',
|
||||
CREATE_PAYMENT_METHOD_SUCCESS: 'CREATE_PAYMENT_METHOD_SUCCESS',
|
||||
CREATE_PAYMENT_METHOD_FAILURE: 'CREATE_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
UPDATE_PAYMENT_METHOD_REQUEST: 'UPDATE_PAYMENT_METHOD_REQUEST',
|
||||
UPDATE_PAYMENT_METHOD_SUCCESS: 'UPDATE_PAYMENT_METHOD_SUCCESS',
|
||||
UPDATE_PAYMENT_METHOD_FAILURE: 'UPDATE_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
DELETE_PAYMENT_METHOD_REQUEST: 'DELETE_PAYMENT_METHOD_REQUEST',
|
||||
DELETE_PAYMENT_METHOD_SUCCESS: 'DELETE_PAYMENT_METHOD_SUCCESS',
|
||||
DELETE_PAYMENT_METHOD_FAILURE: 'DELETE_PAYMENT_METHOD_FAILURE',
|
||||
|
||||
CLEAR_PAYMENT_METHOD_ERROR: 'CLEAR_PAYMENT_METHOD_ERROR',
|
||||
CLEAR_CURRENT_PAYMENT_METHOD: 'CLEAR_CURRENT_PAYMENT_METHOD',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const fetchPaymentMethods = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.getAll(params);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPaymentMethod = (id) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.getById(id);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createPaymentMethod = (formData) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.create(formData);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePaymentMethod = (id, formData) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
const data = await paymentMethodsApi.update(id, formData);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePaymentMethod = (id) => async (dispatch) => {
|
||||
dispatch({ type: PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_REQUEST });
|
||||
try {
|
||||
await paymentMethodsApi.remove(id);
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_SUCCESS,
|
||||
payload: id,
|
||||
});
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_FAILURE,
|
||||
payload: error.response?.data?.message || error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const clearPaymentMethodError = () => ({
|
||||
type: PAYMENT_METHOD_ACTIONS.CLEAR_PAYMENT_METHOD_ERROR,
|
||||
});
|
||||
|
||||
export const clearCurrentPaymentMethod = () => ({
|
||||
type: PAYMENT_METHOD_ACTIONS.CLEAR_CURRENT_PAYMENT_METHOD,
|
||||
});
|
||||
@ -4,6 +4,8 @@ import productReducer from './reducers/productReducer';
|
||||
import authReducer from './reducers/authReducer';
|
||||
import categoryReducer from './reducers/categoryReducer';
|
||||
import orderReducer from './reducers/orderReducer';
|
||||
import paymentMethodReducer from './reducers/paymentMethodReducer';
|
||||
import organizationReducer from './reducers/organizationReducer';
|
||||
|
||||
// Legacy reducer for existing functionality
|
||||
const legacyReducer = (state = initialState, action) => {
|
||||
@ -79,6 +81,8 @@ const rootReducer = combineReducers({
|
||||
auth: authReducer,
|
||||
categories: categoryReducer,
|
||||
orders: orderReducer,
|
||||
paymentMethods: paymentMethodReducer,
|
||||
organizations: organizationReducer
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
||||
212
src/core/redux/reducers/organizationReducer.js
Normal file
212
src/core/redux/reducers/organizationReducer.js
Normal file
@ -0,0 +1,212 @@
|
||||
import { ORGANIZATION_ACTIONS } from '../actions/organizationActions';
|
||||
|
||||
const initialState = {
|
||||
// Organizations list
|
||||
organizations: [],
|
||||
totalOrganizations: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
|
||||
// Current organization (for edit/view)
|
||||
currentOrganization: null,
|
||||
|
||||
// Search results
|
||||
searchResults: [],
|
||||
searchQuery: '',
|
||||
|
||||
// Loading states
|
||||
loading: false,
|
||||
organizationLoading: false,
|
||||
searchLoading: false,
|
||||
|
||||
// Error states
|
||||
error: null,
|
||||
organizationError: null,
|
||||
searchError: null,
|
||||
|
||||
// Operation states
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
};
|
||||
|
||||
const organizationReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
// Fetch Organizations
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_SUCCESS: {
|
||||
const { organizations, total_count, page, total_pages, limit } = action.payload.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
organizations: organizations,
|
||||
totalOrganizations: total_count || organizations.length,
|
||||
currentPage: page || 1,
|
||||
totalPages: total_pages || 1,
|
||||
pageSize: limit || 10,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATIONS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Fetch Single Organization
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
organizationLoading: true,
|
||||
organizationError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
organizationLoading: false,
|
||||
currentOrganization: action.payload.data,
|
||||
organizationError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.FETCH_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
organizationLoading: false,
|
||||
organizationError: action.payload,
|
||||
};
|
||||
|
||||
// Create Organization
|
||||
case ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
creating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
organizations: [action.payload.data, ...state.organizations],
|
||||
totalOrganizations: state.totalOrganizations + 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.CREATE_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Update Organization
|
||||
case ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
updating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
organizations: state.organizations.map(org =>
|
||||
org.id === action.payload.data.id ? action.payload.data : org
|
||||
),
|
||||
currentOrganization: action.payload.data,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.UPDATE_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Delete Organization
|
||||
case ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
deleting: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
organizations: state.organizations.filter(org => org.id !== action.payload),
|
||||
totalOrganizations: state.totalOrganizations - 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.DELETE_ORGANIZATION_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Search Organizations
|
||||
case ORGANIZATION_ACTIONS.SEARCH_ORGANIZATIONS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: true,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.SEARCH_ORGANIZATIONS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchResults: action.payload.data || action.payload,
|
||||
searchQuery: action.payload.query || '',
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.SEARCH_ORGANIZATIONS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchError: action.payload,
|
||||
};
|
||||
|
||||
// Clear States
|
||||
case ORGANIZATION_ACTIONS.CLEAR_ORGANIZATION_ERROR:
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
organizationError: null,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case ORGANIZATION_ACTIONS.CLEAR_CURRENT_ORGANIZATION:
|
||||
return {
|
||||
...state,
|
||||
currentOrganization: null,
|
||||
organizationError: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default organizationReducer;
|
||||
106
src/core/redux/reducers/paymentMethodReducer.js
Normal file
106
src/core/redux/reducers/paymentMethodReducer.js
Normal file
@ -0,0 +1,106 @@
|
||||
import { PAYMENT_METHOD_ACTIONS } from '../actions/paymentMethodActions';
|
||||
|
||||
const initialState = {
|
||||
paymentMethods: [],
|
||||
totalPaymentMethods: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
pageSize: 10,
|
||||
|
||||
currentPaymentMethod: null,
|
||||
|
||||
loading: false,
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
|
||||
error: null,
|
||||
detailError: null,
|
||||
};
|
||||
|
||||
const paymentMethodReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_REQUEST:
|
||||
return { ...state, loading: true, error: null };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_SUCCESS: {
|
||||
const { payment_methods, total_count, page, total_pages, limit } = action.payload.data;
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
paymentMethods: payment_methods,
|
||||
totalPaymentMethods: total_count || payment_methods.length,
|
||||
currentPage: page || 1,
|
||||
totalPages: total_pages || 1,
|
||||
pageSize: limit || 10,
|
||||
};
|
||||
}
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHODS_FAILURE:
|
||||
return { ...state, loading: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, detailLoading: true, detailError: null };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_SUCCESS:
|
||||
return { ...state, detailLoading: false, currentPaymentMethod: action.payload.data };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.FETCH_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, detailLoading: false, detailError: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, creating: true };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
paymentMethods: [action.payload.data, ...state.paymentMethods],
|
||||
totalPaymentMethods: state.totalPaymentMethods + 1,
|
||||
};
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CREATE_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, creating: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, updating: true };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
paymentMethods: state.paymentMethods.map((item) =>
|
||||
item.id === action.payload.data.id ? action.payload.data : item
|
||||
),
|
||||
currentPaymentMethod: action.payload.data,
|
||||
};
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.UPDATE_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, updating: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_REQUEST:
|
||||
return { ...state, deleting: true };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
paymentMethods: state.paymentMethods.filter((item) => item.id !== action.payload),
|
||||
totalPaymentMethods: state.totalPaymentMethods - 1,
|
||||
};
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.DELETE_PAYMENT_METHOD_FAILURE:
|
||||
return { ...state, deleting: false, error: action.payload };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CLEAR_PAYMENT_METHOD_ERROR:
|
||||
return { ...state, error: null, detailError: null };
|
||||
|
||||
case PAYMENT_METHOD_ACTIONS.CLEAR_CURRENT_PAYMENT_METHOD:
|
||||
return { ...state, currentPaymentMethod: null };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default paymentMethodReducer;
|
||||
387
src/feature-module/FinanceAccounts/paymentmethodlist.jsx
Normal file
387
src/feature-module/FinanceAccounts/paymentmethodlist.jsx
Normal file
@ -0,0 +1,387 @@
|
||||
import { Select, Tag } from "antd";
|
||||
import {
|
||||
ChevronUp,
|
||||
PlusCircle,
|
||||
RotateCcw,
|
||||
Trash2,
|
||||
} from "feather-icons-react/build/IconComponents";
|
||||
import { useEffect, useState } from "react";
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import CustomPagination from "../../components/CustomPagination";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import AddCategoryList from "../../core/modals/inventory/addcategorylist";
|
||||
import EditCategoryList from "../../core/modals/inventory/editcategorylist";
|
||||
import Table from "../../core/pagination/datatable";
|
||||
import { setToogleHeader } from "../../core/redux/action";
|
||||
import { deletePaymentMethod, fetchPaymentMethod, fetchPaymentMethods } from "../../core/redux/actions/paymentMethodActions";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
const PaymentMethodList = () => {
|
||||
const {
|
||||
paymentMethods: apiPaymentMethods,
|
||||
loading,
|
||||
error,
|
||||
totalPaymentMethods,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage,
|
||||
} = useSelector((state) => state.paymentMethods);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const dataSource = apiPaymentMethods?.length > 0 ? apiPaymentMethods : [];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadPaymentMethods = async () => {
|
||||
try {
|
||||
const searchParams = {
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
search: debouncedSearchTerm || "",
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== "")
|
||||
);
|
||||
|
||||
await dispatch(fetchPaymentMethods(cleanParams));
|
||||
} catch (error) {
|
||||
console.error("Failed to load categories", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadPaymentMethods();
|
||||
}, [dispatch, currentPage, pageSize, debouncedSearchTerm]);
|
||||
|
||||
// Debounce search term
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
}, 500); // 500ms delay
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = (newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1); // Reset to first page when changing page size
|
||||
};
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const value = e.target.value;
|
||||
setSearchTerm(value);
|
||||
// Reset to first page when searching
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Calculate pagination info
|
||||
const totalRecords = totalPaymentMethods || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
const handleDeletePaymentMethod = async (paymentMethodId) => {
|
||||
try {
|
||||
await dispatch(deletePaymentMethod(paymentMethodId));
|
||||
// Show success message
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Payment Method has been deleted successfully.",
|
||||
icon: "success",
|
||||
className: "btn btn-success",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete payment method:", error);
|
||||
MySwal.fire({
|
||||
title: "Error!",
|
||||
text: "Failed to delete payment method. Please try again.",
|
||||
icon: "error",
|
||||
className: "btn btn-danger",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-danger",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderTooltip = (props) => (
|
||||
<Tooltip id="pdf-tooltip" {...props}>
|
||||
Pdf
|
||||
</Tooltip>
|
||||
);
|
||||
const renderExcelTooltip = (props) => (
|
||||
<Tooltip id="excel-tooltip" {...props}>
|
||||
Excel
|
||||
</Tooltip>
|
||||
);
|
||||
const renderPrinterTooltip = (props) => (
|
||||
<Tooltip id="printer-tooltip" {...props}>
|
||||
Printer
|
||||
</Tooltip>
|
||||
);
|
||||
const renderRefreshTooltip = (props) => (
|
||||
<Tooltip id="refresh-tooltip" {...props}>
|
||||
Refresh
|
||||
</Tooltip>
|
||||
);
|
||||
const renderCollapseTooltip = (props) => (
|
||||
<Tooltip id="refresh-tooltip" {...props}>
|
||||
Collapse
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const dateOptions = [
|
||||
{ label: "Sort By: Last 7 Days", value: "last7days" },
|
||||
{ label: "Sort By: Last Month", value: "lastmonth" },
|
||||
{ label: "Sort By: Ascending", value: "ascending" },
|
||||
{ label: "Sort By: Descending", value: "descending" },
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Payment Method",
|
||||
dataIndex: "paymentmethod",
|
||||
render: (_, record) => {
|
||||
return <span>{record.name}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.name.length - b.name.length,
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
dataIndex: "type",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.type}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.type.length - b.type.length,
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
render: (_, record) => {
|
||||
return <span>{formatDate(record.created_at)}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.created_at.length - b.created_at.length,
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
render: (_, record) => (
|
||||
<Tag color="#87d068">{record.is_active ? "Active" : "Inactive"}</Tag>
|
||||
),
|
||||
sorter: (a, b) => a.status.length - b.status.length,
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (_, record) => (
|
||||
<td className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
<Link
|
||||
className="me-2 p-2"
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-category"
|
||||
onClick={() => dispatch(fetchPaymentMethod(record.id))}
|
||||
>
|
||||
<i data-feather="edit" className="feather-edit"></i>
|
||||
</Link>
|
||||
<Link
|
||||
className="confirm-text p-2"
|
||||
to="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
MySwal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "You won't be able to revert this!",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#00ff00",
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
cancelButtonColor: "#ff0000",
|
||||
cancelButtonText: "Cancel",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
handleDeletePaymentMethod(record.id || record.key);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trash2 className="feather-trash-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
),
|
||||
},
|
||||
];
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
<div className="page-header">
|
||||
<div className="add-item d-flex">
|
||||
<div className="page-title">
|
||||
<h4>Payment Method</h4>
|
||||
<h6>Manage your payment methods</h6>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="table-top-head">
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderTooltip}>
|
||||
<Link>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/pdf.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
|
||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/excel.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
|
||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<i data-feather="printer" className="feather-printer" />
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
|
||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<RotateCcw />
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
|
||||
<Link
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
id="collapse-header"
|
||||
className={data ? "active" : ""}
|
||||
onClick={() => {
|
||||
dispatch(setToogleHeader(!data));
|
||||
}}
|
||||
>
|
||||
<ChevronUp />
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="page-btn">
|
||||
<Link
|
||||
to="#"
|
||||
className="btn btn-added"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-category"
|
||||
>
|
||||
<PlusCircle className="me-2" />
|
||||
Add New Category
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* /product list */}
|
||||
<div className="card table-list-card">
|
||||
<div className="card-body">
|
||||
<div className="table-top">
|
||||
<div className="search-set">
|
||||
<div className="search-input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
className="form-control form-control-sm formsearch"
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Link to className="btn btn-searchset">
|
||||
<i data-feather="search" className="feather-search" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
style={{ height: 36 }}
|
||||
defaultValue={dateOptions[0]?.value}
|
||||
options={dateOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive">
|
||||
{loading ? (
|
||||
<div className="text-center p-4">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p className="mt-2">Loading categories...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<strong>Error:</strong> {error}
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger ms-2"
|
||||
onClick={() => dispatch(fetchPaymentMethods())}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Table columns={columns} dataSource={dataSource} />
|
||||
|
||||
<CustomPagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalRecords}
|
||||
totalPages={actualTotalPages}
|
||||
loading={loading}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showInfo={true}
|
||||
showPageSizeSelector={true}
|
||||
compact={false}
|
||||
className="paymentmethod-list-pagination"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /category list */}
|
||||
</div>
|
||||
</div>
|
||||
<AddCategoryList />
|
||||
<EditCategoryList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentMethodList;
|
||||
@ -11,7 +11,7 @@ import {
|
||||
Plus,
|
||||
PlusCircle,
|
||||
Trash2,
|
||||
X
|
||||
X,
|
||||
} from "feather-icons-react/build/IconComponents";
|
||||
import { useEffect, useState } from "react";
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
@ -180,12 +180,19 @@ const AddProduct = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Prepare the data for submission
|
||||
const isAllEmpty = variants.every(
|
||||
(item) =>
|
||||
item.name === "" && item.price_modifier === 0 && item.cost === 0
|
||||
);
|
||||
|
||||
const productData = {
|
||||
...formData,
|
||||
variants
|
||||
};
|
||||
|
||||
if (!isAllEmpty) {
|
||||
productData.variants = variants;
|
||||
}
|
||||
|
||||
// Remove empty values
|
||||
const cleanData = Object.fromEntries(
|
||||
Object.entries(productData).filter(([, value]) => {
|
||||
@ -222,7 +229,7 @@ const AddProduct = () => {
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Error!",
|
||||
text: error.message || "Failed to create product. Please try again.",
|
||||
text: error?.response?.data?.errors[0].cause || "Failed to create product. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -254,7 +261,7 @@ const AddProduct = () => {
|
||||
|
||||
const handleChangeVariant = (index, field, value) => {
|
||||
const newVariants = [...variants];
|
||||
if (['price_modifier', 'cost'].includes(field)) value = Number(value);
|
||||
if (["price_modifier", "cost"].includes(field)) value = Number(value);
|
||||
newVariants[index][field] = value;
|
||||
setVariants(newVariants);
|
||||
};
|
||||
@ -683,6 +690,7 @@ const AddProduct = () => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tab-content" id="pills-tabContent">
|
||||
<div
|
||||
className="tab-pane fade show active"
|
||||
@ -872,7 +880,10 @@ const AddProduct = () => {
|
||||
className="btn btn-primary mt-2"
|
||||
onClick={addVariant}
|
||||
>
|
||||
<Plus data-feather="plus" className="me-1 icon-small" />
|
||||
<Plus
|
||||
data-feather="plus"
|
||||
className="me-1 icon-small"
|
||||
/>
|
||||
Add Variant
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -188,12 +188,19 @@ const EditProduct = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Prepare the data for submission
|
||||
const isAllEmpty = variants.every(
|
||||
(item) =>
|
||||
item.name === "" && item.price_modifier === 0 && item.cost === 0
|
||||
);
|
||||
|
||||
const productData = {
|
||||
...formData,
|
||||
variants,
|
||||
};
|
||||
|
||||
if (!isAllEmpty) {
|
||||
productData.variants = variants;
|
||||
}
|
||||
|
||||
// Remove empty values
|
||||
const cleanData = Object.fromEntries(
|
||||
Object.entries(productData).filter(([, value]) => {
|
||||
|
||||
@ -12,27 +12,26 @@ const Signin = () => {
|
||||
const authState = useSelector((state) => state.auth);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: ''
|
||||
email: "",
|
||||
password: "",
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setError("");
|
||||
|
||||
try {
|
||||
await authApi.login(formData);
|
||||
navigate(route.dashboard);
|
||||
authApi.login(formData).then(() => navigate(route.dashboard));
|
||||
} catch (error) {
|
||||
setError(error.message || 'Login failed');
|
||||
setError(error.message || "Login failed");
|
||||
}
|
||||
};
|
||||
|
||||
@ -115,7 +114,7 @@ const Signin = () => {
|
||||
className="btn btn-login"
|
||||
disabled={authState.loading}
|
||||
>
|
||||
{authState.loading ? 'Signing In...' : 'Sign In'}
|
||||
{authState.loading ? "Signing In..." : "Sign In"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="signinform">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DatePicker, Space, Select as AntSelect } from "antd";
|
||||
import { Select as AntSelect, DatePicker, Space } from "antd";
|
||||
import {
|
||||
ChevronUp,
|
||||
PlusCircle,
|
||||
@ -17,7 +17,7 @@ import {
|
||||
fetchOrders,
|
||||
} from "../../core/redux/actions/orderActions";
|
||||
import { formatRupiah } from "../../utils/currency";
|
||||
import { formatDate } from "../../utils/date";
|
||||
import { formatDate, formatInputDate } from "../../utils/date";
|
||||
|
||||
const SalesList = () => {
|
||||
const {
|
||||
@ -35,25 +35,41 @@ const SalesList = () => {
|
||||
|
||||
const dataSource = apiOrders?.length > 0 ? apiOrders : [];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||
const [params, setParams] = useState({
|
||||
page: reduxCurrentPage || 1,
|
||||
limit: reduxPageSize || 10,
|
||||
status: null,
|
||||
date_from: null,
|
||||
date_to: null,
|
||||
});
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
const [orderStatus, setOrderStatus] = useState(null);
|
||||
|
||||
const [selectedOrder, setSelectedOrder] = useState(null);
|
||||
|
||||
const handleSetParams = (key, value) => {
|
||||
setParams({
|
||||
...params,
|
||||
[key]: value,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadOrders = async () => {
|
||||
try {
|
||||
const searchParams = {
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
const receivedParams = {
|
||||
page: params.page,
|
||||
limit: params.limit,
|
||||
search: debouncedSearchTerm || "",
|
||||
status: orderStatus,
|
||||
status: params.status,
|
||||
date_from: params.date_from,
|
||||
date_to: params.date_to,
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== "")
|
||||
Object.entries(receivedParams).filter(([, value]) => value !== "")
|
||||
);
|
||||
|
||||
await dispatch(fetchOrders(cleanParams));
|
||||
@ -63,7 +79,7 @@ const SalesList = () => {
|
||||
};
|
||||
|
||||
loadOrders();
|
||||
}, [dispatch, currentPage, pageSize, debouncedSearchTerm, orderStatus]);
|
||||
}, [dispatch, params, debouncedSearchTerm]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
@ -78,30 +94,23 @@ const SalesList = () => {
|
||||
const value = e.target.value;
|
||||
setSearchTerm(value);
|
||||
// Reset to first page when searching
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleFilterStatus = (e) => {
|
||||
const value = e.target.value;
|
||||
setOrderStatus(value);
|
||||
|
||||
setCurrentPage(1);
|
||||
handleSetParams("page", 1);
|
||||
};
|
||||
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
handleSetParams("page", page);
|
||||
};
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = (newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1); // Reset to first page when changing page size
|
||||
handleSetParams("limit", newPageSize);
|
||||
handleSetParams("page", 1);
|
||||
};
|
||||
|
||||
// Calculate pagination info
|
||||
const totalRecords = totalOrders || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / params.limit);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
// Clear error when component unmounts
|
||||
@ -141,13 +150,6 @@ const SalesList = () => {
|
||||
cancelled: "danger",
|
||||
};
|
||||
|
||||
const options = [
|
||||
{ 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 [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const handleDateChange = (date) => {
|
||||
setSelectedDate(date);
|
||||
@ -272,23 +274,48 @@ const SalesList = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Space warp>
|
||||
<Space warp align="center">
|
||||
<AntSelect
|
||||
style={{ height: 36, width: 120 }}
|
||||
placeholder={"Status"}
|
||||
options={paymentStatus}
|
||||
value={
|
||||
paymentStatus.find(
|
||||
(option) => option.value === orderStatus
|
||||
(option) => option.value === params.status
|
||||
) || null
|
||||
}
|
||||
onChange={handleFilterStatus}
|
||||
onChange={(selectedOption) =>
|
||||
handleSetParams("status", selectedOption)
|
||||
}
|
||||
/>
|
||||
|
||||
<AntSelect
|
||||
<DatePicker
|
||||
selected={params.date_from}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? handleSetParams("date_from", formatInputDate(date))
|
||||
: handleSetParams("date_from", "")
|
||||
}
|
||||
height={120}
|
||||
type="date"
|
||||
className="datetimepicker w-100"
|
||||
dateFormat="dd-MM-yyyy"
|
||||
placeholder="From Date"
|
||||
style={{ height: 36 }}
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
selected={params.date_to}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? handleSetParams("date_to", formatInputDate(date))
|
||||
: handleSetParams("date_to", "")
|
||||
}
|
||||
type="date"
|
||||
className="datetimepicker w-100"
|
||||
dateFormat="dd-MM-yyyy"
|
||||
placeholder="To Date"
|
||||
style={{ height: 36 }}
|
||||
defaultValue={options[0]?.value}
|
||||
options={options}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
@ -376,12 +403,13 @@ const SalesList = () => {
|
||||
className="dropdown-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#sales-details-new"
|
||||
onClick={() => setSelectedOrder(item)}
|
||||
>
|
||||
<i
|
||||
data-feather="eye"
|
||||
className="info-img"
|
||||
/>
|
||||
Sale Detail
|
||||
Sales Detail
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
@ -455,8 +483,8 @@ const SalesList = () => {
|
||||
</table>
|
||||
|
||||
<CustomPagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
currentPage={params.page}
|
||||
pageSize={params.limit}
|
||||
totalCount={totalRecords}
|
||||
totalPages={actualTotalPages}
|
||||
loading={loading}
|
||||
@ -687,178 +715,135 @@ const SalesList = () => {
|
||||
</div>
|
||||
</div>
|
||||
{/* /add popup */}
|
||||
|
||||
{/* details popup */}
|
||||
<div className="modal fade" id="sales-details-new">
|
||||
<div className="modal-dialog sales-details-modal">
|
||||
<div className="modal-content">
|
||||
<div className="page-wrapper details-blk">
|
||||
<div className="content p-4">
|
||||
<div className="d-flex justify-content-between align-items-center mb-4 modal-header">
|
||||
<h4 className="fw-bold">Sales Detail</h4>
|
||||
<button className="btn btn-dark" data-bs-dismiss="modal">
|
||||
<i className="fa fa-arrow-left me-2"></i>Back to Sales
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="row g-4 mb-4">
|
||||
<div className="col-md-4">
|
||||
<h6 className="fw-bold text-muted">Customer Info</h6>
|
||||
<p className="mb-0">Carl Evans</p>
|
||||
<small className="text-muted d-block">
|
||||
3103 Trainer Avenue Peoria, IL 61602
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Email: carlevans241@example.com
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Phone: +1 987 471 6589
|
||||
</small>
|
||||
{setSelectedOrder && (
|
||||
<div className="content p-4">
|
||||
<div className="d-flex justify-content-between align-items-center mb-4 modal-header">
|
||||
<h4 className="fw-bold">Sales Detail</h4>
|
||||
<button className="btn btn-dark" data-bs-dismiss="modal">
|
||||
<i className="fa fa-arrow-left me-2"></i>Back to Sales
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<h6 className="fw-bold text-muted">Company Info</h6>
|
||||
<p className="mb-0">DGT</p>
|
||||
<small className="text-muted d-block">
|
||||
2077 Chicago Avenue Orosi, CA 93647
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Email: admin@example.com
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Phone: +1 893 174 0385
|
||||
</small>
|
||||
|
||||
<div className="row g-4 mb-4">
|
||||
<div className="col-md-4">
|
||||
<h6 className="fw-bold text-muted">Customer Info</h6>
|
||||
<p className="mb-0">
|
||||
{selectedOrder?.metadata?.customer_name}
|
||||
</p>
|
||||
<small className="text-muted d-block">
|
||||
3103 Trainer Avenue Peoria, IL 61602
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Email: carlevans241@example.com
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Phone: +1 987 471 6589
|
||||
</small>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<h6 className="fw-bold text-muted">Company Info</h6>
|
||||
<p className="mb-0">DGT</p>
|
||||
<small className="text-muted d-block">
|
||||
2077 Chicago Avenue Orosi, CA 93647
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Email: admin@example.com
|
||||
</small>
|
||||
<small className="text-muted d-block">
|
||||
Phone: +1 893 174 0385
|
||||
</small>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<h6 className="fw-bold text-muted">Invoice Info</h6>
|
||||
<small className="d-block">
|
||||
Reference:{" "}
|
||||
<span className="text-warning fw-semibold">
|
||||
{selectedOrder?.order_number}
|
||||
</span>
|
||||
</small>
|
||||
<small className="d-block">
|
||||
Date: {formatDate(selectedOrder?.created_at)}
|
||||
</small>
|
||||
<small className="d-block">
|
||||
Status:{" "}
|
||||
<span
|
||||
className={`badge text-bg-${
|
||||
badgeColors[selectedOrder?.status]
|
||||
}`}
|
||||
>
|
||||
{selectedOrder?.status}
|
||||
</span>
|
||||
</small>
|
||||
<small className="d-block">
|
||||
Payment Status:{" "}
|
||||
<span className="badge bg-light-success text-success">
|
||||
Paid
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<h6 className="fw-bold text-muted">Invoice Info</h6>
|
||||
<small className="d-block">
|
||||
Reference:{" "}
|
||||
<span className="text-warning fw-semibold">
|
||||
#SL0101
|
||||
</span>
|
||||
</small>
|
||||
<small className="d-block">Date: Dec 24, 2024</small>
|
||||
<small className="d-block">
|
||||
Status:{" "}
|
||||
<span className="badge bg-success">Completed</span>
|
||||
</small>
|
||||
<small className="d-block">
|
||||
Payment Status:{" "}
|
||||
<span className="badge bg-light-success text-success">
|
||||
Paid
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 className="fw-bold mb-3">Order Summary</h5>
|
||||
<h5 className="fw-bold mb-3">Order Summary</h5>
|
||||
|
||||
<div className="table-responsive mb-4">
|
||||
<table className="table table-bordered">
|
||||
<thead className="thead-light text-dark">
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Purchase Price($)</th>
|
||||
<th>Discount($)</th>
|
||||
<th>Tax(%)</th>
|
||||
<th>Tax Amount($)</th>
|
||||
<th>Unit Cost($)</th>
|
||||
<th>Total Cost(%)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img
|
||||
src="/assets/img/products/stock-img-02.png"
|
||||
alt="Nike Jordan"
|
||||
width="30"
|
||||
className="me-2"
|
||||
/>{" "}
|
||||
Nike Jordan
|
||||
</td>
|
||||
<td>2000</td>
|
||||
<td>500</td>
|
||||
<td>0.00</td>
|
||||
<td>0.00</td>
|
||||
<td>0.00</td>
|
||||
<td>1500</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img
|
||||
src="/assets/img/products/stock-img-03.png"
|
||||
alt="Apple Watch"
|
||||
width="30"
|
||||
className="me-2"
|
||||
/>{" "}
|
||||
Apple Series 5 Watch
|
||||
</td>
|
||||
<td>3000</td>
|
||||
<td>400</td>
|
||||
<td>0.00</td>
|
||||
<td>0.00</td>
|
||||
<td>0.00</td>
|
||||
<td>1700</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img
|
||||
src="/assets/img/products/stock-img-05.png"
|
||||
alt="Lobar Handy"
|
||||
width="30"
|
||||
className="me-2"
|
||||
/>{" "}
|
||||
Lobar Handy
|
||||
</td>
|
||||
<td>2500</td>
|
||||
<td>500</td>
|
||||
<td>0.00</td>
|
||||
<td>0.00</td>
|
||||
<td>0.00</td>
|
||||
<td>2000</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="row justify-content-end">
|
||||
<div className="col-md-6">
|
||||
<div className="table-responsive mb-4">
|
||||
<table className="table table-bordered">
|
||||
<thead className="thead-light text-dark">
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Variant</th>
|
||||
<th>Status</th>
|
||||
<th>Quantity</th>
|
||||
<th>Unit Price</th>
|
||||
<th>Total Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Order Tax</td>
|
||||
<td className="text-end">$ 0.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Discount</td>
|
||||
<td className="text-end">$ 0.00</td>
|
||||
</tr>
|
||||
<tr className="fw-bold">
|
||||
<td>Grand Total</td>
|
||||
<td className="text-end">$ 5200.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Paid</td>
|
||||
<td className="text-end">$ 5200.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Due</td>
|
||||
<td className="text-end">$ 0.00</td>
|
||||
</tr>
|
||||
{selectedOrder?.order_items?.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>{item?.product_name}</td>
|
||||
<td>{item?.product_variant_name ?? "-"}</td>
|
||||
<td>{item?.status}</td>
|
||||
<td>{item?.quantity}</td>
|
||||
<td>{formatRupiah(item?.unit_price)}</td>
|
||||
<td>{formatRupiah(item?.total_price)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-end modal-footer">
|
||||
<button
|
||||
className="btn btn-outline-primary me-2"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-primary">Submit</button>
|
||||
<div className="row justify-content-end pb-3">
|
||||
<div className="col-md-6">
|
||||
<table className="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Order Tax</td>
|
||||
<td className="text-end">{formatRupiah(selectedOrder?.tax_amount)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Discount</td>
|
||||
<td className="text-end">{formatRupiah(selectedOrder?.discount_amount)}</td>
|
||||
</tr>
|
||||
<tr className="fw-bold">
|
||||
<td>Grand Total</td>
|
||||
<td className="text-end">{formatRupiah(selectedOrder?.total_amount)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sub Total</td>
|
||||
<td className="text-end">{formatRupiah(selectedOrder?.subtotal)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -894,7 +879,7 @@ const SalesList = () => {
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Qty</th>
|
||||
<th>Purchase Price($)</th>
|
||||
<th>Purchase Price</th>
|
||||
<th>Discount($)</th>
|
||||
<th>Tax(%)</th>
|
||||
<th>Tax Amount($)</th>
|
||||
|
||||
396
src/feature-module/superadmin/companylist.jsx
Normal file
396
src/feature-module/superadmin/companylist.jsx
Normal file
@ -0,0 +1,396 @@
|
||||
import { Select } from "antd";
|
||||
import {
|
||||
ChevronUp,
|
||||
PlusCircle,
|
||||
RotateCcw,
|
||||
Trash2,
|
||||
} from "feather-icons-react/build/IconComponents";
|
||||
import { useEffect, useState } from "react";
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import CustomPagination from "../../components/CustomPagination";
|
||||
import ImageWithBasePath from "../../core/img/imagewithbasebath";
|
||||
import AddCategoryList from "../../core/modals/inventory/addcategorylist";
|
||||
import EditCategoryList from "../../core/modals/inventory/editcategorylist";
|
||||
import Table from "../../core/pagination/datatable";
|
||||
import { setToogleHeader } from "../../core/redux/action";
|
||||
import { fetchOrganizations } from "../../core/redux/actions/organizationActions";
|
||||
import { deletePaymentMethod, fetchPaymentMethod, fetchPaymentMethods } from "../../core/redux/actions/paymentMethodActions";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
const CompanyList = () => {
|
||||
const {
|
||||
organizations: apiPaymentMethods,
|
||||
loading,
|
||||
error,
|
||||
totalPaymentMethods,
|
||||
totalPages,
|
||||
pageSize: reduxPageSize,
|
||||
currentPage: reduxCurrentPage,
|
||||
} = useSelector((state) => state.organizations);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.toggle_header);
|
||||
const dataSource = apiPaymentMethods?.length > 0 ? apiPaymentMethods : [];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
|
||||
const [pageSize, setPageSize] = useState(reduxPageSize || 10);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadPaymentMethods = async () => {
|
||||
try {
|
||||
const searchParams = {
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
search: debouncedSearchTerm || "",
|
||||
};
|
||||
|
||||
// Remove empty parameters
|
||||
const cleanParams = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([, value]) => value !== "")
|
||||
);
|
||||
|
||||
await dispatch(fetchOrganizations(cleanParams));
|
||||
} catch (error) {
|
||||
console.error("Failed to load categories", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadPaymentMethods();
|
||||
}, [dispatch, currentPage, pageSize, debouncedSearchTerm]);
|
||||
|
||||
// Debounce search term
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
}, 500); // 500ms delay
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
// Handle pagination
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = (newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1); // Reset to first page when changing page size
|
||||
};
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const value = e.target.value;
|
||||
setSearchTerm(value);
|
||||
// Reset to first page when searching
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Calculate pagination info
|
||||
const totalRecords = totalPaymentMethods || dataSource.length;
|
||||
const calculatedTotalPages = Math.ceil(totalRecords / pageSize);
|
||||
const actualTotalPages = totalPages || calculatedTotalPages;
|
||||
|
||||
const handleDeletePaymentMethod = async (paymentMethodId) => {
|
||||
try {
|
||||
await dispatch(deletePaymentMethod(paymentMethodId));
|
||||
// Show success message
|
||||
MySwal.fire({
|
||||
title: "Deleted!",
|
||||
text: "Payment Method has been deleted successfully.",
|
||||
icon: "success",
|
||||
className: "btn btn-success",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-success",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete payment method:", error);
|
||||
MySwal.fire({
|
||||
title: "Error!",
|
||||
text: "Failed to delete payment method. Please try again.",
|
||||
icon: "error",
|
||||
className: "btn btn-danger",
|
||||
customClass: {
|
||||
confirmButton: "btn btn-danger",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderTooltip = (props) => (
|
||||
<Tooltip id="pdf-tooltip" {...props}>
|
||||
Pdf
|
||||
</Tooltip>
|
||||
);
|
||||
const renderExcelTooltip = (props) => (
|
||||
<Tooltip id="excel-tooltip" {...props}>
|
||||
Excel
|
||||
</Tooltip>
|
||||
);
|
||||
const renderPrinterTooltip = (props) => (
|
||||
<Tooltip id="printer-tooltip" {...props}>
|
||||
Printer
|
||||
</Tooltip>
|
||||
);
|
||||
const renderRefreshTooltip = (props) => (
|
||||
<Tooltip id="refresh-tooltip" {...props}>
|
||||
Refresh
|
||||
</Tooltip>
|
||||
);
|
||||
const renderCollapseTooltip = (props) => (
|
||||
<Tooltip id="refresh-tooltip" {...props}>
|
||||
Collapse
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const dateOptions = [
|
||||
{ label: "Sort By: Last 7 Days", value: "last7days" },
|
||||
{ label: "Sort By: Last Month", value: "lastmonth" },
|
||||
{ label: "Sort By: Ascending", value: "ascending" },
|
||||
{ label: "Sort By: Descending", value: "descending" },
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Company Name",
|
||||
dataIndex: "companyname",
|
||||
render: (_, record) => {
|
||||
return <span>{record.name}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.name.length - b.name.length,
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
dataIndex: "email",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.email ?? '-'}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.email.length - b.email.length,
|
||||
},
|
||||
{
|
||||
title: "Phone Number",
|
||||
dataIndex: "phonenumber",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.phone_number ?? '-'}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.phone_number.length - b.phone_number.length,
|
||||
},
|
||||
{
|
||||
title: "Plan",
|
||||
dataIndex: "plan",
|
||||
render: (_, record) => {
|
||||
return <span>{record?.plan_type ?? '-'}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.plan_type.length - b.plan_type.length,
|
||||
},
|
||||
{
|
||||
title: "Created On",
|
||||
dataIndex: "createdon",
|
||||
render: (_, record) => {
|
||||
return <span>{formatDate(record.created_at)}</span>;
|
||||
},
|
||||
sorter: (a, b) => a.created_at.length - b.created_at.length,
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (_, record) => (
|
||||
<td className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
<Link
|
||||
className="me-2 p-2"
|
||||
to="#"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-category"
|
||||
onClick={() => dispatch(fetchPaymentMethod(record.id))}
|
||||
>
|
||||
<i data-feather="edit" className="feather-edit"></i>
|
||||
</Link>
|
||||
<Link
|
||||
className="confirm-text p-2"
|
||||
to="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
MySwal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "You won't be able to revert this!",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#00ff00",
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
cancelButtonColor: "#ff0000",
|
||||
cancelButtonText: "Cancel",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
handleDeletePaymentMethod(record.id || record.key);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trash2 className="feather-trash-2" />
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
),
|
||||
},
|
||||
];
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-wrapper">
|
||||
<div className="content">
|
||||
<div className="page-header">
|
||||
<div className="add-item d-flex">
|
||||
<div className="page-title">
|
||||
<h4>Companies</h4>
|
||||
<h6>Manage your companies</h6>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="table-top-head">
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderTooltip}>
|
||||
<Link>
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/pdf.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderExcelTooltip}>
|
||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<ImageWithBasePath
|
||||
src="assets/img/icons/excel.svg"
|
||||
alt="img"
|
||||
/>
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderPrinterTooltip}>
|
||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<i data-feather="printer" className="feather-printer" />
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderRefreshTooltip}>
|
||||
<Link data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<RotateCcw />
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<OverlayTrigger placement="top" overlay={renderCollapseTooltip}>
|
||||
<Link
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
id="collapse-header"
|
||||
className={data ? "active" : ""}
|
||||
onClick={() => {
|
||||
dispatch(setToogleHeader(!data));
|
||||
}}
|
||||
>
|
||||
<ChevronUp />
|
||||
</Link>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="page-btn">
|
||||
<Link
|
||||
to="#"
|
||||
className="btn btn-added"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#add-category"
|
||||
>
|
||||
<PlusCircle className="me-2" />
|
||||
Add New Category
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* /product list */}
|
||||
<div className="card table-list-card">
|
||||
<div className="card-body">
|
||||
<div className="table-top">
|
||||
<div className="search-set">
|
||||
<div className="search-input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
className="form-control form-control-sm formsearch"
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Link to className="btn btn-searchset">
|
||||
<i data-feather="search" className="feather-search" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
style={{ height: 36 }}
|
||||
defaultValue={dateOptions[0]?.value}
|
||||
options={dateOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive">
|
||||
{loading ? (
|
||||
<div className="text-center p-4">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p className="mt-2">Loading categories...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<strong>Error:</strong> {error}
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger ms-2"
|
||||
onClick={() => dispatch(fetchPaymentMethods())}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Table columns={columns} dataSource={dataSource} />
|
||||
|
||||
<CustomPagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalRecords}
|
||||
totalPages={actualTotalPages}
|
||||
loading={loading}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showInfo={true}
|
||||
showPageSizeSelector={true}
|
||||
compact={false}
|
||||
className="paymentmethod-list-pagination"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /category list */}
|
||||
</div>
|
||||
</div>
|
||||
<AddCategoryList />
|
||||
<EditCategoryList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyList;
|
||||
@ -5,7 +5,6 @@ const ENDPOINTS = {
|
||||
CATEGORIES: 'categories',
|
||||
CATEGORY_BY_ID: (id) => `categories/${id}`,
|
||||
CATEGORY_PRODUCTS: (id) => `categories/${id}/products`,
|
||||
SEARCH: 'categories/search',
|
||||
};
|
||||
|
||||
// Categories API service
|
||||
@ -65,19 +64,6 @@ export const categoriesApi = {
|
||||
}
|
||||
},
|
||||
|
||||
// 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 {
|
||||
|
||||
91
src/services/organizationsApi.js
Normal file
91
src/services/organizationsApi.js
Normal file
@ -0,0 +1,91 @@
|
||||
import api from './api';
|
||||
|
||||
const ENDPOINTS = {
|
||||
ORGANIZATIONS: 'organizations',
|
||||
ORGANIZATION_BY_ID: (id) => `organizations/${id}`,
|
||||
};
|
||||
|
||||
export const organizationsApi = {
|
||||
// Get all organizations
|
||||
getAllOrganizations: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.ORGANIZATIONS, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching organizations:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get organization by ID
|
||||
getOrganizationById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.ORGANIZATION_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching organization ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create organization
|
||||
createOrganization: async (data) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.ORGANIZATIONS, data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating organization:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update organization
|
||||
updateOrganization: async (id, data) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.ORGANIZATION_BY_ID(id), data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating organization ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete organization
|
||||
deleteOrganization: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.ORGANIZATION_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting organization ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk update
|
||||
bulkUpdate: async (organizations) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.ORGANIZATIONS}/bulk`, {
|
||||
organizations,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating organizations:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk delete
|
||||
bulkDelete: async (ids) => {
|
||||
try {
|
||||
const response = await api.delete(`${ENDPOINTS.ORGANIZATIONS}/bulk`, {
|
||||
data: { ids },
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting organizations:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default organizationsApi;
|
||||
91
src/services/paymentMethodsApi.js
Normal file
91
src/services/paymentMethodsApi.js
Normal file
@ -0,0 +1,91 @@
|
||||
import api from './api';
|
||||
|
||||
const ENDPOINTS = {
|
||||
PAYMENT_METHODS: 'payment-methods',
|
||||
PAYMENT_METHOD_BY_ID: (id) => `payment-methods/${id}`,
|
||||
};
|
||||
|
||||
export const paymentMethodsApi = {
|
||||
// Get all payment methods
|
||||
getAll: async (params = {}) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.PAYMENT_METHODS, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching payment methods:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get payment method by ID
|
||||
getById: async (id) => {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.PAYMENT_METHOD_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching payment method ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create new payment method
|
||||
create: async (data) => {
|
||||
try {
|
||||
const response = await api.post(ENDPOINTS.PAYMENT_METHODS, data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating payment method:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Update payment method
|
||||
update: async (id, data) => {
|
||||
try {
|
||||
const response = await api.put(ENDPOINTS.PAYMENT_METHOD_BY_ID(id), data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error updating payment method ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Delete payment method
|
||||
remove: async (id) => {
|
||||
try {
|
||||
const response = await api.delete(ENDPOINTS.PAYMENT_METHOD_BY_ID(id));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting payment method ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk update
|
||||
bulkUpdate: async (methods) => {
|
||||
try {
|
||||
const response = await api.put(`${ENDPOINTS.PAYMENT_METHODS}/bulk`, {
|
||||
methods,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating payment methods:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Bulk delete
|
||||
bulkDelete: async (ids) => {
|
||||
try {
|
||||
const response = await api.delete(`${ENDPOINTS.PAYMENT_METHODS}/bulk`, {
|
||||
data: { ids },
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting payment methods:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default paymentMethodsApi;
|
||||
@ -10,4 +10,8 @@ const formatDate = (isoDate) => {
|
||||
return formatted
|
||||
};
|
||||
|
||||
export { formatDate };
|
||||
const formatInputDate = (date) => {
|
||||
return new Date(date).toLocaleDateString("en-CA");
|
||||
};
|
||||
|
||||
export { formatDate, formatInputDate };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user