add superadmin authentication

This commit is contained in:
ferdiansyah783 2025-08-03 11:14:55 +07:00
parent a40a1994e5
commit 0656cda2ec
22 changed files with 2887 additions and 791 deletions

View File

@ -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);

View File

@ -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",

View File

@ -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} />
))}

View File

@ -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,

View 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;

View File

@ -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

View 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,
});

View 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,
});

View File

@ -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;

View 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;

View 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;

View 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;

View File

@ -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>

View File

@ -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]) => {

View File

@ -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">

View File

@ -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>

View 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;

View File

@ -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 {

View 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;

View 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;

View File

@ -10,4 +10,8 @@ const formatDate = (isoDate) => {
return formatted
};
export { formatDate };
const formatInputDate = (date) => {
return new Date(date).toLocaleDateString("en-CA");
};
export { formatDate, formatInputDate };