Compare commits
171 Commits
335bd0bddf
...
testing
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2c17a3d4a | ||
|
|
0ecc030f20 | ||
|
|
5b37c9da1e | ||
|
|
9bfbf5ddb8 | ||
|
|
2e8f8d5a77 | ||
|
|
248980d24b | ||
|
|
8966295260 | ||
|
|
df62dcda64 | ||
|
|
44f34490a0 | ||
|
|
8efb309fa9 | ||
|
|
171a92a3dc | ||
|
|
ec021ee33c | ||
|
|
3aa9ebf619 | ||
|
|
8f19a41c57 | ||
|
|
638a68ff03 | ||
|
|
e5361a5d77 | ||
|
|
71c23a2125 | ||
|
|
abaae9603e | ||
|
|
7a3e76aa35 | ||
|
|
eaf8e77b79 | ||
|
|
2488ab98c0 | ||
|
|
6648cd3bf3 | ||
| d1cc504397 | |||
|
|
1e7b7347e4 | ||
|
|
32ccea4917 | ||
|
|
1fafdfe54d | ||
|
|
20a92a60ab | ||
|
|
284b5f3099 | ||
|
|
901dfb93a6 | ||
|
|
2805528ba3 | ||
|
|
a8a0e2ffef | ||
|
|
897572c1f1 | ||
|
|
03ab1dcd6f | ||
|
|
9d8278900d | ||
|
|
f65b5f9b2d | ||
|
|
ffda4cf04b | ||
|
|
f2d101ef16 | ||
|
|
b91c97c6c2 | ||
|
|
2a0517d8dc | ||
|
|
0c6238c843 | ||
|
|
0531684c73 | ||
|
|
49e8e5531f | ||
|
|
15c9cc1d94 | ||
|
|
cb7bdcdeb3 | ||
|
|
8106d505a0 | ||
|
|
021eb6e4db | ||
|
|
98f0e9dd29 | ||
|
|
e8f28df886 | ||
|
|
1fc5756af0 | ||
|
|
4a1a958475 | ||
|
|
f1772a7e07 | ||
|
|
57509b2de8 | ||
|
|
e6e8f59afa | ||
|
|
29f30f9f98 | ||
|
|
963b85b15f | ||
|
|
58e49c0f87 | ||
|
|
a1781ade46 | ||
|
|
6856b4b610 | ||
|
|
7c3b0a52c5 | ||
|
|
3a847d874c | ||
|
|
8acc9430b3 | ||
|
|
c9bbae35f9 | ||
|
|
0997b16add | ||
|
|
8b69f2df6a | ||
|
|
7e8c9c92d0 | ||
|
|
f08fdc682b | ||
|
|
ab029438fb | ||
|
|
ae9df75508 | ||
|
|
0192d4be37 | ||
|
|
628ab225ff | ||
|
|
3549035672 | ||
|
|
982513c86e | ||
|
|
9180076edb | ||
|
|
2d15461f86 | ||
|
|
940c2a11d0 | ||
|
|
9fb5e230b7 | ||
|
|
44a6f60868 | ||
|
|
c0b0a5f431 | ||
|
|
22e5421d95 | ||
|
|
ff651461a1 | ||
|
|
346af173b2 | ||
|
|
a673ea69d4 | ||
| 9b97228ec8 | |||
| 2cb6173b81 | |||
| dc1aa2250f | |||
| a554803603 | |||
|
|
0f11dd0019 | ||
| 83a180354d | |||
| bf938178a4 | |||
| 421926a6ff | |||
| 097d8bad99 | |||
| e24d48b917 | |||
| cd586ddf1b | |||
| 001d346426 | |||
| 2ad88f91d4 | |||
| b56710d363 | |||
| 22d07519ff | |||
|
|
68fc0bfdf5 | ||
| 1b27fa32ce | |||
| 9579771f98 | |||
|
|
c7de34fda2 | ||
|
|
599f320598 | ||
| 6f25a6a234 | |||
| 5db4c3a236 | |||
| dc3c861471 | |||
| 066238b334 | |||
| 0bd8cc6434 | |||
| 84a64c9ae6 | |||
| c5c3ebfedd | |||
|
|
4ab8daaaf8 | ||
|
|
dbde49fffa | ||
| 688f674062 | |||
| b960b116dc | |||
| 795643eead | |||
|
|
59df59a9d6 | ||
| 895516977a | |||
|
|
31f1c5005e | ||
| 2149144d84 | |||
|
|
768e0ec8ae | ||
|
|
90792de1ac | ||
|
|
8908bacc8a | ||
|
|
38e5185d22 | ||
|
|
663cdfc297 | ||
|
|
e36c343cbf | ||
| bc1d1923d2 | |||
| b9b62bb034 | |||
| 5ae4660507 | |||
|
|
05074b6055 | ||
| 22f31a76cb | |||
|
|
1041528f1e | ||
|
|
6a2c3ba20d | ||
|
|
f898f28deb | ||
|
|
a80163d589 | ||
| 7a7aeab5b8 | |||
| e9fe9410ac | |||
| a9cf30252b | |||
| 6c7d38779a | |||
| 7dcff8798d | |||
| 0d34e1bae7 | |||
|
|
4fa87e01b6 | ||
|
|
943f401e63 | ||
| 18c9ff8b78 | |||
| 3aa93b358f | |||
|
|
027af5f93e | ||
| 9822388ca3 | |||
|
|
f7e76c8fc0 | ||
|
|
f0fcdc14ea | ||
| d56678786b | |||
|
|
0c219ba1a9 | ||
|
|
9e3918b2eb | ||
| b9a902507e | |||
| d37c8ed196 | |||
| cd125a5932 | |||
|
|
50078e4cd7 | ||
|
|
4cdef1270b | ||
|
|
611d416109 | ||
|
|
7fdcab7215 | ||
| c47236869f | |||
|
|
315eaccf50 | ||
|
|
5029837c1b | ||
|
|
39b16bd6e6 | ||
|
|
0c3ccce916 | ||
|
|
07a5d4dd52 | ||
|
|
aabbbfff8b | ||
|
|
a64731f79a | ||
| 7ac9c41556 | |||
|
|
127cce73e6 | ||
|
|
ea3ae8b621 | ||
| 13e6260ea2 | |||
| e23067bb1b | |||
| 6cccadb1a9 |
23
.env
Normal file
23
.env
Normal file
@@ -0,0 +1,23 @@
|
||||
# VITE_API_URL='https://ssa.betadelivery.com/apia/v1'
|
||||
# # VITE_API_URL='http://192.16.50.44/seo-backend/apia/v1'
|
||||
# VITE_USER_NAME="Admin"
|
||||
# VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
|
||||
# VITE_APP_NAME=MyViteApp
|
||||
# VITE_IMG_TEMPLATES='https://ssa.betadelivery.com/storage/app/public/uploads/post_templates/'
|
||||
# VITE_API_URL='https://ssa.betadelivery.com/testing/apia/v1'
|
||||
# VITE_API_URL='https://ssa.betadelivery.com/apia/v1'
|
||||
# VITE_USER_NAME="Admin"
|
||||
# VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
|
||||
# # VITE_PASSWORD="71%@L%es^bUX94`J9XT*%4&^%tUU^%Q^ffgt"
|
||||
# VITE_APP_NAME=MyViteApp
|
||||
# VITE_IMG_TEMPLATES='https://ssa.betadelivery.com/testing/storage/app/public/uploads/post_templates/'
|
||||
# VITE_MAIN_URL='https://ssa.betadelivery.com/testing'
|
||||
# VITE_POST_IMG=`${VITE_MAIN_URL}/storage/app/public/uploads/post/`
|
||||
VITE_API_URL='https://ssa.betadelivery.com/testing/apia/v1'
|
||||
VITE_USER_NAME="Admin"
|
||||
VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
|
||||
VITE_APP_NAME=MyViteApp
|
||||
VITE_MAIN_URL='https://ssa.betadelivery.com/testing'
|
||||
VITE_IMG_TEMPLATES=`${VITE_MAIN_URL}/storage/app/public/uploads/post_templates/`
|
||||
VITE_IMG_PROFILE=`${VITE_MAIN_URL}/storage/app/public/uploads/profile/`
|
||||
VITE_POST_IMG=`${VITE_MAIN_URL}/storage/app/public/uploads/post/`
|
||||
7
.env.testing
Normal file
7
.env.testing
Normal file
@@ -0,0 +1,7 @@
|
||||
VITE_API_URL='https://ssa.betadelivery.com/testing/apia/v1'
|
||||
VITE_USER_NAME="Admin"
|
||||
VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
|
||||
VITE_APP_NAME=MyViteApp
|
||||
VITE_IMG_TEMPLATES='https://ssa.betadelivery.com/storage/app/public/uploads/post_templates/'
|
||||
VITE_MAIN_URL='https://ssa.betadelivery.com/testing'
|
||||
VITE_POST_IMG=`${VITE_MAIN_URL}/storage/app/public/uploads/post/`
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.env
|
||||
|
||||
@@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.jlpvaemfr1"
|
||||
"revision": "0.73grfmd27h8"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
||||
26
index.html
26
index.html
@@ -1,13 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SEO Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" name="referrer" content="strict-origin-when-cross-origin" />
|
||||
<!-- <link rel="icon" type="image/png+xml" href="/src/assets/favicon.png" /> -->
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SEO Admin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5120
package-lock.json
generated
5120
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -13,18 +13,26 @@
|
||||
"@chakra-ui/react": "^3.2.3",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"axios": "^1.7.9",
|
||||
"chart.js": "^4.4.7",
|
||||
"framer-motion": "^11.18.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"vite-plugin-pwa": "^0.21.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "^3.2.3",
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
|
||||
81
src/App.tsx
81
src/App.tsx
@@ -1,24 +1,65 @@
|
||||
import { useContext } from 'react';
|
||||
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
|
||||
import GlobalStateContext from './Contexts/GlobalStateContext';
|
||||
import DefaultLayout from './Layouts/DefaultLayout';
|
||||
import Login from './Pages/Login';
|
||||
import { RouteLink } from './Routes/Routes';
|
||||
import { useContext, useEffect } from "react";
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
|
||||
import GlobalStateContext from "./Contexts/GlobalStateContext";
|
||||
import DefaultLayout from "./Layouts/DefaultLayout";
|
||||
import Login from "./Pages/Login";
|
||||
import { RouteLink } from "./Routes/Routes";
|
||||
import ForgotPassword from "./Pages/ForgotPassword";
|
||||
import VerifyEnterOTP from "./Pages/VerifyEnterOTP";
|
||||
import SetNewPassword from "./Pages/SetNewPassword";
|
||||
|
||||
function App() {
|
||||
const context = useContext(GlobalStateContext);
|
||||
if (!context) throw new Error('App must be used within a GlobalStateProvider');
|
||||
const { isAuthenticate } = context;
|
||||
function App() {
|
||||
const context = useContext(GlobalStateContext);
|
||||
if (!context) throw new Error("App must be used within a GlobalStateProvider");
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/*" element={isAuthenticate === true ? (<DefaultLayout><Routes>{RouteLink.map(({ path, Component }, index) => (<Route key={index} path={path} element={<Component />} />))}</Routes></DefaultLayout>) : (<Login />)} />
|
||||
<Route path="*" element={<Login />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
const { isAuthenticate, setIsAuthenticate } = context;
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
setIsAuthenticate(!!token); // Converts token to boolean
|
||||
}, [setIsAuthenticate]);
|
||||
|
||||
console.log("Auth Status:", isAuthenticate);
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* Redirect logged-in users away from login */}
|
||||
<Route path="/login" element={isAuthenticate && localStorage.getItem("token") ? <Navigate to="/" /> : <Login />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
<Route path="/forgot-password/verify-otp" element={<VerifyEnterOTP />} />
|
||||
<Route path="/forgot-password/reset-password" element={<SetNewPassword />} />
|
||||
|
||||
{/* Protected Routes */}
|
||||
<Route
|
||||
path="/*"
|
||||
element={isAuthenticate && localStorage.getItem("token") ? (
|
||||
<DefaultLayout>
|
||||
<Routes>
|
||||
{RouteLink.map(({ path, Component }, index) => (
|
||||
<Route key={index} path={path} element={<Component />} />
|
||||
))}
|
||||
</Routes>
|
||||
</DefaultLayout>
|
||||
) : (
|
||||
<Routes>
|
||||
{/* Allow only forgot password flows */}
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
<Route path="/forgot-password/verify" element={<VerifyEnterOTP />} /> {/* ✅ Add this line */}
|
||||
<Route path="/forgot-password/verify-otp" element={<VerifyEnterOTP />} />
|
||||
<Route path="/forgot-password/reset-password" element={<SetNewPassword />} />
|
||||
|
||||
{/* Default to login */}
|
||||
<Route path="*" element={<Navigate to="/login" />} />
|
||||
</Routes>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Catch-all route to prevent unauthorized access */}
|
||||
<Route path="*" element={<Navigate to="/login" />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
import { createContext, Dispatch, SetStateAction } from 'react';
|
||||
|
||||
// Define the shape of your context value
|
||||
type GlobalStateContextType = {
|
||||
export interface GlobalStateContextType {
|
||||
isAuthenticate: boolean;
|
||||
setIsAuthenticate: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
isBarLoading: boolean;
|
||||
setIsBarLoading: Dispatch<SetStateAction<boolean>>;
|
||||
userAccess: string[];
|
||||
setUserAccess: Dispatch<SetStateAction<string[]>>;
|
||||
}
|
||||
// Create the context with a default value of `undefined`
|
||||
const GlobalStateContext = createContext<GlobalStateContextType | undefined>(undefined);
|
||||
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import { ReactNode, useState } from 'react';
|
||||
import GlobalStateContext from './GlobalStateContext';
|
||||
import { ReactNode, useState } from "react";
|
||||
import GlobalStateContext from "./GlobalStateContext";
|
||||
|
||||
|
||||
|
||||
|
||||
const GlobalStateProvider = ({ children }:{children:ReactNode}) => {
|
||||
const GlobalStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [isAuthenticate, setIsAuthenticate] = useState<boolean>(true);
|
||||
const [isBarLoading, setIsBarLoading] = useState<boolean>(false); // ✅ Fixed typo
|
||||
const [userAccess, setUserAccess] = useState<string[]>(() => {
|
||||
const stored = sessionStorage.getItem('userAccess');
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
});
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<GlobalStateContext.Provider value={{ isAuthenticate, setIsAuthenticate }}>
|
||||
<GlobalStateContext.Provider value={{
|
||||
isAuthenticate,
|
||||
setIsAuthenticate,
|
||||
isBarLoading,
|
||||
setIsBarLoading, // ✅ Fixed typo
|
||||
userAccess,
|
||||
setUserAccess
|
||||
}}>
|
||||
{children}
|
||||
</GlobalStateContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,51 +1,98 @@
|
||||
import { HStack, Image, Text, VStack } from "@chakra-ui/react";
|
||||
import React, { FC } from "react";
|
||||
import { RiNotificationLine } from "react-icons/ri";
|
||||
import React, { FC, useContext } from "react";
|
||||
// import { RiNotificationLine } from "react-icons/ri";
|
||||
import { NavLink, useLocation, useNavigate } from "react-router-dom";
|
||||
import { nav } from "../Routes/Nav";
|
||||
import logo from '../assets/logo.svg';
|
||||
import { AccordionItem, AccordionItemContent, AccordionItemTrigger, AccordionRoot } from "../components/ui/accordion";
|
||||
import { Avatar } from "../components/ui/avatar";
|
||||
import { LuLogOut } from "react-icons/lu";
|
||||
import { logout } from "../Redux/Service/authSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import { useLogOutMutation } from "../Redux/Service/apiSlice";
|
||||
import ProgressBar from "../components/ProgressBar/ProgressBar";
|
||||
import { useGetProfileQuery } from "../Redux/Service/profile.password";
|
||||
|
||||
const PROFILEIMGURL = import.meta.env.VITE_IMG_PROFILE
|
||||
|
||||
const DefaultLayout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { data } = useGetProfileQuery()
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
|
||||
const context = useContext(GlobalStateContext);
|
||||
if (!context) {
|
||||
throw new Error('App must be used within a GlobalStateProvider');
|
||||
}
|
||||
const { setIsAuthenticate, isBarLoading, userAccess } = context;
|
||||
const [logOutAdmin] = useLogOutMutation()
|
||||
|
||||
const filteredNav = nav.filter(item =>
|
||||
userAccess.includes('*') || !item.resourceTitle || userAccess.includes(item.resourceTitle)
|
||||
);
|
||||
|
||||
|
||||
// Logout function
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
// ✅ Call mutation and wait for the response
|
||||
const res = await logOutAdmin().unwrap();
|
||||
console.log("Logout Success:", res);
|
||||
|
||||
// ✅ Clear local storage & update authentication state
|
||||
dispatch(logout());
|
||||
localStorage.removeItem("token");
|
||||
setIsAuthenticate(false);
|
||||
// ✅ Redirect to login page
|
||||
navigate("/login");
|
||||
} catch (error) {
|
||||
console.error("Logout Failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack position={'relative'} bg="#F2F2F2" backgroundPosition="center" backgroundRepeat="repeat" backgroundSize="cover" gap={0} w="100%" h="100vh" p={0}>
|
||||
|
||||
<VStack zIndex={1} gap={0} rounded={'lg'} h="100%" w="16%" overflow={'scroll'} >
|
||||
<HStack w={'100%'} p={3} h={'8%'} justifyContent={'center'}>
|
||||
<Image w={55} src={logo} />
|
||||
</HStack>
|
||||
<VStack w={'100%'} p={4} pt={0}>
|
||||
{nav?.map(({ title, path, Icon, type, children }, index) => type === 'single' ?
|
||||
<NavLink className="link" key={index} to={path} style={{ cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #ffffff', backgroundColor:'#fff', color:'#000', boxShadow:'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px'}} ><Icon style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>{title}</Text></NavLink> :
|
||||
<AccordionRoot bg={'#fff'} rounded={'lg'} collapsible>
|
||||
<AccordionItem boxShadow={'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px'} borderBottom={'none'} p={0} key={index} value={title}>
|
||||
<AccordionItemTrigger className="Oxygen" color={'#fff'} onClick={() => navigate(path)} gap={0} style={{ cursor: 'pointer', borderRadius: '8px', padding: '5px', width: '100%', display: 'flex', alignItems: 'center', border: '1px solid #ffffff', backgroundColor:'#fff',color:'#000', fontSize: '14px', }}> <Text fontSize={'xs'} gap={1} display={'flex'} alignItems={'center'} ><Icon style={{ fontSize: '20px' }} />{title}</Text></AccordionItemTrigger>
|
||||
{children?.map(({ title, path, Icon }, index) => <AccordionItemContent className={`linkChild Oxygen ${location?.pathname === path && 'activeChild'}`} key={index} onClick={()=>navigate(path)} style={{ marginTop: 6, cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #ffffff', backgroundColor:'#fff',color:'#919198' }} ><Icon style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>{title}</Text></AccordionItemContent>)}
|
||||
</AccordionItem>
|
||||
</AccordionRoot>)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
<VStack gap={0} h="100%" w="85%" >
|
||||
<HStack h={'8%'} w={'100%'} justifyContent={'flex-end'} pe={3} gap={6}>
|
||||
<NavLink to={'/manage-notification'}><RiNotificationLine color="#013e3e" cursor={'pointer'} style={{ fontSize: '22px' }} /></NavLink>
|
||||
<HStack cursor={'pointer'} onClick={() => navigate('/profile')} >
|
||||
<Avatar size={'sm'} src="https://i.pinimg.com/736x/d6/cd/0f/d6cd0ffd4634b0763d3958a7325ce26e.jpg" />
|
||||
<VStack color={'#013e3e'} gap={0} alignItems={'flex-start'}>
|
||||
<Text fontSize={'sm'} fontWeight={'bold'}>Ritesh Pandey</Text>
|
||||
<Text fontSize={'xs'} >ritesh.pandey@wdimails.com</Text>
|
||||
</VStack>
|
||||
<VStack gap={0} w="100%" h="100vh" bg="#F2F2F2">
|
||||
<ProgressBar isLoading={isBarLoading} />
|
||||
<HStack overflow={'hidden'} position={'relative'} bg="#F2F2F2" backgroundPosition="center" backgroundRepeat="repeat" backgroundSize="cover" gap={0} w="100%" h="calc(100% - 4px)" p={0}>
|
||||
|
||||
<VStack pt={0} zIndex={1} gap={0} rounded={'lg'} h="100%" w="15%" overflow={'auto'} >
|
||||
<HStack w={'100%'} p={3} h={'7%'} justifyContent={'center'}>
|
||||
<Image w={55} src={logo} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
{children}
|
||||
</VStack>
|
||||
</HStack>
|
||||
<VStack w={'100%'} p={2} pt={0}>
|
||||
{filteredNav?.map(({ title, path, Icon, type, children, initPath }, index) => type === 'single' ?
|
||||
<NavLink className="link" key={index} to={path || ''} style={{ cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #ffffff', backgroundColor: '#fff', color: '#000', boxShadow: 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' }} ><Icon style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>{title}</Text></NavLink> :
|
||||
<AccordionRoot border={location?.pathname.startsWith(initPath ?? path) ? "1px solid #02A0A0" : '1px'} key={index} bg={'#fff'} rounded={'lg'} collapsible>
|
||||
<AccordionItem rounded={'lg'} bg={'#fff'} boxShadow={'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px'} borderBottom={'none'} p={0} key={index} value={title}>
|
||||
<AccordionItemTrigger className="Oxygen" color={'#fff'} onClick={() => navigate(path || '')} gap={0} style={{ cursor: 'pointer', borderRadius: '8px', padding: '5px', width: '100%', display: 'flex', alignItems: 'center', border: '1px solid #ffffff', backgroundColor: '#fff', color: '#000', fontSize: '14px', }}> <Text fontSize={'xs'} gap={1} display={'flex'} alignItems={'center'} ><Icon style={{ fontSize: '20px' }} />{title}</Text></AccordionItemTrigger>
|
||||
{children?.map(({ title, path, Icon }, index) => <AccordionItemContent className={`linkChild Oxygen ${location?.pathname === path && 'activeChild'}`} key={index} onClick={() => navigate(path)} style={{ marginTop: 6, cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #ffffff', backgroundColor: '#fff', color: '#919198' }} ><Icon style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>{title}</Text></AccordionItemContent>)}
|
||||
</AccordionItem>
|
||||
</AccordionRoot>)}
|
||||
</VStack>
|
||||
<VStack w={'100%'} p={3} pt={0}>
|
||||
<HStack onClick={handleLogout} className="link" style={{ cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #ffffff', backgroundColor: '#fff', color: '#000', boxShadow: 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' }} ><LuLogOut style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>Logout</Text></HStack>
|
||||
</VStack>
|
||||
</VStack>
|
||||
<VStack gap={0} h="100%" w="85%" >
|
||||
|
||||
<HStack h={'11%'} w={'100%'} justifyContent={'flex-end'} pe={3} gap={6}>
|
||||
|
||||
{/* <NavLink to={'/manage-notification'}><RiNotificationLine color="#013e3e" cursor={'pointer'} style={{ fontSize: '22px' }} /></NavLink> */}
|
||||
<HStack cursor={'pointer'} onClick={() => navigate('/profile')} >
|
||||
<Avatar size={'sm'} src={`${PROFILEIMGURL}${data?.data.profile_photo}`} />
|
||||
<VStack color={'#013e3e'} gap={0} alignItems={'flex-start'}>
|
||||
<Text fontSize={'sm'} fontWeight={'bold'}>{data?.data?.first_name ? `${data?.data?.first_name.charAt(0).toUpperCase()}${data?.data.first_name.slice(1)}` : ''}</Text>
|
||||
<Text fontSize={'xs'} >{data?.data?.phone_number ? data?.data?.phone_number : ''}</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</HStack>
|
||||
{children}
|
||||
</VStack>
|
||||
</HStack>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
119
src/Pages/Dashboard/AgencyName.tsx
Normal file
119
src/Pages/Dashboard/AgencyName.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
// Image, Input, Stack,
|
||||
Text
|
||||
} from "@chakra-ui/react";
|
||||
// import React, { useState, useEffect } from "react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
// import { IoAddSharp } from "react-icons/io5";
|
||||
// import delateIcon from "../../assets/deleteIcon.png";
|
||||
// import { FaClockRotateLeft } from "react-icons/fa6";
|
||||
import { Link } from "react-router-dom";
|
||||
import { AgencyList } from "../../Redux/Service/dashBoard";
|
||||
|
||||
// interface Todo {
|
||||
// id: number;
|
||||
// text: string;
|
||||
// completed: boolean;
|
||||
// timestamp: string;
|
||||
// }
|
||||
|
||||
const AgencyName = ({ agencyList }: { agencyList: AgencyList }) => {
|
||||
// const [todos, setTodos] = useState<Todo[]>([]);
|
||||
// const [input, setInput] = useState<string>("");
|
||||
|
||||
|
||||
// const getCurrentTime = () => {
|
||||
// const now = new Date();
|
||||
// return now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||
// };
|
||||
|
||||
|
||||
// const addTodo = () => {
|
||||
// if (input.trim() === "") return;
|
||||
// setTodos([...todos, { id: Date.now(), text: input, completed: false, timestamp: getCurrentTime() }]);
|
||||
// setInput("");
|
||||
// };
|
||||
|
||||
// Delete a task
|
||||
// const deleteTodo = (id: number) => {
|
||||
// setTodos(todos.filter((todo) => todo.id !== id));
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// const savedTodos = localStorage.getItem("todos");
|
||||
// if (savedTodos) {
|
||||
// setTodos(JSON.parse(savedTodos));
|
||||
// }
|
||||
// }, []); // Runs only on mount
|
||||
|
||||
// // 🔹 Save todos to localStorage whenever they change
|
||||
// useEffect(() => {
|
||||
// if (todos.length > 0) {
|
||||
// localStorage.setItem("todos", JSON.stringify(todos));
|
||||
// }
|
||||
// }, [todos]); // Runs when `todos` changes
|
||||
|
||||
|
||||
return (
|
||||
<Box p={"10px"}>
|
||||
<HStack justifyContent={"space-between"} mb={5}>
|
||||
<Text fontSize={"xs"} fontWeight={500}>
|
||||
Agency List
|
||||
</Text>
|
||||
{/* <Button
|
||||
bg={"#fff"}
|
||||
color={"#222222CC"}
|
||||
px={3}
|
||||
fontSize={"12px"}
|
||||
h={"28px"}
|
||||
onClick={addTodo}
|
||||
>
|
||||
<IoAddSharp /> Add
|
||||
</Button> */}
|
||||
<Button
|
||||
bg={"#fff"}
|
||||
color={"#222222CC"}
|
||||
px={3}
|
||||
fontSize={"12px"}
|
||||
h={"28px"}
|
||||
>
|
||||
<Link to="/master-module/agency-master">View ALL</Link>
|
||||
</Button>
|
||||
</HStack>
|
||||
{/* <Input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Add a task..."
|
||||
backgroundColor={"#fff"}
|
||||
size={"sm"}
|
||||
w={"100%"}
|
||||
p={2}
|
||||
mb={4}
|
||||
/> */}
|
||||
{agencyList?.data.map((todo) => (
|
||||
<HStack key={todo.id} backgroundColor={"#fff"} rounded={5} mb={3} p={4} justifyContent={'space-between'} alignItems={'inherit'}>
|
||||
<Text fontSize={'sm'} color={'#222222bd'} fontWeight={400}>{todo.name}</Text>
|
||||
{/* <Stack display={'flex'} alignItems={'end'} w={'130px'}>
|
||||
<Box display={'flex'} alignItems={'center'} gap={2}>
|
||||
<FaClockRotateLeft fontSize={'10px'} style={{ fontSize: '13px', color: '#222222bd' }} />
|
||||
<Text fontSize={"xs"} color={"#222222bd"}>{todo.timestamp}</Text>
|
||||
</Box>
|
||||
<Box
|
||||
onClick={() => deleteTodo(todo.id)}
|
||||
bg={"none"}
|
||||
color={"#22222299"}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<Image w={"16px"} src={delateIcon} />
|
||||
</Box>
|
||||
</Stack> */}
|
||||
</HStack>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgencyName;
|
||||
@@ -1,8 +1,214 @@
|
||||
import {
|
||||
Box,
|
||||
createListCollection,
|
||||
Heading,
|
||||
HStack,
|
||||
Status,
|
||||
Tabs,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import BarChart from "../../components/Charts/BarChart";
|
||||
import CircularApp from "../../components/Charts/CircularProgress";
|
||||
import SemiDoughnutChart from "../../components/Charts/SemiDoughnutChart";
|
||||
import MainFrame from "../../components/MainFrame";
|
||||
import {
|
||||
AccordionItem,
|
||||
AccordionItemContent,
|
||||
AccordionItemTrigger,
|
||||
AccordionRoot,
|
||||
} from "../../components/ui/accordion";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectRoot,
|
||||
SelectTrigger,
|
||||
SelectValueText
|
||||
} from "../../components/ui/select";
|
||||
import AgencyName from "./AgencyName";
|
||||
import { useGetAgencyListQuery, useGetFaqListQuery, useGetNewUserQuery, useGetPastUserQuery, useGetTotalUserQuery } from "../../Redux/Service/dashBoard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<div>Dashboard</div>
|
||||
)
|
||||
}
|
||||
const { data: totalUsers } = useGetTotalUserQuery()
|
||||
const { data: pastUsers } = useGetPastUserQuery()
|
||||
const { data: newUsers } = useGetNewUserQuery()
|
||||
const {data: agencyList} = useGetAgencyListQuery()
|
||||
const { data: faqList } = useGetFaqListQuery()
|
||||
const [activeTab, setActiveTab] = useState("tab-1");
|
||||
const [totalUser, setTotalUser] = useState<any>(null);
|
||||
|
||||
export default Dashboard
|
||||
console.log("data", totalUser)
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === "tab-1") {
|
||||
setTotalUser( pastUsers?.data)
|
||||
} else if (activeTab === "tab-2") {
|
||||
setTotalUser(totalUsers?.data)
|
||||
} else if (activeTab === "tab-3") {
|
||||
setTotalUser(newUsers?.data)
|
||||
}
|
||||
}, [totalUsers?.data, pastUsers?.data, newUsers?.data, activeTab]);
|
||||
|
||||
const frameworks = createListCollection({
|
||||
items: [
|
||||
{ label: "Today", value: "Today" },
|
||||
{ label: "Week", value: "Week" },
|
||||
{ label: "Month", value: "Month" },
|
||||
{ label: "Year", value: "Year" },
|
||||
],
|
||||
});
|
||||
|
||||
// const accItems = [
|
||||
// {
|
||||
// value: "1",
|
||||
// title: "How to create new account?",
|
||||
// text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since. Lorem Ipsum has been the industry's standard dummy text ever since.",
|
||||
// },
|
||||
// {
|
||||
// value: "2",
|
||||
// title: "How to create new account?",
|
||||
// text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since. Lorem Ipsum has been the industry's standard dummy text ever since.",
|
||||
// },
|
||||
// {
|
||||
// value: "3",
|
||||
// title: "How to create new account?",
|
||||
// text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since. Lorem Ipsum has been the industry's standard dummy text ever since.",
|
||||
// },
|
||||
// {
|
||||
// value: "4",
|
||||
// title: "How to create new account?",
|
||||
// text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since. Lorem Ipsum has been the industry's standard dummy text ever since.",
|
||||
// },
|
||||
// ];
|
||||
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box display={"flex"} p={"20px"} pe={'20px'} gap={5}>
|
||||
<Box rounded={'lg'} w={"30%"} boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}>
|
||||
<Heading fontSize={"sm"} p={2}>
|
||||
Total Users : {totalUsers?.data?.totalUserCount ?? 0}
|
||||
</Heading>
|
||||
<Tabs.Root
|
||||
size={"sm"}
|
||||
w={"80%"}
|
||||
m={"auto"}
|
||||
variant="enclosed"
|
||||
fitted
|
||||
defaultValue={"tab-1"}
|
||||
value={activeTab}
|
||||
onValueChange={(details) => setActiveTab(details.value)}
|
||||
mb={6}
|
||||
>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger fontSize={"xs"} value="tab-1">
|
||||
Past 24 hrs
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger fontSize={"xs"} value="tab-2">
|
||||
Total Users
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger fontSize={"xs"} value="tab-3">
|
||||
New Signups
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
<Box>
|
||||
{totalUser && <SemiDoughnutChart totalUser={totalUser} />}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
w={"80%"}
|
||||
m={"auto"}
|
||||
display={"flex"}
|
||||
justifyContent={"space-between"}
|
||||
mt={8}
|
||||
>
|
||||
<Status.Root colorPalette="blue">
|
||||
<Status.Indicator />
|
||||
Recruiter <Text fontWeight={500}>{totalUser?.past24hourRecruiterCount ?? totalUser?.totalRecruiterCount ?? totalUser?.newRecuiterCount }</Text>
|
||||
</Status.Root>
|
||||
<Status.Root colorPalette="blue">
|
||||
<Status.Indicator />
|
||||
Customer <Text fontWeight={500}>{totalUser?.past24hourCustomercount ?? totalUser?.totalCustomerCount ?? totalUser?.newCustomerCount}</Text>
|
||||
</Status.Root>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
p={"20px"}
|
||||
w={"49%"}
|
||||
rounded={'lg'}
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
>
|
||||
<HStack alignItems={"center"} mb={4}>
|
||||
<Text fontSize={"sm"}>Item approvals in</Text>
|
||||
<SelectRoot collection={frameworks} size="xs" width="200px">
|
||||
<SelectTrigger>
|
||||
<SelectValueText p={2} placeholder="Select movie" />
|
||||
</SelectTrigger>
|
||||
<SelectContent p={2}>
|
||||
{frameworks.items.map((movie) => (
|
||||
<SelectItem item={movie} key={movie.value}>
|
||||
{movie.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
</HStack>
|
||||
<BarChart />
|
||||
</Box>
|
||||
<Box
|
||||
w={"20%"}
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
p={'10px'}
|
||||
rounded={'lg'}
|
||||
>
|
||||
<Text fontSize={"sm"} fontWeight={500} pt={3}>Number Of Groups created</Text>
|
||||
<CircularApp />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box p={"20px"} pt={0} display={"flex"} gap={5}>
|
||||
<Box w={"50%"} rounded={'lg'} bg={"#f2f2f2"} h={'100%'} p={"10px"} overflowY={'scroll'} height={'292px'}>
|
||||
<HStack justifyContent={"space-between"} mb={5}>
|
||||
<Text fontSize={"xs"} fontWeight={500}>Faqs</Text>
|
||||
<Button
|
||||
bg={"#fff"}
|
||||
color={"#222222CC"}
|
||||
px={3}
|
||||
fontSize={"12px"}
|
||||
h={"28px"}
|
||||
>
|
||||
<Link to="/manage-cms/faq">View ALL</Link>
|
||||
</Button>
|
||||
</HStack>
|
||||
<AccordionRoot collapsible defaultValue={["b"]}>
|
||||
{faqList?.data.map((item) => (
|
||||
<AccordionItem
|
||||
boxShadow={'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px'}
|
||||
key={item.id}
|
||||
value={item.faqs_xid.toString()}
|
||||
bg={"#fff"}
|
||||
mb={2}
|
||||
p={"12px"}
|
||||
rounded={5}
|
||||
borderBottom={0}
|
||||
>
|
||||
<AccordionItemTrigger fontSize={"sm"} >
|
||||
{item.question}
|
||||
</AccordionItemTrigger>
|
||||
<AccordionItemContent fontSize={"xs"} color={'#222222CC'} pt={2}>
|
||||
{item.answer}
|
||||
</AccordionItemContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</AccordionRoot>
|
||||
</Box>
|
||||
<Box w={"50%"} rounded={'lg'} bg={"#f2f2f2"} alignItems={'flex-start'} overflowY={'scroll'} height={'292px'}>
|
||||
{agencyList && <AgencyName agencyList={agencyList}/>}
|
||||
</Box>
|
||||
</Box>
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
65
src/Pages/ForgetPassword.tsx
Normal file
65
src/Pages/ForgetPassword.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react";
|
||||
import { Button } from "../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../components/ui/dialog";
|
||||
function ForgetPassword() {
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Text w={"100%"} textAlign={"end"} mt={2} cursor={"pointer"}>
|
||||
Forgot password?
|
||||
</Text>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
p={2}
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white" pt={3} pb={2}>
|
||||
<DialogTitle
|
||||
alignSelf="center"
|
||||
color="black"
|
||||
fontSize="18px"
|
||||
textAlign={"center"}
|
||||
>
|
||||
Forgot Password
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white" pt={5}>
|
||||
<Stack p={2}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Please Enter Email Address
|
||||
</Field.Label>
|
||||
<Input
|
||||
color="black"
|
||||
p={2}
|
||||
fontSize="sm"
|
||||
type="text"
|
||||
border="1px solid grey"
|
||||
size={"sm"}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Reset Password
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgetPassword;
|
||||
171
src/Pages/ForgotPassword.tsx
Normal file
171
src/Pages/ForgotPassword.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
HStack,
|
||||
Image,
|
||||
Input,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
// import { useDispatch } from "react-redux";
|
||||
import logo from "../assets/logo.svg";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Field } from "../components/ui/field";
|
||||
import { toaster, Toaster } from "../components/ui/toaster";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface FormValues {
|
||||
mobileNumber: number;
|
||||
}
|
||||
|
||||
const ForgotPassword = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>();
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
const username = import.meta.env.VITE_USER_NAME || ""; // Replace with actual username
|
||||
const password = import.meta.env.VITE_PASSWORD || ""; // Replace with actual password
|
||||
const basicAuth = `${username}:${password}`; // Encode to Base64
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/send-otp`,
|
||||
{
|
||||
mobile_number: Number(data.mobileNumber),
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Basic ${basicAuth}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (res.status === 200) {
|
||||
navigate(`/forgot-password/verify?phone=${data.mobileNumber}`)
|
||||
} else {
|
||||
// alert(res.data.message || "Something went wrong");
|
||||
toaster.create({
|
||||
// title: error?.response?.data?.message,
|
||||
title: res.data.message || "Something went wrong",
|
||||
type: "error",
|
||||
})
|
||||
setIsLoading(false);
|
||||
}
|
||||
console.log("============", res);
|
||||
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
|
||||
if (error) {
|
||||
toaster.create({
|
||||
// title: error?.response?.data?.message,
|
||||
title: "Something Went Wrong",
|
||||
type: "info",
|
||||
})
|
||||
// console.log("Login failed", error?.response?.data?.message);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
<VStack appearance={'light'} w={"100%"} h={"100vh"} bg={"#ffffff"}>
|
||||
<HStack
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
w={"100%"}
|
||||
ps={8}
|
||||
h={"7%"}
|
||||
justifyContent={"flex-start"}
|
||||
>
|
||||
<Image w={50} src={logo} />
|
||||
</HStack>
|
||||
|
||||
<HStack w={"100%"} h={"93%"} p={8} gap={8}>
|
||||
<Center
|
||||
display={{ base: "none", md: "flex" }}
|
||||
bg={"#02A0A033"}
|
||||
w={"50%"}
|
||||
h={"100%"}
|
||||
rounded={"3xl"}
|
||||
>
|
||||
<Image w={250} src={logo} />
|
||||
</Center>
|
||||
|
||||
<Box
|
||||
as={"form"}
|
||||
onSubmit={onSubmit}
|
||||
p={{ base: 4, md: 16 }}
|
||||
w={{ base: "100%", md: "50%" }}
|
||||
h={"100%"}
|
||||
>
|
||||
<VStack gap={2} w={"100%"}>
|
||||
<Text
|
||||
w={"100%"}
|
||||
textAlign={"center"}
|
||||
fontSize={"24px"}
|
||||
fontWeight={"normal"}
|
||||
color={"#313039"}
|
||||
>
|
||||
Forgot Password
|
||||
</Text>
|
||||
|
||||
<Box mt={6} gap={4} w={"full"}>
|
||||
<Field
|
||||
color={"#313039"}
|
||||
label={"Enter Mobile Number"}
|
||||
w={"100%"}
|
||||
invalid={!!errors.mobileNumber}
|
||||
errorText={errors.mobileNumber?.message}
|
||||
mb={4}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
ps={3}
|
||||
maxLength={10}
|
||||
{...register("mobileNumber", {
|
||||
required: "Please enter a 10 digit mobile number",
|
||||
pattern: {
|
||||
value: /^[0-9]{10}$/,
|
||||
message: "Mobile number must be exactly 10 digits"
|
||||
}
|
||||
})}
|
||||
placeholder="Enter Mobile Number"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
target.value = target.value.replace(/\D/g, "").slice(0, 10);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
mt={4}
|
||||
size={"sm"}
|
||||
bg={"#02A0A0"}
|
||||
rounded={"md"}
|
||||
w={"100%"}
|
||||
color={"#ffffff"}
|
||||
type="submit"
|
||||
>
|
||||
Send OTP
|
||||
</Button>
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</HStack>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPassword;
|
||||
@@ -1,96 +1,203 @@
|
||||
import { Center, HStack, Image, Input, Text, VStack } from "@chakra-ui/react"
|
||||
import { useContext, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext"
|
||||
import logo from '../assets/logo.svg'
|
||||
import { Button } from "../components/ui/button"
|
||||
import { Field } from "../components/ui/field"
|
||||
import { Toaster, toaster } from "../components/ui/toaster"
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
HStack,
|
||||
Image,
|
||||
Input,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import axios from "axios";
|
||||
import { useContext, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useDispatch } from "react-redux";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import { setToken } from "../Redux/Service/authSlice";
|
||||
import logo from "../assets/logo.svg";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Field } from "../components/ui/field";
|
||||
import { toaster, Toaster } from "../components/ui/toaster";
|
||||
import { PasswordInput } from "../components/ui/password-input";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
// import ForgetPassword from "./ForgetPassword";
|
||||
|
||||
interface FormValues {
|
||||
mobileNumber: number
|
||||
mobileNumber: number;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const Login = () => {
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const context = useContext(GlobalStateContext);
|
||||
if (!context) {
|
||||
throw new Error('App must be used within a GlobalStateProvider');
|
||||
throw new Error("App must be used within a GlobalStateProvider");
|
||||
}
|
||||
const { setIsAuthenticate } = context;
|
||||
const { setIsAuthenticate, setUserAccess } = context;
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>()
|
||||
} = useForm<FormValues>();
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
setIsLoading(true);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setIsLoading(true)
|
||||
if (data?.mobileNumber === 1234567890) {
|
||||
setTimeout(() => {
|
||||
setIsAuthenticate(true);
|
||||
setIsLoading(false)
|
||||
}, 3000); // 3-second delay
|
||||
const username = import.meta.env.VITE_USER_NAME || "";
|
||||
const password = import.meta.env.VITE_PASSWORD || "";
|
||||
const basicAuth = `${username}:${password}`;
|
||||
|
||||
} else {
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/login`,
|
||||
{
|
||||
mobile_number: Number(data.mobileNumber),
|
||||
password: data.password,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Basic ${basicAuth}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const loginData = res.data?.data;
|
||||
const { principal_type_xid, role_permission } = loginData;
|
||||
|
||||
const isAdmin = principal_type_xid === 1;
|
||||
|
||||
const allowedTitles = isAdmin
|
||||
? ['*']
|
||||
: role_permission?.get_resource_action_link
|
||||
?.filter((link: unknown) => (link as any).is_active)
|
||||
.map((link: unknown) => (link as any).app_resource.app_resource_title) ?? [];
|
||||
|
||||
setIsAuthenticate(true);
|
||||
setUserAccess(allowedTitles);
|
||||
dispatch(setToken(String(loginData["access-token"])));
|
||||
sessionStorage.setItem('userAccess', JSON.stringify(allowedTitles));
|
||||
navigate("/dashboard");
|
||||
setIsLoading(false);
|
||||
|
||||
} catch (error: any) {
|
||||
console.log('error', error?.response?.data?.message);
|
||||
toaster.create({
|
||||
title: `Invalid Credentials`,
|
||||
title: "Error",
|
||||
description: error?.response?.data?.message || "Login failed",
|
||||
type: "error",
|
||||
})
|
||||
setIsLoading(false)
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// console.log("User Access in Context:", userAccess);
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<VStack
|
||||
w={'100%'} h={'100vh'} bg={'#ffffff'} >
|
||||
|
||||
<HStack boxShadow={'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px'} w={'100%'} ps={8} h={'7%'} justifyContent={'flex-start'}>
|
||||
<Image w={50} src={logo} />
|
||||
|
||||
<VStack appearance={'light'} w={"100%"} h={"100vh"} bg={"#ffffff"}>
|
||||
<HStack
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
w={"100%"}
|
||||
ps={8}
|
||||
h={"7%"}
|
||||
justifyContent={"flex-start"}
|
||||
>
|
||||
<Image w={50} src={logo} />
|
||||
</HStack>
|
||||
|
||||
|
||||
<HStack
|
||||
w={'100%'} h={'93%'} p={8} gap={8}>
|
||||
|
||||
|
||||
|
||||
|
||||
<Center display={{ base: 'none', md: 'flex' }} bg={'#02A0A033'} w={'50%'} h={'100%'} rounded={'3xl'}>
|
||||
<HStack w={"100%"} h={"93%"} p={8} gap={8}>
|
||||
<Center
|
||||
display={{ base: "none", md: "flex" }}
|
||||
bg={"#02A0A033"}
|
||||
w={"50%"}
|
||||
h={"100%"}
|
||||
rounded={"3xl"}
|
||||
>
|
||||
<Image w={250} src={logo} />
|
||||
</Center>
|
||||
|
||||
<Box
|
||||
as={"form"}
|
||||
onSubmit={onSubmit}
|
||||
p={{ base: 4, md: 16 }}
|
||||
w={{ base: "100%", md: "50%" }}
|
||||
h={"100%"}
|
||||
>
|
||||
<VStack gap={2} w={"100%"}>
|
||||
<Text
|
||||
w={"100%"}
|
||||
textAlign={"center"}
|
||||
fontSize={"24px"}
|
||||
fontWeight={"normal"}
|
||||
color={"#313039"}
|
||||
>
|
||||
LOGIN
|
||||
</Text>
|
||||
|
||||
|
||||
|
||||
|
||||
<Center as={'form'} onSubmit={onSubmit} p={{ base: 4, md: 16 }} w={{ base: '100%', md: '50%' }} h={'100%'}>
|
||||
<VStack gap={2} w={'100%'} alignItems={'flex-start'}>
|
||||
<Text w={'100%'} textAlign={'center'} fontSize={'24px'} fontWeight={'normal'} color={'#313039'}>LOGIN</Text>
|
||||
|
||||
<VStack mt={6} gap={4} w={'full'}>
|
||||
<Field color={'#313039'} label={'Enter Mobile Number'} w={'100%'} invalid={!!errors.mobileNumber} errorText={errors.mobileNumber?.message} >
|
||||
<Input ps={3} {...register("mobileNumber", { required: "Mobile Number address is required" })} placeholder="Mobile Number Address" />
|
||||
{/* <Text as={'span'} w={'100%'} fontSize={'xs'} fontWeight={'normal'} color={'#686677'}>Forget password</Text> */}
|
||||
<Box mt={6} gap={4} w={"full"}>
|
||||
<Field
|
||||
color={"#313039"}
|
||||
label={"Enter Mobile Number"}
|
||||
w={"100%"}
|
||||
invalid={!!errors.mobileNumber}
|
||||
errorText={errors.mobileNumber?.message}
|
||||
mb={4}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
ps={3}
|
||||
{...register("mobileNumber", {
|
||||
required: "Mobile Number address is required",
|
||||
})}
|
||||
placeholder="Mobile Number Address"
|
||||
/>
|
||||
{/* <Text as={'span'} w={'100%'} fontSize={'xs'} fontWeight={'normal'} color={'#686677'}>Forget password</Text> */}
|
||||
</Field>
|
||||
<Button loading={isLoading} mt={4} size={'sm'} bg={'#02A0A0'} rounded={'md'} w={'100%'} color={'#ffffff'} type="submit">Send OTP</Button>
|
||||
|
||||
<Text>Forgot password</Text>
|
||||
</VStack>
|
||||
|
||||
<Field
|
||||
color={"#313039"}
|
||||
label={"Enter password."}
|
||||
w={"100%"}
|
||||
invalid={!!errors.password}
|
||||
errorText={errors.password?.message}
|
||||
mb={2}
|
||||
>
|
||||
<PasswordInput
|
||||
ps={3}
|
||||
{...register("password", { required: "Pasword is required" })}
|
||||
placeholder="Enter password"
|
||||
/>
|
||||
{/* <Text as={'span'} w={'100%'} fontSize={'xs'} fontWeight={'normal'} color={'#686677'}>Forget password</Text> */}
|
||||
</Field>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
mt={4}
|
||||
size={"sm"}
|
||||
bg={"#02A0A0"}
|
||||
rounded={"md"}
|
||||
w={"100%"}
|
||||
color={"#ffffff"}
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
|
||||
{/* <ForgetPassword /> */}
|
||||
<Box textAlign={"center"} mt={4}>
|
||||
<Link
|
||||
to={'/forgot-password'}
|
||||
style={{ color: "black", fontSize: "16px" }}
|
||||
>
|
||||
Forgot Password
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</VStack>
|
||||
</Center>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Login
|
||||
export default Login;
|
||||
|
||||
@@ -1,12 +1,132 @@
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
// import { Badge, HStack, Text, VStack } from "@chakra-ui/react";
|
||||
// import MainFrame from "../../../components/MainFrame"
|
||||
// import { useGetAboutUsQuery } from "../../../Redux/Service/manage.aboutus.service";
|
||||
// import { Spinner } from "../../../components/Sipnner/Spinner";
|
||||
// import GlobalStateContext from "../../../Contexts/GlobalStateContext";
|
||||
// import { useContext, useEffect } from "react";
|
||||
// import AboutUsAddModel from "./AboutUsAddModel";
|
||||
|
||||
|
||||
|
||||
// const AboutUs = () => {
|
||||
|
||||
// const { data, isLoading, isFetching } = useGetAboutUsQuery();
|
||||
|
||||
// // const content = data?.data
|
||||
// console.log('====================================');
|
||||
// console.log(data);
|
||||
// console.log('====================================');
|
||||
|
||||
// const context = useContext(GlobalStateContext);
|
||||
// if (!context) throw new Error('App must be used within a GlobalStateProvider');
|
||||
|
||||
// const { setIsBarLoading } = context;
|
||||
// useEffect(() => {
|
||||
// setIsBarLoading(isFetching)
|
||||
// }, [data])
|
||||
|
||||
// return (
|
||||
// <MainFrame transperant={true}>
|
||||
// <VStack gap={4} pb={4} pt={0}>
|
||||
// {isLoading || isFetching ?
|
||||
// <Spinner /> : data?.data?.map(({ id, content, about_language }) => <VStack bg={'#fff'}
|
||||
// boxShadow={'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px'} rounded={'lg'} p={3} key={id}>
|
||||
// <HStack
|
||||
// w={"100%"}
|
||||
// justifyContent={"space-between"}
|
||||
|
||||
// py={0}
|
||||
// px={0}
|
||||
// >
|
||||
// <Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
// About Us <Badge variant={'surface'} colorPalette="cyan" ms={2} size={'sm'} fontSize={'xs'} px={2}>🎓 {about_language?.language_name}</Badge>
|
||||
// </Text>
|
||||
|
||||
// {/* <AboutUsAddModel /> */}
|
||||
// <AboutUsAddModel/>
|
||||
// </HStack>
|
||||
// <Text
|
||||
// as="p"
|
||||
// fontSize="sm"
|
||||
// fontWeight={400}
|
||||
// color="#1D1D1D"
|
||||
// >
|
||||
// {content}
|
||||
// </Text>
|
||||
|
||||
// </VStack>)}
|
||||
// </VStack>
|
||||
// </MainFrame>
|
||||
// )
|
||||
// }
|
||||
// export default AboutUs
|
||||
|
||||
import { Badge, HStack, Stack, Text, VStack } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame";
|
||||
import { useGetAboutUsQuery } from "../../../Redux/Service/manage.aboutus.service";
|
||||
import { Spinner } from "../../../components/Sipnner/Spinner";
|
||||
import GlobalStateContext from "../../../Contexts/GlobalStateContext";
|
||||
import { useContext, useEffect } from "react";
|
||||
import AboutUsAddModel from "./AboutUsAddModel";
|
||||
|
||||
const AboutUs = () => {
|
||||
return (
|
||||
|
||||
<MainFrame >
|
||||
AboutUs
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default AboutUs
|
||||
const { data, isLoading, isFetching } = useGetAboutUsQuery();
|
||||
|
||||
console.log("Fetched About Us Data:", data);
|
||||
|
||||
const context = useContext(GlobalStateContext);
|
||||
if (!context) throw new Error("App must be used within a GlobalStateProvider");
|
||||
|
||||
const { setIsBarLoading } = context;
|
||||
useEffect(() => {
|
||||
setIsBarLoading(isFetching);
|
||||
}, [isFetching, setIsBarLoading]);
|
||||
|
||||
return (
|
||||
<MainFrame transperant={true}>
|
||||
<VStack gap={4} pb={4} pt={0}>
|
||||
<Stack bg={"#fff"} w={"100%"} mt={2} p={4} borderRadius={4}><Text color={"black"} textAlign={"left"} fontWeight={"600"} > About Us
|
||||
</Text></Stack>
|
||||
|
||||
{isLoading || isFetching ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
data?.data?.map(({ id, content, about_language }) => (
|
||||
<VStack
|
||||
bg={"#fff"}
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
rounded={"lg"}
|
||||
p={3}
|
||||
key={id}
|
||||
>
|
||||
|
||||
<HStack w={"100%"} justifyContent={"space-between"} py={0} px={0}>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
<Badge
|
||||
variant={"surface"}
|
||||
colorPalette="cyan"
|
||||
ms={2}
|
||||
size={"sm"}
|
||||
fontSize={"xs"}
|
||||
px={2}
|
||||
>
|
||||
🎓 {about_language?.language_name}
|
||||
</Badge>
|
||||
</Text>
|
||||
|
||||
{/* Pass Data to AboutUsAddModel */}
|
||||
<AboutUsAddModel aboutUsData={{ id, content, about_language }} />
|
||||
</HStack>
|
||||
<Text as="p" fontSize="sm" fontWeight={400} color="#1D1D1D">
|
||||
{/* {content} */}
|
||||
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||
</Text>
|
||||
</VStack>
|
||||
))
|
||||
)}
|
||||
</VStack>
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUs;
|
||||
|
||||
152
src/Pages/ManageCMS/AboutUs/AboutUsAddModel.tsx
Normal file
152
src/Pages/ManageCMS/AboutUs/AboutUsAddModel.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { FaRegEdit } from "react-icons/fa";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Field, Stack, Text } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import ReactQuill from "react-quill";
|
||||
import "react-quill/dist/quill.snow.css"; // Import the styles
|
||||
import { useState } from "react";
|
||||
import { useUpdateAboutUsMutation } from "../../../Redux/Service/manage.aboutus.service";
|
||||
import { useForm, Controller } from "react-hook-form"; // Import React Hook Form
|
||||
import { toaster, Toaster } from "../../../components/ui/toaster";
|
||||
|
||||
function AboutUsAddModel({ aboutUsData }: { aboutUsData: any }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// RTK Query Mutation Hook
|
||||
const [updateAboutUs, { isLoading }] = useUpdateAboutUsMutation();
|
||||
|
||||
// React Hook Form
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
content: "",
|
||||
languageCode: "",
|
||||
},
|
||||
});
|
||||
|
||||
// Function to handle edit click (pre-fill the editor)
|
||||
const handleEditClick = (data: any) => {
|
||||
setValue("content", data.content); // Pre-fill the content field
|
||||
setValue("languageCode", data.about_language.language_code); // Pre-fill the language code
|
||||
setIsOpen(true); // Open dialog
|
||||
};
|
||||
|
||||
// Function to handle update submission
|
||||
const onSubmit = async (formData: any) => {
|
||||
if (!formData.content.trim()) return; // Prevent empty updates
|
||||
|
||||
try {
|
||||
await updateAboutUs({
|
||||
id: aboutUsData.id,
|
||||
content: formData.content,
|
||||
language_code: formData.languageCode,
|
||||
}).unwrap();
|
||||
setIsOpen(false); // Close dialog on success
|
||||
reset(); // Reset the form
|
||||
} catch (error: any) {
|
||||
console.error("Update failed:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: `${error.data.message || "Failed to update"}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
bgColor="#EEEEEE"
|
||||
pl={3}
|
||||
pr={3}
|
||||
size="xs"
|
||||
color="#000"
|
||||
onClick={() => handleEditClick(aboutUsData)} // Set content before opening modal
|
||||
>
|
||||
<FaRegEdit color="#000" style={{ height: "14px", width: "14px" }} />
|
||||
<Text color="#000" mt={1}>Edit</Text>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent bg="#fff" w={{ base: "90%", md: "1200px" }} height="auto" p={3}>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit About Us
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3} mb={8}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
About Us Content
|
||||
</Field.Label>
|
||||
{/* Use Controller to integrate ReactQuill with React Hook Form */}
|
||||
<Controller
|
||||
name="content"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<ReactQuill
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter About Us content"
|
||||
modules={{
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
['clean']
|
||||
],
|
||||
}}
|
||||
formats={[
|
||||
'header',
|
||||
'bold', 'italic', 'underline', 'strike',
|
||||
'list', 'bullet',
|
||||
'link', 'image'
|
||||
]}
|
||||
style={{ color: "black", border: "none", fontSize: "12px", height: "170px", width: "100%" }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter display="flex" justifyContent="center" pt="2">
|
||||
<Button
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color="#fff"
|
||||
// isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
onClick={handleSubmit(onSubmit)} // Use handleSubmit to trigger form submission
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AboutUsAddModel;
|
||||
161
src/Pages/ManageCMS/FAQ/EditDetails.tsx
Normal file
161
src/Pages/ManageCMS/FAQ/EditDetails.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Field, Input, Stack, Textarea } from "@chakra-ui/react";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useUpdateFaqMutation } from "../../../Redux/Service/faqs.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
interface RowData {
|
||||
id: number;
|
||||
principal_type_xid: number;
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
function EditDetails({ rowData, refetch }: {rowData: RowData, refetch: VoidFunction}) {
|
||||
const [faqQuestion, setFaqQuestion] = useState(rowData?.question);
|
||||
const [faqAnswer, setFaqAnswer] = useState(rowData?.answer);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [updateFaq, { isLoading }] = useUpdateFaqMutation()
|
||||
|
||||
// console.log('ROWDATA', rowData);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (rowData) {
|
||||
setFaqQuestion(rowData.question);
|
||||
setFaqAnswer(rowData.answer);
|
||||
}
|
||||
}, [rowData]);
|
||||
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!faqQuestion.trim() || !faqAnswer.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Input fields cannot be empty",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: rowData?.id,
|
||||
question: faqQuestion,
|
||||
answer: faqAnswer,
|
||||
principal_type_xid: rowData?.principal_type_xid,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await updateFaq(payload).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "FAQ updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update FAQ",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
|
||||
<Button bg="transparent" color={"black"} h={"18px"} onClick={handleOpenModal}><Edit /></Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
p={3}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit Details (ID: {rowData?.id})
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Question
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Question"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={faqQuestion}
|
||||
onChange={(e) => setFaqQuestion(e.target.value)}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Answer
|
||||
</Field.Label>
|
||||
<Textarea
|
||||
placeholder="Answer"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
p={2}
|
||||
fontSize="12px"
|
||||
height="auto"
|
||||
pt={1.5}
|
||||
value={faqAnswer}
|
||||
onChange={(e) => setFaqAnswer(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditDetails;
|
||||
@@ -1,12 +1,248 @@
|
||||
import { Box, HStack, Image, Spinner, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import EditDetails from "./EditDetails";
|
||||
// import { InputGroup } from "../../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import AlertDailog from "../../../components/AlertDailog";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import FaqAddModel from "./FaqAddModel";
|
||||
import Delete from "../../../components/ActionIcons/Delete";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FaqData, useDeleteFaqPostMutation, useFaqToggleMutation, useGetFaqQuery } from "../../../Redux/Service/faqs.service";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import NoData from "../../../components/NoData";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
'Category',
|
||||
"Question",
|
||||
"Answer",
|
||||
"Action",
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Question": "Lorem Ipsum",
|
||||
// "Answer": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"} />
|
||||
// </Box>
|
||||
// {/* <EditDetails /> */}
|
||||
// <EditDetails id={(i + 1).toString()} question="Lorem Ipsum" answer="Lorem Ipsum" />
|
||||
|
||||
// <AlertDailog
|
||||
// AltertTiggerIcon={() => <Delete />}
|
||||
// alertText="Delete Users"
|
||||
// alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
// alertCaption="are you sure you want to delete ?"
|
||||
// onConfirm={() => {
|
||||
// console.log("User deleted:", i + 1);
|
||||
// }}
|
||||
// />
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const FAQ = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, refetch, isLoading, isFetching, isError } = useGetFaqQuery(currentPage)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [faqToggle] = useFaqToggleMutation()
|
||||
const [deleteFaqPost] = useDeleteFaqPostMutation()
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [deleteModal, setDeleteModal] = useState(false)
|
||||
const [selectedFaqId, setSelectedFaqId] = useState<number | null>(null);
|
||||
|
||||
console.log('DATA', data?.data.data);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
|
||||
const handleToggle = async (agencyId: number, currentStatus: string) => {
|
||||
const newStatus = currentStatus === '1' ? '0' : '1';
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await faqToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
setTimeout(() => {
|
||||
refetch();
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Someting went wrong.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteFaq = async (faqId: number) => {
|
||||
try {
|
||||
const response = await deleteFaqPost({ id: faqId }).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "FAQ deleted successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
console.log("FAQ deleted successfully:", response);
|
||||
}
|
||||
// Optionally, refetch data or update state after deletion
|
||||
} catch (error) {
|
||||
console.error("Error deleting FAQ:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) =>
|
||||
agency.translations.some((translation: any) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const questionMatch = translation.question?.toLowerCase().includes(searchLower);
|
||||
const answerMatch = translation.answer?.toLowerCase().includes(searchLower);
|
||||
return questionMatch || answerMatch;
|
||||
})
|
||||
);
|
||||
|
||||
console.log("filteredData", filteredData);
|
||||
|
||||
|
||||
const managepost = filteredData?.flatMap((agency: FaqData, index: number) =>
|
||||
agency.translations.map((translation: any) => ({
|
||||
'id': translation.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Question": translation.question,
|
||||
"Answer": translation.answer,
|
||||
'Category': agency.principal_type_xid === 2 ? "Recruiter" : "Job Seeker",
|
||||
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
<EditDetails rowData={{ id: agency.id, question: translation.question, answer: translation.answer, principal_type_xid: agency.principal_type_xid }} refetch={refetch} />
|
||||
|
||||
<AlertDailog
|
||||
isOpen={deleteModal}
|
||||
AltertTiggerIcon={() => <Delete onClick={() => {
|
||||
setSelectedFaqId(agency.id);
|
||||
setDeleteModal(true)
|
||||
}} />}
|
||||
alertText="Delete FAQ"
|
||||
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
alertCaption="are you sure you want to delete ?"
|
||||
onClose={() => setDeleteModal(false)}
|
||||
onConfirm={() => {
|
||||
// console.log("Deleting FAQ with ID:", selectedFaqId); // Correct ID
|
||||
if (selectedFaqId) {
|
||||
setDeleteModal(false);
|
||||
handleDeleteFaq(selectedFaqId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={'teal'}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(Number(agency.id), agency.is_active.toString())}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
key={`switch-${agency.id}`}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
})))
|
||||
|
||||
if (isFetching) {
|
||||
return (
|
||||
|
||||
<MainFrame >
|
||||
FAQ
|
||||
</MainFrame>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"center"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Spinner color={'teal'} />
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
export default FAQ
|
||||
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
FAQs
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<FaqAddModel refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
{isFetching && <Spinner />}
|
||||
{!isLoading && !data?.data ? (
|
||||
<NoData message={'No data found'} text={'Please add new data'} />
|
||||
) : (<DataTable
|
||||
sortableColumns={["Name"]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>)}
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default FAQ
|
||||
174
src/Pages/ManageCMS/FAQ/FaqAddModel.tsx
Normal file
174
src/Pages/ManageCMS/FAQ/FaqAddModel.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Box, Field, Input, Stack, Text, Textarea } from "@chakra-ui/react";
|
||||
import { IoMdAdd } from "react-icons/io";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { useState } from "react";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
import { useCreateFaqPostMutation } from "../../../Redux/Service/faqs.service";
|
||||
|
||||
function FaqAddModel({ refetch }: { refetch: VoidFunction }) {
|
||||
const [faqQuestion, setFaqQuestion] = useState('');
|
||||
const [faqAnswer, setFaqAnswer] = useState('');
|
||||
const [userType, setUserType] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createFaqPost, { isLoading }] = useCreateFaqPostMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (userType === "" || isNaN(Number(userType))) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please select a valid user type.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!faqQuestion.trim() || !faqAnswer.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please fill in all required fields",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
principal_type_xid: Number(userType),
|
||||
language_code: 'en',
|
||||
question: faqQuestion,
|
||||
answer: faqAnswer
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await createFaqPost(payload).unwrap();
|
||||
if (response) {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "FAQ updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update FAQ",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Add
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Select User Type</Field.Label>
|
||||
<Box bgColor="#EEEEEE" borderRadius="md" p={1} w={'100%'}>
|
||||
<select
|
||||
style={{
|
||||
width: "100%",
|
||||
background: "transparent",
|
||||
color: "black",
|
||||
border: "none",
|
||||
fontSize: "12px",
|
||||
height: "30px",
|
||||
outline: "none",
|
||||
}}
|
||||
value={userType}
|
||||
onChange={(e) => setUserType(e.target.value)}
|
||||
>
|
||||
<option value="" disabled>Select User Type</option>
|
||||
<option value="2">Recruiter</option>
|
||||
<option value="3">Jobseeker</option>
|
||||
</select>
|
||||
</Box>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Questions
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Questions"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
_focusVisible={{ outline: 'none' }}
|
||||
size={'sm'}
|
||||
value={faqQuestion}
|
||||
onChange={(e) => setFaqQuestion(e.target.value)}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Answer
|
||||
</Field.Label>
|
||||
<Textarea
|
||||
placeholder="Answer"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
p={2}
|
||||
fontSize="12px"
|
||||
height="120px"
|
||||
resize={'none'}
|
||||
_focusVisible={{ outline: 'none' }}
|
||||
value={faqAnswer}
|
||||
onChange={(e) => setFaqAnswer(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default FaqAddModel;
|
||||
@@ -1,12 +1,82 @@
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
|
||||
import { Badge, HStack, Stack, Text, VStack } from "@chakra-ui/react";
|
||||
import { useGetPrivacyPolicyQuery } from "../../../Redux/Service/privacy.policy.service";
|
||||
import MainFrame from "../../../components/MainFrame";
|
||||
import { Spinner } from "../../../components/Sipnner/Spinner";
|
||||
import PrivacyPolicyAddModel from "./PrivacyPolicyAddModel";
|
||||
import GlobalStateContext from "../../../Contexts/GlobalStateContext";
|
||||
import { useContext, useEffect } from "react";
|
||||
|
||||
const PrivacyPolicy = () => {
|
||||
return (
|
||||
|
||||
<MainFrame >
|
||||
PrivacyPolicy
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default PrivacyPolicy
|
||||
const { data, isLoading, isFetching, refetch } = useGetPrivacyPolicyQuery();
|
||||
|
||||
console.log("PRIVACY", data?.data);
|
||||
|
||||
const context = useContext(GlobalStateContext);
|
||||
if (!context)
|
||||
throw new Error("App must be used within a GlobalStateProvider");
|
||||
|
||||
const { setIsBarLoading } = context;
|
||||
useEffect(() => {
|
||||
setIsBarLoading(isFetching);
|
||||
}, [data]);
|
||||
|
||||
console.log("Privacy Policy Data:", data?.data);
|
||||
|
||||
return (
|
||||
<MainFrame transperant={true}>
|
||||
<Stack bg={"#fff"} w={"100%"} p={4} borderRadius={4} mb={4}>
|
||||
<Text color={"black"} textAlign={"left"} fontWeight={"600"}>
|
||||
Privacy Policy
|
||||
</Text>
|
||||
</Stack>
|
||||
<VStack gap={4} pb={4} pt={0}>
|
||||
{isLoading || isFetching ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
data?.data?.map(({ id, content, privacy_language }) => (
|
||||
<VStack
|
||||
bg={"#fff"}
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
rounded={"lg"}
|
||||
p={3}
|
||||
key={id}
|
||||
>
|
||||
<HStack w={"100%"} justifyContent={"space-between"} py={0} px={0}>
|
||||
<Text
|
||||
as={"span"}
|
||||
fontSize={"sm"}
|
||||
fontWeight={500}
|
||||
color={"#000"}
|
||||
>
|
||||
<Badge
|
||||
variant={"surface"}
|
||||
colorPalette="cyan"
|
||||
size={"sm"}
|
||||
fontSize={"xs"}
|
||||
px={2}
|
||||
>
|
||||
🎓 {privacy_language?.language_name}
|
||||
</Badge>
|
||||
</Text>
|
||||
|
||||
<PrivacyPolicyAddModel
|
||||
policyData={{ id, content, privacy_language }}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</HStack>
|
||||
<Text
|
||||
as="p"
|
||||
fontSize="sm"
|
||||
fontWeight={400}
|
||||
color="#1D1D1D"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
</VStack>
|
||||
))
|
||||
)}
|
||||
</VStack>
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivacyPolicy;
|
||||
|
||||
153
src/Pages/ManageCMS/PrivacyPolicy/PrivacyPolicyAddModel.tsx
Normal file
153
src/Pages/ManageCMS/PrivacyPolicy/PrivacyPolicyAddModel.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { FaRegEdit } from "react-icons/fa";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogRoot,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Box, Field, Stack, Text } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { useUpdatePrivacyPolicyMutation } from "../../../Redux/Service/privacy.policy.service";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useState } from "react";
|
||||
import ReactQuill from "react-quill";
|
||||
|
||||
function PrivacyPolicyAddModel({ policyData, refetch }: { policyData: any, refetch: VoidFunction }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [updatePrivacyPolicy, { isLoading }] = useUpdatePrivacyPolicyMutation()
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
content: "",
|
||||
languageCode: "",
|
||||
},
|
||||
});
|
||||
|
||||
console.log('POLICY', policyData);
|
||||
|
||||
const handleEditClick = (data: any) => {
|
||||
setValue("content", data.content); // Pre-fill the content field
|
||||
setValue("languageCode", data.privacy_language.language_code); // Pre-fill the language code
|
||||
setIsOpen(true); // Open dialog
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: any) => {
|
||||
if (!formData.content.trim()) return; // Prevent empty updates
|
||||
|
||||
try {
|
||||
await updatePrivacyPolicy({
|
||||
id: policyData.id,
|
||||
content: formData.content,
|
||||
language_code: formData.languageCode,
|
||||
}).unwrap();
|
||||
setIsOpen(false); // Close dialog on success
|
||||
reset(); // Reset the form
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Update failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<Box key={policyData.id}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
bgColor={"#EEEEEE"}
|
||||
pl={3} pr={3}
|
||||
size={"xs"}
|
||||
color={"#000"}
|
||||
onClick={() => handleEditClick(policyData)}
|
||||
>
|
||||
{" "}
|
||||
<FaRegEdit
|
||||
color="#000"
|
||||
style={{ height: "14px", width: "14px" }}
|
||||
/>{" "}
|
||||
<Text color={"#000"} mt={1}>
|
||||
Edit
|
||||
</Text>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</Box>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
minW={'600px'}
|
||||
// w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
{/* <DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader> */}
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
PrivacyPolicy
|
||||
</Field.Label>
|
||||
{/* <Textarea
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
p={2}
|
||||
fontSize="12px"
|
||||
height={'140px'}
|
||||
_focusVisible={{ outline: 'none' }}
|
||||
resize={'none'}
|
||||
/> */}
|
||||
<Controller
|
||||
name="content"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<ReactQuill
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter About Us content"
|
||||
modules={{
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
['clean']
|
||||
],
|
||||
}}
|
||||
formats={[
|
||||
'header',
|
||||
'bold', 'italic', 'underline', 'strike',
|
||||
'list', 'bullet',
|
||||
'link', 'image'
|
||||
]}
|
||||
style={{ color: "black", border: "none", fontSize: "12px", height: "220px", width: "100%",marginBottom:'20px' }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} mt={'4'} onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default PrivacyPolicyAddModel;
|
||||
15
src/Pages/ManageCMS/PrivacyPolicy/PrivacyPolicySkeleton.tsx
Normal file
15
src/Pages/ManageCMS/PrivacyPolicy/PrivacyPolicySkeleton.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Box, Skeleton } from '@chakra-ui/react'
|
||||
|
||||
const PrivacyPolicySkeleton = () => {
|
||||
return (
|
||||
<Box px={3} pb={3} w="90%">
|
||||
<Skeleton height="20px" mb="10px" width="90%" />
|
||||
<Skeleton height="20px" mb="15px" width="90%" />
|
||||
<Skeleton height="20px" mb="10px" width="90%" />
|
||||
<Skeleton height="20px" mb="10px" width="80%" />
|
||||
<Skeleton height="20px" width="60%" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrivacyPolicySkeleton
|
||||
@@ -1,12 +1,67 @@
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
|
||||
import { Badge, HStack, Spinner, Stack, Text, VStack } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame";
|
||||
import TermsAndConditionsAddModel from "./TermsAndConditionsAddModel";
|
||||
import { useGetTermsQuery } from "../../../Redux/Service/terms.and.condition.service";
|
||||
|
||||
const TermsAndConditions = () => {
|
||||
return (
|
||||
|
||||
<MainFrame >
|
||||
TermsAndConditions
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default TermsAndConditions
|
||||
const { data, refetch, isLoading, isFetching } = useGetTermsQuery();
|
||||
|
||||
console.log(data);
|
||||
|
||||
return (
|
||||
<MainFrame transperant={true}>
|
||||
<VStack gap={4} pb={4} pt={0}>
|
||||
<Stack bg={"#fff"} w={"100%"} mt={2} p={4} borderRadius={4}>
|
||||
<Text color={"black"} textAlign={"left"} fontWeight={"600"}>
|
||||
Terms and Conditions
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{isLoading || isFetching ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
data?.data?.map(({ id, content, terms_cond_language }) => (
|
||||
<VStack
|
||||
bg={"#fff"}
|
||||
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
|
||||
rounded={"lg"}
|
||||
p={3}
|
||||
key={id}
|
||||
>
|
||||
<HStack w={"100%"} justifyContent={"space-between"} py={0} px={0}>
|
||||
<Text
|
||||
as={"span"}
|
||||
fontSize={"sm"}
|
||||
fontWeight={500}
|
||||
color={"#000"}
|
||||
>
|
||||
<Badge
|
||||
variant={"surface"}
|
||||
colorPalette="cyan"
|
||||
ms={2}
|
||||
size={"sm"}
|
||||
fontSize={"xs"}
|
||||
px={2}
|
||||
>
|
||||
🎓 {terms_cond_language?.language_name}
|
||||
</Badge>
|
||||
</Text>
|
||||
|
||||
{/* Pass Data to AboutUsAddModel */}
|
||||
<TermsAndConditionsAddModel
|
||||
termsData={{ id, content, terms_cond_language }}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</HStack>
|
||||
<Text as="p" fontSize="sm" fontWeight={400} color="#1D1D1D">
|
||||
{/* {content} */}
|
||||
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||
</Text>
|
||||
</VStack>
|
||||
))
|
||||
)}
|
||||
</VStack>
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
export default TermsAndConditions;
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { FaRegEdit } from "react-icons/fa";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Field, Stack, Text } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import ReactQuill from "react-quill";
|
||||
import { useState } from "react";
|
||||
import { useUpdateTermsMutation } from "../../../Redux/Service/terms.and.condition.service";
|
||||
|
||||
function TermsAndConditionsAddModel({ termsData, refetch }: { termsData: any, refetch: VoidFunction }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [updateTerms, { isLoading }] = useUpdateTermsMutation()
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
content: "",
|
||||
languageCode: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleEditClick = (data: any) => {
|
||||
setValue("content", data.content); // Pre-fill the content field
|
||||
setValue("languageCode", data.terms_cond_language.language_code); // Pre-fill the language code
|
||||
setIsOpen(true); // Open dialog
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: any) => {
|
||||
if (!formData.content.trim()) return; // Prevent empty updates
|
||||
|
||||
try {
|
||||
await updateTerms({
|
||||
id: termsData.id,
|
||||
content: formData.content,
|
||||
language_code: formData.languageCode,
|
||||
}).unwrap();
|
||||
setIsOpen(false); // Close dialog on success
|
||||
reset(); // Reset the form
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Update failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
bgColor={"#EEEEEE"}
|
||||
pl={3} pr={3}
|
||||
size={"xs"}
|
||||
color={"#000"}
|
||||
onClick={() => handleEditClick(termsData)}
|
||||
>
|
||||
{" "}
|
||||
<FaRegEdit
|
||||
color="#000"
|
||||
style={{ height: "14px", width: "14px" }}
|
||||
/>{" "}
|
||||
<Text color={"#000"} mt={1}>
|
||||
Edit
|
||||
</Text>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
maxW={'600px'}
|
||||
// w={{ base: "90%", md: "600px" }}
|
||||
height={"auto"}
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
TermsAndConditions
|
||||
</Field.Label>
|
||||
{/* <Textarea
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
p={2}
|
||||
fontSize="12px"
|
||||
height={'140px'}
|
||||
_focusVisible={{outline:'none'}}
|
||||
resize={'none'}
|
||||
/> */}
|
||||
<Controller
|
||||
name="content"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<ReactQuill
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter About Us content"
|
||||
modules={{
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
['clean']
|
||||
],
|
||||
}}
|
||||
formats={[
|
||||
'header',
|
||||
'bold', 'italic', 'underline', 'strike',
|
||||
'list', 'bullet',
|
||||
'link', 'image'
|
||||
]}
|
||||
style={{ color: "black", border: "none", fontSize: "12px", height: "220px", width: "100%",marginBottom:'20px' }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} mt={'4'} onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default TermsAndConditionsAddModel;
|
||||
@@ -1,11 +1,136 @@
|
||||
import MainFrame from "../../components/MainFrame"
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../components/MainFrame";
|
||||
import PendingRequests from "../../Pages/ManageContact/PendingRequests";
|
||||
// import { InputGroup } from "../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../components/DataTable";
|
||||
import { useGetContactQuery } from "../../Redux/Service/manage.contactus.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Spinner } from "../../components/Sipnner/Spinner";
|
||||
import { useDebounce } from "../../components/Hooks/useDebounce";
|
||||
import SearchComponent from "../../components/SearchComponent";
|
||||
|
||||
// table data
|
||||
const tableHeadRow = ["Sr. No", "Email id", "Name", "Date", "Action"];
|
||||
|
||||
const ManageContact = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, isLoading, isError, refetch, isFetching } = useGetContactQuery(queryArgs);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setLocalData((data as any)?.data?.data || []);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) =>
|
||||
agency?.first_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agency?.email.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const formatDateOfBirth = (dob: string): string => {
|
||||
return new Date(dob).toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const managepost = filteredData?.map((agency: any, index: number) => ({
|
||||
"Sr. No": index + 1, // Typically Sr. No starts from 1, not using id which might not be sequential
|
||||
"Email id": agency?.email || "-",
|
||||
Name: agency?.first_name || "-",
|
||||
Date: formatDateOfBirth(agency?.created_at) || "-",
|
||||
Action: (
|
||||
<HStack justifyContent="center" cursor={agency.response_status === 0 ? 'pointer' : 'default'}>
|
||||
{agency.response_status === 0 ? <PendingRequests data={agency} refetch={refetch} /> : 'Resolved'}
|
||||
{/* <PendingRequests data={agency} refetch={refetch} /> */}
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Spinner />
|
||||
</Box>
|
||||
</MainFrame>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Text>Error loading data</Text>
|
||||
</Box>
|
||||
</MainFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame >
|
||||
ManageContact
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Contact Requests
|
||||
</Text>
|
||||
|
||||
<HStack>
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost || []} // Ensure an empty array is passed if managepost is undefined
|
||||
paginationData={{
|
||||
current_page: (data?.data as any)?.current_page || 1,
|
||||
last_page: (data?.data as any)?.last_page || 1,
|
||||
per_page: (data?.data as any)?.per_page || 10,
|
||||
total: (data?.data as any)?.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default ManageContact
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageContact;
|
||||
|
||||
170
src/Pages/ManageContact/PendingRequests.tsx
Normal file
170
src/Pages/ManageContact/PendingRequests.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { Badge, Field, HStack, Input, Stack, Textarea } from "@chakra-ui/react";
|
||||
import { usePendingRequestMutation } from "../../Redux/Service/manage.contactus.service";
|
||||
import { useState } from "react";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
|
||||
function PendingRequests({ data, refetch }: { data: any, refetch: VoidFunction }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [res, setRes] = useState({
|
||||
contact_us_xid: data.id,
|
||||
message: data.message,
|
||||
solution: '',
|
||||
})
|
||||
const [pendingRequest, { isLoading }] = usePendingRequestMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async (status: string) => {
|
||||
const payload = { ...res, response_status: status };
|
||||
|
||||
if(res.solution === ''){
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "All fields are required",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await pendingRequest(payload).unwrap()
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Country updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
setIsOpen(false);
|
||||
refetch()
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update Country",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: error?.data?.message || "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRoot placement="center" key={data.id} open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Badge fontSize={"xs"} px={2} bg={'#02a0a01f'} onClick={handleOpenModal}>
|
||||
Answer request
|
||||
</Badge>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Pending Requests
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Request Type
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Message"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={res.message}
|
||||
disabled
|
||||
// onChange={(e) => setRes({ ...res, message: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Solution
|
||||
</Field.Label>
|
||||
<Textarea
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="80px"
|
||||
pt={1.5}
|
||||
onChange={(e) => setRes({ ...res, solution: e.target.value })}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter
|
||||
display={{ base: "block", md: "flex" }}
|
||||
justifyContent="center"
|
||||
gap={1}
|
||||
pt={2}
|
||||
>
|
||||
<HStack mt={2} mb={3} width={"100%"} justifyContent={"space-between"}>
|
||||
<Button
|
||||
width={"48%"}
|
||||
color="black"
|
||||
_hover={{ bgColor: "white" }}
|
||||
variant="outline"
|
||||
borderRadius="sm"
|
||||
border="1px solid #02A0A0"
|
||||
size={"xs"}
|
||||
onClick={() => handleSubmit('1')}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Unresolved
|
||||
</Button>
|
||||
<Button
|
||||
width={"48%"}
|
||||
borderRadius="sm"
|
||||
// bgColor="#007F33"
|
||||
bgColor={"#02A0A0"}
|
||||
color="white"
|
||||
// colorPalette="#007F33"
|
||||
size={"xs"}
|
||||
onClick={() => handleSubmit('0')}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Resolved
|
||||
</Button>
|
||||
</HStack>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PendingRequests;
|
||||
58
src/Pages/ManageGroups/AddGroup.tsx
Normal file
58
src/Pages/ManageGroups/AddGroup.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Button } from "../../components/ui/button"
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"
|
||||
import { Field, Heading, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { Switch } from "../../components/ui/switch";
|
||||
import { IoMdAdd } from "react-icons/io";
|
||||
function AddGroup() {
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" >
|
||||
<DialogTrigger asChild>
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"}><IoMdAdd /> <Text>Add</Text></Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={"auto"}
|
||||
// overflow={'scroll'}
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3} >
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="xs">Group Name</Field.Label>
|
||||
<Input bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" />
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="xs">Group Introduction</Field.Label>
|
||||
<Input bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" />
|
||||
</Field.Root>
|
||||
|
||||
<Button w="30%" size={'xs'} bg="#02A0A0" mt={3} color={"#fff"}>
|
||||
Add Member
|
||||
</Button>
|
||||
<Heading color="black" pt={1} fontSize="12px">public/Private</Heading>
|
||||
<Switch />
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AddGroup
|
||||
119
src/Pages/ManageGroups/EditDetailGroup.tsx
Normal file
119
src/Pages/ManageGroups/EditDetailGroup.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { Avatar, Box, Field, Heading, Input, Span, Stack } from "@chakra-ui/react";
|
||||
import { Switch } from "../../components/ui/switch";
|
||||
import { AvatarGroup } from "../../components/ui/avatar";
|
||||
import Edit from "../../components/ActionIcons/Edit";
|
||||
function EditDetailGroups() {
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Span><Edit /></Span>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
p={3} // Reduced padding
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit details
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Group Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
value="Priyanka"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Description
|
||||
</Field.Label>
|
||||
<Input
|
||||
value="Joshi"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Members
|
||||
</Field.Label>
|
||||
|
||||
<Box
|
||||
bgColor="#EEEEEE"
|
||||
border="none"
|
||||
w="100%"
|
||||
display="flex"
|
||||
p={1}
|
||||
rounded={4}
|
||||
>
|
||||
<AvatarGroup gap="0" spaceX="-3" size={"xs"}>
|
||||
<Avatar.Root border={"none"}>
|
||||
<Avatar.Fallback />
|
||||
<Avatar.Image src="https://cdn.myanimelist.net/r/84x124/images/characters/9/131317.webp?s=d4b03c7291407bde303bc0758047f6bd" />
|
||||
</Avatar.Root>
|
||||
|
||||
<Avatar.Root border={"none"}>
|
||||
<Avatar.Fallback />
|
||||
<Avatar.Image src="https://cdn.myanimelist.net/r/84x124/images/characters/7/284129.webp?s=a8998bf668767de58b33740886ca571c" />
|
||||
</Avatar.Root>
|
||||
|
||||
<Avatar.Root border={"none"}>
|
||||
<Avatar.Fallback />
|
||||
<Avatar.Image src="https://cdn.myanimelist.net/r/84x124/images/characters/9/105421.webp?s=269ff1b2bb9abe3ac1bc443d3a76e863" />
|
||||
</Avatar.Root>
|
||||
<Avatar.Root
|
||||
variant="solid"
|
||||
border={"none"}
|
||||
backgroundColor={"transparent"}
|
||||
>
|
||||
<Avatar.Fallback ml={5}>+3</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</AvatarGroup>
|
||||
</Box>
|
||||
</Field.Root>
|
||||
<Heading color="black" pt={1} fontSize="12px">
|
||||
public/Private
|
||||
</Heading>
|
||||
<Switch />
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button size={"xs"} w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>{" "}
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditDetailGroups;
|
||||
@@ -1,11 +1,107 @@
|
||||
import MainFrame from "../../components/MainFrame"
|
||||
import { Box, HStack,
|
||||
// Image,
|
||||
Input, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../components/MainFrame";
|
||||
import { InputGroup } from "../../components/ui/input-group";
|
||||
import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../components/DataTable";
|
||||
// import AlertDailog from "../../components/AlertDailog";
|
||||
// import { RiDeleteBin5Line } from "react-icons/ri";
|
||||
import ViewManageGroup from "./ViewManageGroup";
|
||||
import EditDetailGroups from "./EditDetailGroup";
|
||||
import AddGroup from "./AddGroup";
|
||||
// import Delete from "../../components/ActionIcons/Delete";
|
||||
// import ViewSubAdmin from "./ViewSubAdmin"
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Group Name",
|
||||
"Description",
|
||||
"Date",
|
||||
"Group type",
|
||||
"Action",
|
||||
];
|
||||
|
||||
const managepost: any[] = [
|
||||
...Array.from({ length: 12 }, (_, i) => ({
|
||||
"Sr. No": i + 1,
|
||||
"Group Name": "ABC",
|
||||
Description: "Lorem ipsum",
|
||||
Date: "12/01/1987",
|
||||
"Group type": "Private",
|
||||
Action: (
|
||||
<HStack justifyContent="center">
|
||||
<ViewManageGroup />
|
||||
<EditDetailGroups />
|
||||
{/* <AlertDailog
|
||||
AltertTiggerIcon={() => <Delete />}
|
||||
alertText="Delete Users"
|
||||
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
alertCaption="are you sure you want to delete ?"
|
||||
onConfirm={() => {
|
||||
console.log("User deleted:", i + 1);
|
||||
}}
|
||||
/> */}
|
||||
</HStack>
|
||||
),
|
||||
})),
|
||||
];
|
||||
|
||||
|
||||
|
||||
const ManageGroups = () => {
|
||||
return (
|
||||
<MainFrame >
|
||||
ManageGroups
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Manage Groups
|
||||
</Text>
|
||||
|
||||
export default ManageGroups
|
||||
<HStack>
|
||||
<InputGroup
|
||||
startElement={
|
||||
<LuSearch
|
||||
fontSize={"xs"}
|
||||
style={{ position: "relative", left: "10px" }}
|
||||
/>
|
||||
}
|
||||
color={"#000"}
|
||||
>
|
||||
<Input
|
||||
p={3}
|
||||
w={300}
|
||||
bg={"#fff"}
|
||||
colorPalette={"blue"}
|
||||
_focus={{ border: "1px solid #02A0A0" }}
|
||||
rounded={"md"}
|
||||
size={"xs"}
|
||||
fontSize={"sm"}
|
||||
placeholder="Search..."
|
||||
bgColor={"#EEEEEE"}
|
||||
ps={8}
|
||||
border={"none"}
|
||||
/>
|
||||
</InputGroup>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<AddGroup />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
/>
|
||||
</Box>{" "}
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
export default ManageGroups;
|
||||
|
||||
116
src/Pages/ManageGroups/ViewManageGroup.tsx
Normal file
116
src/Pages/ManageGroups/ViewManageGroup.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { Avatar, Box, Field, Heading, Input, Span, Stack } from "@chakra-ui/react";
|
||||
import { Switch } from "../../components/ui/switch";
|
||||
import { AvatarGroup } from "../../components/ui/avatar";
|
||||
import View from "../../components/ActionIcons/View";
|
||||
function ViewManageGroup() {
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Span><View /></Span>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
View details
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Group Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
value="Priyanka"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Description
|
||||
</Field.Label>
|
||||
<Input
|
||||
value="Joshi"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Members
|
||||
</Field.Label>
|
||||
|
||||
<Box
|
||||
bgColor="#EEEEEE"
|
||||
border="none"
|
||||
w="100%"
|
||||
display="flex"
|
||||
p={1}
|
||||
rounded={4}
|
||||
>
|
||||
<AvatarGroup gap="0" spaceX="-3" size={"xs"}>
|
||||
<Avatar.Root border={"none"}>
|
||||
<Avatar.Fallback />
|
||||
<Avatar.Image src="https://cdn.myanimelist.net/r/84x124/images/characters/9/131317.webp?s=d4b03c7291407bde303bc0758047f6bd" />
|
||||
</Avatar.Root>
|
||||
|
||||
<Avatar.Root border={"none"}>
|
||||
<Avatar.Fallback />
|
||||
<Avatar.Image src="https://cdn.myanimelist.net/r/84x124/images/characters/7/284129.webp?s=a8998bf668767de58b33740886ca571c" />
|
||||
</Avatar.Root>
|
||||
|
||||
<Avatar.Root border={"none"}>
|
||||
<Avatar.Fallback />
|
||||
<Avatar.Image src="https://cdn.myanimelist.net/r/84x124/images/characters/9/105421.webp?s=269ff1b2bb9abe3ac1bc443d3a76e863" />
|
||||
</Avatar.Root>
|
||||
<Avatar.Root
|
||||
variant="solid"
|
||||
border={"none"}
|
||||
backgroundColor={"transparent"}
|
||||
>
|
||||
<Avatar.Fallback ml={5}>+3</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</AvatarGroup>
|
||||
</Box>
|
||||
</Field.Root>
|
||||
<Heading color="black" pt={1} fontSize="12px">
|
||||
public/Private
|
||||
</Heading>
|
||||
<Switch />
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewManageGroup;
|
||||
@@ -1,12 +1,184 @@
|
||||
import MainFrame from "../../components/MainFrame"
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
Image,
|
||||
// Image,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
// import { RiDeleteBin5Line } from "react-icons/ri";
|
||||
// import AlertDailog from "../../components/AlertDailog";
|
||||
import DataTable from "../../components/DataTable";
|
||||
import MainFrame from "../../components/MainFrame";
|
||||
// import { InputGroup } from "../../components/ui/input-group";
|
||||
import ManageJobsAdd from "./ManageJobsAdd";
|
||||
import ViewManageJob from "./ViewManageJob";
|
||||
import {
|
||||
useDeleteJobsPostMutation,
|
||||
useGetManageJobsQuery,
|
||||
} from "../../Redux/Service/manage.jobs.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Spinner } from "../../components/Sipnner/Spinner";
|
||||
import Delete from "../../components/ActionIcons/Delete";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
import AlertDailog from "../../components/AlertDailog";
|
||||
import { useDebounce } from "../../components/Hooks/useDebounce";
|
||||
import SearchComponent from "../../components/SearchComponent";
|
||||
// import { useState } from "react";
|
||||
// import { useGetManageJobsQuery } from "../../Redux/Service/manage.jobs.service";
|
||||
// import Delete from "../../components/ActionIcons/Delete";
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Job Title",
|
||||
"Workspace mode",
|
||||
"Category",
|
||||
"Sub-category",
|
||||
"Salary",
|
||||
"Action",
|
||||
];
|
||||
|
||||
const ManageJobs = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [localData, setLocalData] = useState([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isLoading, isError, isFetching } = useGetManageJobsQuery(queryArgs);
|
||||
const [deleteJobsPost] = useDeleteJobsPostMutation();
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const [selectedJobsId, setSelectedJobsId] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setLocalData((data as any)?.data?.data || []);
|
||||
}
|
||||
}, [data]);
|
||||
// console.log(data?.data.data);
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleDeleteJobs = async (jobsId: number) => {
|
||||
try {
|
||||
const response = await deleteJobsPost({ id: jobsId }).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Jobs deleted successfully",
|
||||
type: "Success",
|
||||
});
|
||||
refetch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting FAQ:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const managepost = localData?.map((agency: any, index: number) => ({
|
||||
"Sr. No": index + 1,
|
||||
"Job Title": agency?.job_title,
|
||||
"Workspace mode": agency?.workspace?.en_name,
|
||||
Category: agency?.industry?.en_name,
|
||||
"Sub-category": agency?.department?.en_name,
|
||||
Salary: agency?.ctc_amount,
|
||||
Action: (
|
||||
<HStack justifyContent="center">
|
||||
<ViewManageJob data={agency} />
|
||||
<ManageJobsAdd data={agency} refetch={refetch}/>
|
||||
<AlertDailog
|
||||
isOpen={deleteModal}
|
||||
AltertTiggerIcon={() => (
|
||||
<Delete
|
||||
onClick={() => {
|
||||
setSelectedJobsId(agency.id);
|
||||
setDeleteModal(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
alertText="Delete FAQ"
|
||||
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
alertCaption="are you sure you want to delete ?"
|
||||
onClose={() => setDeleteModal(false)}
|
||||
onConfirm={() => {
|
||||
// console.log("Deleting FAQ with ID:", selectedFaqId); // Correct ID
|
||||
if (selectedJobsId) {
|
||||
setDeleteModal(false);
|
||||
handleDeleteJobs(selectedJobsId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
ManageJobs
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Spinner />
|
||||
</Box>
|
||||
</MainFrame>
|
||||
)
|
||||
);
|
||||
}
|
||||
export default ManageJobs
|
||||
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
View job Posting
|
||||
</Text>
|
||||
|
||||
<HStack>
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
export default ManageJobs;
|
||||
|
||||
505
src/Pages/ManageJobs/ManageJobsAdd.tsx
Normal file
505
src/Pages/ManageJobs/ManageJobsAdd.tsx
Normal file
@@ -0,0 +1,505 @@
|
||||
import {
|
||||
Field,
|
||||
Input,
|
||||
Span,
|
||||
Stack,
|
||||
} from "@chakra-ui/react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
|
||||
// import { TbEdit } from "react-icons/tb";
|
||||
import Edit from "../../components/ActionIcons/Edit";
|
||||
import { JobStatusData, useGetCountryQuery, useGetDepartmentQuery, useGetIndustryQuery, useGetManageJobTypeQuery, useGetWorkspaceModesQuery, useUpdateJobsMutation, WorkSpace } from "../../Redux/Service/manage.jobs.service";
|
||||
import { useReducer, useRef } from "react";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
|
||||
const reducerFunction = (state: any, action: any) => {
|
||||
switch (action.type) {
|
||||
case "SET_JOB_TITLE":
|
||||
return { ...state, jobTitle: action.payload };
|
||||
case "SET_WORKSPACE_MODE":
|
||||
return { ...state, workspaceMode: action.payload };
|
||||
case "SET_CATEGORY":
|
||||
return { ...state, category: action.payload };
|
||||
case "SET_SUB_CATEGORY":
|
||||
return { ...state, subCategory: action.payload };
|
||||
case "SET_SALARY":
|
||||
return { ...state, salary: action.payload };
|
||||
case "SET_EXPERIENCE":
|
||||
return { ...state, experience: action.payload };
|
||||
case "SET_JOB_LOCATION":
|
||||
return { ...state, jobLocation: action.payload };
|
||||
case "SET_COUNTRY_SELECTION":
|
||||
return { ...state, countrySelection: action.payload };
|
||||
case "SET_JOB_TYPE":
|
||||
return { ...state, jobType: action.payload };
|
||||
case "SET_SKILLS_REQUIRED":
|
||||
return { ...state, skillsRequired: action.payload };
|
||||
case "SET_JOB_DESCRIPTION":
|
||||
return { ...state, jobDescription: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
function ManageJobsAdd({ data, refetch }: { data: JobStatusData, refetch: () => void }) {
|
||||
const { data: workspaceModes } = useGetWorkspaceModesQuery({});
|
||||
const { data: industryData } = useGetIndustryQuery({});
|
||||
const { data: departmentData } = useGetDepartmentQuery({});
|
||||
const { data: countryData } = useGetCountryQuery({});
|
||||
const { data: jobTypeData } = useGetManageJobTypeQuery({});
|
||||
const [updateJobs, { isLoading}] = useUpdateJobsMutation();
|
||||
// console.log('Modes:', jobTypeData?.data.data);
|
||||
|
||||
const initialState = {
|
||||
jobTitle: data?.job_title || "",
|
||||
workspaceMode: data?.workspace_mode_xid || "",
|
||||
category: data?.industry_xid || "",
|
||||
subCategory: data?.department_xid || "",
|
||||
salary: data?.ctc_amount || "",
|
||||
experience: data?.experience || "",
|
||||
jobLocation: data?.job_location || "",
|
||||
countrySelection: data?.country_xid || "",
|
||||
jobType: data?.job_type_xid || "",
|
||||
skillsDescription: data?.skill_description || "",
|
||||
jobDescription: data?.job_description || "",
|
||||
};
|
||||
|
||||
const [state, dispatch] = useReducer(reducerFunction, initialState);
|
||||
const closeRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const getDisplayName = (name: string, maxLength = 30) =>
|
||||
name.length > maxLength ? name.slice(0, maxLength) + "..." : name;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const {
|
||||
jobTitle,
|
||||
workspaceMode,
|
||||
category,
|
||||
subCategory,
|
||||
salary,
|
||||
experience,
|
||||
jobLocation,
|
||||
countrySelection,
|
||||
jobType,
|
||||
skillsDescription,
|
||||
jobDescription,
|
||||
} = state;
|
||||
|
||||
if (
|
||||
!jobTitle ||
|
||||
!workspaceMode ||
|
||||
!category ||
|
||||
!subCategory ||
|
||||
!salary ||
|
||||
!experience ||
|
||||
!jobLocation ||
|
||||
!countrySelection ||
|
||||
!jobType ||
|
||||
!skillsDescription ||
|
||||
!jobDescription
|
||||
) {
|
||||
toaster.create({
|
||||
title: "Missing Fields",
|
||||
description: "Please fill in all required fields before submitting.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Only en_name is editable, so we only need to send that in the payload.
|
||||
const payload = {
|
||||
id: data?.id,
|
||||
job_title: state.jobTitle,
|
||||
workspace_mode_xid: state.workspaceMode,
|
||||
industry_xid: state.category,
|
||||
department_xid: state.subCategory,
|
||||
ctc_amount: state.salary,
|
||||
experience: state.experience,
|
||||
job_location: state.jobLocation,
|
||||
country_xid: state.countrySelection,
|
||||
job_type_xid: state.jobType,
|
||||
skill_description: state.skillsDescription,
|
||||
job_description: state.jobDescription,
|
||||
};
|
||||
|
||||
// console.log('payload', payload)
|
||||
|
||||
try {
|
||||
const response = await updateJobs(payload).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Country updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
closeRef.current?.click();
|
||||
refetch()
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update Country",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Span><Edit /></Span>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Add Details
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Job title
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Title"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={state.jobTitle}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_JOB_TITLE",
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Workspace mode
|
||||
</Field.Label>
|
||||
<select
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
value={state.workspace_mode_xid}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_WORKSPACE_MODE",
|
||||
payload: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select country
|
||||
</option>
|
||||
{workspaceModes && workspaceModes?.data.map((mode: WorkSpace) => (
|
||||
<option key={mode.id} value={mode.id}>
|
||||
{getDisplayName(mode.en_name)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Category
|
||||
</Field.Label>
|
||||
<select
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
value={state.industry_xid}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_INDUSTRY",
|
||||
payload: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select department
|
||||
</option>
|
||||
{industryData && industryData?.data.map((mode: WorkSpace) => (
|
||||
<option key={mode.id} value={mode.id}>
|
||||
{getDisplayName(mode.en_name)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Sub-Category
|
||||
</Field.Label>
|
||||
<select
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
value={state.department_xid}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_DEPARTMENT",
|
||||
payload: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select department
|
||||
</option>
|
||||
{departmentData && departmentData?.data.map((mode: WorkSpace) => (
|
||||
<option key={mode.id} value={mode.id}>
|
||||
{getDisplayName(mode.en_name)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Country
|
||||
</Field.Label>
|
||||
<select
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
value={state.country_xid}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_COUNTRY",
|
||||
payload: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select country
|
||||
</option>
|
||||
{countryData && countryData?.data.map((mode: WorkSpace) => (
|
||||
<option key={mode.id} value={mode.id}>
|
||||
{getDisplayName(mode.en_name)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Job type
|
||||
</Field.Label>
|
||||
<select
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
value={state.job_type_xid}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_JOB_TYPE",
|
||||
payload: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select Job Type
|
||||
</option>
|
||||
{jobTypeData && jobTypeData?.data.data.map((mode: WorkSpace) => (
|
||||
<option key={mode.id} value={mode.id}>
|
||||
{getDisplayName(mode.en_name)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Salary
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Salary"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={state.salary}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_SALARY",
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Experience
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Experience in years"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={state.experience}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_EXPERIENCE",
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Job Location
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Location"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={state.jobLocation}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_JOB_LOCATION",
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Skills description
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Skills description"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={state.skillsDescription}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_SKILLS_DESCRIPTION",
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Job Description*
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Description"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={state.jobDescription}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_JOB_DESCRIPTION",
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color={"#fff"}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" ref={closeRef} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManageJobsAdd;
|
||||
307
src/Pages/ManageJobs/ViewManageJob.tsx
Normal file
307
src/Pages/ManageJobs/ViewManageJob.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
import {
|
||||
Field,
|
||||
Input,
|
||||
Stack,
|
||||
} from "@chakra-ui/react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
|
||||
// import { MdOutlineRemoveRedEye } from "react-icons/md";
|
||||
import View from "../../components/ActionIcons/View";
|
||||
import { JobStatusData, useLazyViewJobsQuery } from "../../Redux/Service/manage.jobs.service";
|
||||
|
||||
function ViewManageJob({ data }: { data: JobStatusData }) {
|
||||
const [trigger] = useLazyViewJobsQuery();
|
||||
|
||||
console.log(data);
|
||||
|
||||
const handleView = () => {
|
||||
trigger(data.id);
|
||||
};
|
||||
|
||||
// const viewJobs = data;
|
||||
|
||||
console.log();
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={handleView}
|
||||
bg={'transparent'}
|
||||
color={"black"}
|
||||
>
|
||||
<View />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
{/* {viewJobs?.map((data: any) => ( */}
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Add Details
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Job title
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Title"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.job_title}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Workspace mode
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Workspace Mode"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.workspace?.en_name}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Category
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Category"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.industry?.en_name}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Sub-Category
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Sub-Category"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.department?.en_name}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Salary
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Salary"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.ctc_amount}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Experience
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Experience"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.experience}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Job Location
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Location"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.job_location}
|
||||
/>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Country
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Country"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.country.en_name}
|
||||
/>
|
||||
</Field.Root>
|
||||
|
||||
{/* <SelectRoot collection={frameworks} size="sm" w={"100%"}>
|
||||
<SelectLabel pt={1} color="black" fontSize="12px">
|
||||
Country Selection
|
||||
</SelectLabel>
|
||||
<SelectTrigger
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
borderRadius={"5px"}
|
||||
>
|
||||
<SelectValueText
|
||||
placeholder="Enter the Country Selection"
|
||||
pb={"6px"}
|
||||
fontSize={"12px"}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent
|
||||
position={"relative"}
|
||||
zIndex={"9999"}
|
||||
bg={"#fff"}
|
||||
>
|
||||
{frameworks.items.map((movie) => (
|
||||
<SelectItem
|
||||
item={movie}
|
||||
key={movie.value}
|
||||
color={"black"}
|
||||
pl={2}
|
||||
p={1}
|
||||
_hover={{ bg: "#F0F0F0" }} // Light grey background on hover
|
||||
fontSize="12px"
|
||||
>
|
||||
{movie.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</SelectRoot> */}
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Job type
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Type"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.job_type?.en_name}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Skills required
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Skills Required"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.skill_description}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Job Description*
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Job Description"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.job_description}
|
||||
/>
|
||||
</Field.Root>
|
||||
{/* <Field.Root>
|
||||
<Field.Label pt={1} color="black" fontSize="12px">
|
||||
Upload Image
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Upload Image"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
/>
|
||||
</Field.Root> */}
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
{/* <DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color={"#fff"}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter> */}
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
{/* ))} */}
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewManageJob;
|
||||
@@ -1,12 +1,254 @@
|
||||
import {
|
||||
Box, HStack, Icon, Image,
|
||||
// Span,
|
||||
Text
|
||||
} from "@chakra-ui/react";
|
||||
import MainFrame from "../../components/MainFrame";
|
||||
// import { InputGroup } from "../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../components/DataTable";
|
||||
// import AlertDailog from "../../components/AlertDailog";
|
||||
import { Switch } from "../../components/ui/switch";
|
||||
// import img from "../../assets/waterfall.jpg";
|
||||
// import { RiDeleteBin5Line } from "react-icons/ri";
|
||||
import ViewDailog from "./ViewDailog";
|
||||
import { useGetManagePostsQuery, usePostStatusToggleMutation } from "../../Redux/Service/manage.post.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
import { FaVideo } from "react-icons/fa";
|
||||
import SearchComponent from "../../components/SearchComponent";
|
||||
import { delay } from "../../components/Utils";
|
||||
// import Delete from "../../components/ActionIcons/Delete";
|
||||
// import ViewDailog from './ViewDailog'
|
||||
|
||||
import MainFrame from '../../components/MainFrame'
|
||||
const APIURL = import.meta.env.VITE_POST_IMG
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Images",
|
||||
"Description",
|
||||
"Publish Data",
|
||||
// "Activate/Deactivate",
|
||||
"Action",
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// Images: (
|
||||
// // <Image w={50} src={img} />
|
||||
// <Image rounded={"lg"} w={100} h={50} src={img} />
|
||||
// ),
|
||||
// Description: (
|
||||
// <Text>
|
||||
// {`Lorem ipsum dolor, sit amet consectetur adipisicing elit.}`.slice(
|
||||
// 0,
|
||||
// 30
|
||||
// ) + "..."}
|
||||
// </Text>
|
||||
// ),
|
||||
// "Publish Data": "12/01/2025",
|
||||
// "Activate/Deactivate": (
|
||||
// <Box w={"100%"}>
|
||||
// <Switch size={"sm"} colorPalette={"teal"} />
|
||||
// </Box>
|
||||
// ),
|
||||
// Action: (
|
||||
// <HStack justifyContent="center">
|
||||
// <ViewDailog />
|
||||
// {/* <AlertDailog
|
||||
// AltertTiggerIcon={() => <Span><Delete /> </Span>}
|
||||
// alertText="Delete Users"
|
||||
// alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
// alertCaption="are you sure you want to delete ?"
|
||||
// onConfirm={() => {
|
||||
// console.log("User deleted:", i + 1);
|
||||
// }}
|
||||
// /> */}
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const ManagePost = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, refetch, isFetching, isError } = useGetManagePostsQuery(currentPage)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [postStatusToggle] = usePostStatusToggleMutation()
|
||||
console.log('POSTS', data?.data.data);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await postStatusToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Someting went wrong.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function formatAPIDate(apiDateString: any) {
|
||||
const date = new Date(apiDateString);
|
||||
|
||||
// Get month, day, and year
|
||||
const month = date.getMonth() + 1; // Months are 0-indexed
|
||||
const day = date.getDate();
|
||||
const year = date.getFullYear();
|
||||
|
||||
// Pad with leading zeros if needed
|
||||
const formattedMonth = month.toString().padStart(2, '0');
|
||||
const formattedDay = day.toString().padStart(2, '0');
|
||||
|
||||
return `${formattedMonth}/${formattedDay}/${year}`;
|
||||
}
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
return (agency.post_content_translation.some((item: any) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const title = item.content?.toLowerCase().includes(searchLower);
|
||||
return title;
|
||||
}))
|
||||
});
|
||||
|
||||
const managepost = filteredData?.flatMap((agency: any, index: number) => (agency.post_content_translation.map((translation: any) => ({
|
||||
'id': agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
Images: (
|
||||
agency.images.length > 0 ?
|
||||
agency.images[0].type === "image" ? (
|
||||
<HStack>
|
||||
<Image
|
||||
rounded={"lg"}
|
||||
w={100}
|
||||
h={50}
|
||||
src={`${APIURL}${agency.images[0].image_name}`}
|
||||
/>
|
||||
<Text fontSize="xs" color={'lightgray'}>
|
||||
{`${Number(agency.images.length) > 1 ? '+' + (Number(agency.images.length) - 1) : ''}`}
|
||||
</Text>
|
||||
</HStack>
|
||||
) : (
|
||||
<HStack>
|
||||
<Box
|
||||
rounded={"lg"}
|
||||
w={100}
|
||||
h={50}
|
||||
bg="gray.200"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Icon as={FaVideo} color="gray.500" />
|
||||
</Box>
|
||||
<Text fontSize="xs" color={'lightgray'}>
|
||||
{`${Number(agency.images.length) > 1 ? '+' + (Number(agency.images.length) - 1) : ''}`}
|
||||
</Text>
|
||||
</HStack>
|
||||
) : ''
|
||||
// <Image rounded={"lg"} w={100} h={50} src={img} />
|
||||
),
|
||||
Description: (
|
||||
<Text>
|
||||
{translation?.content?.length > 30
|
||||
? `${translation.content.slice(0, 30)}...`
|
||||
: translation?.content}
|
||||
</Text>
|
||||
),
|
||||
"Publish Data": formatAPIDate(agency.created_at),
|
||||
"is_active": agency.is_active,
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
{/* <ViewAgencyMaster agency={localData} id={agency.id} /> */}
|
||||
<ViewDailog localData={{ ...agency, translation }} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={"teal"}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id, Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}))));
|
||||
|
||||
|
||||
return (
|
||||
<MainFrame>
|
||||
ManagePost
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
{/* Manage Post */}
|
||||
</Text>
|
||||
|
||||
export default ManagePost
|
||||
<HStack>
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={(value) => {
|
||||
setSearchTerm(value);
|
||||
// setCurrentPage(1);
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagePost;
|
||||
|
||||
85
src/Pages/ManagePost/ViewDailog.tsx
Normal file
85
src/Pages/ManagePost/ViewDailog.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Field, HStack, Image, Input, Stack } from "@chakra-ui/react"
|
||||
// import { TbEdit } from "react-icons/tb"
|
||||
// import img from "../../assets/waterfall.jpg"
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"
|
||||
// import Edit from "../../components/ActionIcons/Edit"
|
||||
import { LuEye } from "react-icons/lu"
|
||||
|
||||
const APIURL = import.meta.env.VITE_POST_IMG
|
||||
|
||||
function ViewDailog({ localData }: { localData: any, refetch: VoidFunction }) {
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
{/* <Span><Edit /></Span> */}
|
||||
<LuEye fontSize={"xm"} cursor={'pointer'} />
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
bg={"#fff"}
|
||||
h="auto"
|
||||
p={3} // Reduced padding
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">View Details</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3} >
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Description</Field.Label>
|
||||
<Input
|
||||
// placeholder="Enter the Title"
|
||||
value={localData.translation.content}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">Subtitle</Field.Label>
|
||||
<Input placeholder="Enter the Subtitle" bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" readOnly /> */}
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">Description</Field.Label>
|
||||
<Input placeholder="Enter the Description" bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" readOnly /> */}
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Image</Field.Label>
|
||||
<HStack w="fit-content" flexWrap={'wrap'}>
|
||||
{localData.images.map((img: any) => (
|
||||
img.type === 'image' ? (
|
||||
<Image
|
||||
key={img.id}
|
||||
src={`${APIURL}${img.image_name}`}
|
||||
w="30%"
|
||||
maxH="150px"
|
||||
objectFit="contain"
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
key={img.id}
|
||||
width="45%"
|
||||
height="50"
|
||||
controls
|
||||
>
|
||||
<source src={`${APIURL}${img.image_name}`} type="video/mp4" />
|
||||
</video>
|
||||
)
|
||||
))}
|
||||
</HStack>
|
||||
|
||||
{/* <Image src={img} w="100%" maxH="150px" objectFit="contain" /> */}
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ViewDailog
|
||||
@@ -1,12 +1,168 @@
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame";
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
// import { InputGroup } from "../../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
// import { useGetContactQuery } from "../../../Redux/Service/deactivated.account.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Spinner } from "../../../components/Sipnner/Spinner";
|
||||
import { useGetDeactivateUserQuery, useUserDeactivateToggleMutation } from "../../../Redux/Service/manage.user";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"First Name",
|
||||
"Last Name",
|
||||
"User Type",
|
||||
"Activate/Deactivate",
|
||||
];
|
||||
|
||||
const DeactivatedAccounts = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, isLoading, refetch, isError, isFetching } = useGetDeactivateUserQuery(currentPage);
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [userDeactivateToggle] = useUserDeactivateToggleMutation()
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setLocalData((data as any)?.data?.data || []);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const firstName = agency.first_name?.toLowerCase().includes(searchLower);
|
||||
const lastName = agency.last_name?.toLowerCase().includes(searchLower);
|
||||
// const email = agency.capital?.toLowerCase().includes(searchLower);
|
||||
return firstName || lastName;
|
||||
});
|
||||
|
||||
const handleToggle = async (agencyId: number, currentStatus: string) => {
|
||||
const newStatus = currentStatus === '1' ? '0' : '1';
|
||||
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await userDeactivateToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const manageUser = filteredData?.map((agency: any, index: number) => ({
|
||||
"Sr. No": index + 1,
|
||||
"First Name": agency?.first_name,
|
||||
"Last Name": agency?.last_name,
|
||||
"User Type": agency?.principal_type_xid === 3 ? "JobSeeker" : "Recruiter",
|
||||
"Activate/Deactivate": (
|
||||
<Box display={"flex"} justifyContent={"center"}>
|
||||
<Switch
|
||||
size={"sm"}
|
||||
colorPalette={"teal"}
|
||||
checked={agency.is_active === true}
|
||||
onChange={() => handleToggle(agency.id, agency.is_active ? "1" : "0")}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
}));
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%"
|
||||
>
|
||||
<Spinner />
|
||||
</Box>
|
||||
</MainFrame>
|
||||
);
|
||||
}
|
||||
|
||||
// if (isError) {
|
||||
// return (
|
||||
// <MainFrame>
|
||||
// <Box
|
||||
// display="flex"
|
||||
// justifyContent="center"
|
||||
// alignItems="center"
|
||||
// height="100%"
|
||||
// >
|
||||
// <Text>Error loading data</Text>
|
||||
// </Box>
|
||||
// </MainFrame>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<MainFrame>
|
||||
DeactivatedAccounts
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Deactivated User Accounts
|
||||
</Text>
|
||||
|
||||
export default DeactivatedAccounts
|
||||
<HStack>
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={(value) => {
|
||||
setSearchTerm(value);
|
||||
// setCurrentPage(1);
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={manageUser}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeactivatedAccounts;
|
||||
|
||||
192
src/Pages/ManageUsers/RegisterUsers/AddRegisterUsers.tsx
Normal file
192
src/Pages/ManageUsers/RegisterUsers/AddRegisterUsers.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { Box, Field, Input, Stack } from "@chakra-ui/react";
|
||||
import {
|
||||
DialogActionTrigger,
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { IoMdAdd } from "react-icons/io";
|
||||
import { useState } from "react";
|
||||
// import { useCreateUserMutation } from "../../../Redux/Service/manage.user";
|
||||
// import { useState } from "react";
|
||||
|
||||
function AddRegisterUsers() {
|
||||
// const [createUser] = useCreateUserMutation();
|
||||
const [userType, setUserType] = useState<number | "">("");
|
||||
|
||||
const [user, setUser] = useState<{
|
||||
principal_type_xid: number;
|
||||
principal_source_xid: number | "";
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
gender: string;
|
||||
date_of_birth: string;
|
||||
language_name: string[];
|
||||
}>({
|
||||
principal_type_xid: 1,
|
||||
principal_source_xid: userType,
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
gender: '',
|
||||
date_of_birth: '',
|
||||
language_name: [],
|
||||
});
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Button px={4} size={"xs"} bg={"#02A0A0"}>
|
||||
<IoMdAdd /> Add
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'80vh'}
|
||||
overflow={'scroll'}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Add User Accounts
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
First Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={user.first_name}
|
||||
onChange={(e) => setUser({ ...user, first_name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Last Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={user.last_name}
|
||||
onChange={(e) => setUser({ ...user, last_name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Gender
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={user.gender}
|
||||
onChange={(e) => setUser({ ...user, gender: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
DOB
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
type="date"
|
||||
value={user.date_of_birth}
|
||||
onChange={(e) => setUser({ ...user, date_of_birth: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Select User Type</Field.Label>
|
||||
<Box bgColor="#EEEEEE" borderRadius="md" p={1}>
|
||||
<select
|
||||
style={{
|
||||
width: "100%",
|
||||
background: "transparent",
|
||||
color: "black",
|
||||
border: "none",
|
||||
fontSize: "12px",
|
||||
height: "30px",
|
||||
outline: "none",
|
||||
}}
|
||||
value={userType}
|
||||
onChange={(e) => setUserType(Number(e.target.value))}
|
||||
>
|
||||
<option value="">Select User Type</option>
|
||||
<option value="2">Recruiter</option>
|
||||
<option value="3">Jobseeker</option>
|
||||
</select>
|
||||
</Box>
|
||||
</Field.Root>
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">
|
||||
OTP Verified
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px"
|
||||
/> */}
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Language
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={user.language_name.join(", ")} // display as comma-separated string
|
||||
onChange={(e) =>
|
||||
setUser({
|
||||
...user,
|
||||
language_name: e.target.value
|
||||
.split(",")
|
||||
.map(lang => lang.trim())
|
||||
.filter(Boolean), // remove empty strings
|
||||
})
|
||||
}
|
||||
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter mt={2}>
|
||||
<DialogActionTrigger asChild>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogActionTrigger>
|
||||
</DialogFooter>
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddRegisterUsers;
|
||||
346
src/Pages/ManageUsers/RegisterUsers/EditRegisterUsers.tsx
Normal file
346
src/Pages/ManageUsers/RegisterUsers/EditRegisterUsers.tsx
Normal file
@@ -0,0 +1,346 @@
|
||||
// import { MdOutlineRemoveRedEye } from "react-icons/md";
|
||||
import { Box, Field, HStack, Input, Stack } from "@chakra-ui/react";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
// import { BiEdit } from "react-icons/bi";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
// import { TbEdit } from "react-icons/tb";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { UserData, useUpdateUserMutation } from "../../../Redux/Service/manage.user";
|
||||
import { useState } from "react";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
interface UserPayload {
|
||||
id: number;
|
||||
principal_type_xid: number;
|
||||
principal_source_xid: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
gender: string;
|
||||
date_of_birth: string | null;
|
||||
language_xid: number; // ✅ Always an array
|
||||
}
|
||||
|
||||
export interface UserFormData {
|
||||
id: number;
|
||||
principal_type_xid: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
phone_number: string;
|
||||
gender: string;
|
||||
date_of_birth: string;
|
||||
is_active: boolean;
|
||||
principal_type: {
|
||||
id: number;
|
||||
principal_type_title: string;
|
||||
};
|
||||
principle_language_linkss: {
|
||||
id?: number;
|
||||
iam_principal_xid?: number;
|
||||
language_xid: number;
|
||||
};
|
||||
}
|
||||
|
||||
function EditRegisterUsers({ data, refetch }: { data: UserData, refetch: () => void }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const transformToFormData = (data: UserData): UserFormData => ({
|
||||
...data,
|
||||
principle_language_linkss: {
|
||||
...data.principle_language_linkss,
|
||||
language_xid: data.principle_language_linkss.language_xid, // wrap in array
|
||||
},
|
||||
});
|
||||
const [formData, setFormData] = useState<UserFormData>(transformToFormData(data));
|
||||
// const [formData, setFormData] = useState<UserData>({
|
||||
// id: data?.id,
|
||||
// first_name: data?.first_name || '',
|
||||
// last_name: data?.last_name || '',
|
||||
// principal_type_xid: data?.principal_type_xid,
|
||||
// phone_number: data?.phone_number || '',
|
||||
// gender: data?.gender || '',
|
||||
// date_of_birth: data?.date_of_birth || '',
|
||||
// principal_type: data?.principal_type,
|
||||
// is_active: data?.is_active ?? true,
|
||||
// principle_language_linkss: {
|
||||
// ...data?.principle_language_linkss,
|
||||
// language_xid: Array.isArray(data?.principle_language_linkss?.language_xid)
|
||||
// ? data.principle_language_linkss.language_xid
|
||||
// : [data?.principle_language_linkss?.language_xid].filter(Boolean),
|
||||
// },
|
||||
// // principle_language_linkss: data?.principle_language_linkss ?? [],
|
||||
// });
|
||||
const [updateUser, { isLoading }] = useUpdateUserMutation();
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (formData.first_name === '' || formData.last_name === '' || formData.phone_number === '' || formData.gender === '' || formData.date_of_birth === '') {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Input fields cannot be empty",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// const languageData = formData?.principle_language_linkss?.language_xid;
|
||||
|
||||
const payload: UserPayload = {
|
||||
id: formData?.id,
|
||||
principal_type_xid: formData?.principal_type_xid,
|
||||
principal_source_xid: formData?.id,
|
||||
first_name: formData?.first_name,
|
||||
last_name: formData?.last_name,
|
||||
gender: formData?.gender,
|
||||
date_of_birth: formData?.date_of_birth,
|
||||
// language_xid: languageData,
|
||||
language_xid: formData.principle_language_linkss?.language_xid
|
||||
? formData.principle_language_linkss.language_xid
|
||||
: formData.principle_language_linkss?.language_xid,
|
||||
|
||||
};
|
||||
|
||||
// console.log('payload', payload)
|
||||
|
||||
try {
|
||||
const response = await updateUser(payload).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Country updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
setIsOpen(false);
|
||||
refetch()
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update Country",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: error ? `${error?.data.message}` : "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRoot placement="center" key={formData.id} open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Span>
|
||||
<Edit />
|
||||
</Span> */}
|
||||
<Button bg="transparent" color={"black"} h={"18px"} onClick={handleOpenModal}><Edit /></Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white" p={0}>
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit user Accounts
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
First Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData.first_name}
|
||||
onChange={(e) => setFormData({ ...formData, first_name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Last Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData.last_name}
|
||||
onChange={(e) => setFormData({ ...formData, last_name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Gender
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData.gender}
|
||||
onChange={(e) => setFormData({ ...formData, gender: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
DOB
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData.date_of_birth ? new Date(formData.date_of_birth).toLocaleDateString('en-GB').replace(/\//g, '-') : 'N/A'}
|
||||
onChange={(e) => setFormData({ ...formData, date_of_birth: e.target.value })}
|
||||
disabled={formData.principal_type_xid === 2 ? true : false}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Mobile Number
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData.phone_number || ''}
|
||||
onChange={(e) => setFormData({ ...formData, phone_number: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Type Of User
|
||||
</Field.Label>
|
||||
{/* <Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData.principal_type?.principal_type_title || 'N/A'}
|
||||
onChange={(e) => setFormData({ ...formData, principal_type: { ...formData.principal_type, principal_type_title: e.target.value } })}
|
||||
/> */}
|
||||
|
||||
<Box>
|
||||
<select
|
||||
style={{
|
||||
width: "100%",
|
||||
background: "transparent",
|
||||
color: "black",
|
||||
border: "none",
|
||||
fontSize: "12px",
|
||||
height: "30px",
|
||||
outline: "none",
|
||||
}}
|
||||
value={formData.principal_type_xid?.toString() || 'N/A'}
|
||||
onChange={(e) => setFormData({ ...formData, principal_type_xid: Number(e.target.value) })}
|
||||
>
|
||||
{/* <option value="">Select User Type</option> */}
|
||||
<option value="2">Recruiter</option>
|
||||
<option value="3">Jobseeker</option>
|
||||
</select>
|
||||
</Box>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Default Language
|
||||
</Field.Label>
|
||||
{/* <Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={formData?.principle_language_linkss?.language_xid || 'N/A'}
|
||||
onChange={(e) => setFormData({
|
||||
...formData, principle_language_linkss: {
|
||||
...formData?.principle_language_linkss, language_xid: e.target.value
|
||||
.split(",")
|
||||
.map(lang => lang.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
})}
|
||||
/> */}
|
||||
|
||||
<HStack>
|
||||
<select
|
||||
style={{
|
||||
width: "100%",
|
||||
background: "transparent",
|
||||
color: "black",
|
||||
border: "none",
|
||||
fontSize: "12px",
|
||||
height: "30px",
|
||||
outline: "none",
|
||||
}}
|
||||
value={formData?.principle_language_linkss?.language_xid || ""}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
setFormData({
|
||||
...formData,
|
||||
principle_language_linkss: {
|
||||
...formData?.principle_language_linkss,
|
||||
language_xid: value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<option value="1">English</option>
|
||||
<option value="2">Hindi</option>
|
||||
<option value="3">Marathi</option>
|
||||
<option value="4">Telgu</option>
|
||||
<option value="5">Tamil</option>
|
||||
<option value="6">Bengali</option>
|
||||
<option value="7">Odia</option>
|
||||
</select>
|
||||
</HStack>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter mt={5}>
|
||||
<Button rounded={"md"} w={"100%"} size={"sm"} bg={"#02A0A0"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditRegisterUsers;
|
||||
@@ -1,81 +1,208 @@
|
||||
import { Box, HStack, Image, Input, Text } from "@chakra-ui/react";
|
||||
import {
|
||||
Box, HStack,
|
||||
Image,
|
||||
// Image,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame";
|
||||
import AlertDailog from "../../../components/AlertDailog";
|
||||
import { FcCancel } from "react-icons/fc";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { MdOutlineRemoveRedEye } from "react-icons/md";
|
||||
import { RiDeleteBin5Line } from "react-icons/ri";
|
||||
// import AlertDailog from "../../../components/AlertDailog";
|
||||
// import { RiDeleteBin5Line } from "react-icons/ri";
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { HiCheck } from "react-icons/hi2";
|
||||
import { HiX } from "react-icons/hi";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { InputGroup } from "../../../components/ui/input-group";
|
||||
import { LuSearch } from "react-icons/lu";
|
||||
import ViewRegisterUsers from "./ViewRegisterUsers";
|
||||
import EditRegisterUsers from "./EditRegisterUsers";
|
||||
// import AddRegisterUsers from "./AddRegisterUsers";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDeleteUserMutation, useGetManageUserQuery, UserData, useUserToggleMutation } from "../../../Redux/Service/manage.user";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import AlertDailog from "../../../components/AlertDailog";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
import Delete from "../../../components/ActionIcons/Delete";
|
||||
import { delay } from "../../../components/Utils";
|
||||
// import Delete from "../../../components/ActionIcons/Delete";
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"First Name",
|
||||
"Mobile Number ",
|
||||
"Last Name",
|
||||
"Mobile Number",
|
||||
"Gender",
|
||||
"DOB",
|
||||
"Type Of User",
|
||||
"Language",
|
||||
"Activate/Deactivate",
|
||||
"Default Language",
|
||||
"Active/Deactive",
|
||||
"Action",
|
||||
];
|
||||
|
||||
const registerUser: any[] = [
|
||||
...Array.from({ length: 12 }, (_, i) => ({
|
||||
"Sr. No": i + 1,
|
||||
"First Name": "Ritesh",
|
||||
"Mobile Number ": "akanksha@gmail.com",
|
||||
"Gender": "9876543210",
|
||||
"DOB": "Female",
|
||||
"Type Of User": "15-01-1990",
|
||||
"Language": "Mumbai",
|
||||
"Activate/Deactivate": (
|
||||
<Box>
|
||||
<Switch colorPalette={'teal'} />
|
||||
</Box>
|
||||
),
|
||||
// const registerUser: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "First Name": "Ritesh",
|
||||
// "Mobile Number": "akanksha@gmail.com",
|
||||
// "Gender": "9876543210",
|
||||
// "DOB": "Female",
|
||||
// "Type Of User": "15-01-1990",
|
||||
// "Language": "Mumbai",
|
||||
// "Activate/Deactivate": (
|
||||
// <Box>
|
||||
// <Switch size={'sm'} colorPalette={'teal'} />
|
||||
// </Box>
|
||||
// ),
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <ViewRegisterUsers />
|
||||
// <EditRegisterUsers />
|
||||
// {/* <RiDeleteBin5Line style={{ cursor: "pointer" }} /> */}
|
||||
// {/* <AlertDailog
|
||||
// AltertTiggerIcon={() =><Delete /> } // Pass as function
|
||||
// alertText="Delete Users"
|
||||
// alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
// alertCaption="Are You Sure You Want To Delete This User ?"
|
||||
// onConfirm={() => {
|
||||
// console.log("User deleted:", i + 1);
|
||||
// }}
|
||||
// /> */}
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const RegisterUsers = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, refetch, isFetching, isError } = useGetManageUserQuery(currentPage)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [userToggle] = useUserToggleMutation()
|
||||
const [deleteFaqPost] = useDeleteUserMutation()
|
||||
const [deleteModal, setDeleteModal] = useState(false)
|
||||
const [selectedFaqId, setSelectedFaqId] = useState<number | null>(null);
|
||||
|
||||
console.log("Register Users Data", data?.data.data);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const firstName = agency.first_name?.toLowerCase().includes(searchLower);
|
||||
// const email = agency.capital?.toLowerCase().includes(searchLower);
|
||||
return firstName;
|
||||
});
|
||||
|
||||
const handleToggle = async (agencyId: number, currentStatus: string) => {
|
||||
const newStatus = currentStatus === '1' ? '0' : '1';
|
||||
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await userToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Someting went wrong.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteFaq = async (faqId: number) => {
|
||||
try {
|
||||
const response = await deleteFaqPost({ id: faqId }).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "User deleted successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
console.log("User deleted successfully:", response);
|
||||
}
|
||||
// Optionally, refetch data or update state after deletion
|
||||
} catch (error) {
|
||||
console.error("Error deleting User:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const managepost = filteredData?.flatMap((agency: UserData, index: number) => ({
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"First Name": agency.first_name,
|
||||
"Last Name": agency.last_name,
|
||||
"Mobile Number": agency.phone_number,
|
||||
"Gender": agency.gender,
|
||||
"DOB": agency.date_of_birth ? new Date(agency.date_of_birth).toLocaleDateString('en-GB').replace(/\//g, '-') : 'N/A',
|
||||
"Type Of User": agency.principal_type?.principal_type_title || 'N/A',
|
||||
"Default Language": agency?.principle_language_linkss?.language?.language_name,
|
||||
"Active/Deactive": agency.is_active === true ? 'Active' : 'Inactive',
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
<AlertDailog
|
||||
AltertTiggerIcon={FcCancel}
|
||||
alertText="Suspend User"
|
||||
alertIcon={<Image src={""} h={"39px"} />}
|
||||
alertCaption="Are you sure you want to deactivate this user?"
|
||||
onConfirm={() => {
|
||||
console.log("User deleted:", i + 1);
|
||||
}}
|
||||
<EditRegisterUsers
|
||||
data={agency}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<NavLink to="/manage-user/active-user/view-active-user">
|
||||
<MdOutlineRemoveRedEye
|
||||
style={{ cursor: "pointer", fontSize: "16px" }}
|
||||
<ViewRegisterUsers data={agency} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={'teal'}
|
||||
size={"xs"}
|
||||
checked={agency.is_active === true}
|
||||
onChange={() => handleToggle(agency.id, agency.is_active ? '1' : '0')}
|
||||
/>
|
||||
</NavLink>
|
||||
|
||||
{/* <RiDeleteBin5Line style={{ cursor: "pointer" }} /> */}
|
||||
</Box>
|
||||
<AlertDailog
|
||||
AltertTiggerIcon={RiDeleteBin5Line}
|
||||
alertText="Delete Users"
|
||||
isOpen={deleteModal}
|
||||
AltertTiggerIcon={() => <Delete onClick={() => {
|
||||
setSelectedFaqId(agency.id);
|
||||
setDeleteModal(true)
|
||||
}} />}
|
||||
alertText="Do you want to delete user?"
|
||||
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
alertCaption="are you sure you want to delete ?"
|
||||
onClose={() => setDeleteModal(false)}
|
||||
onConfirm={() => {
|
||||
console.log("User deleted:", i + 1);
|
||||
// console.log("Deleting FAQ with ID:", selectedFaqId); // Correct ID
|
||||
if (selectedFaqId) {
|
||||
setDeleteModal(false);
|
||||
handleDeleteFaq(selectedFaqId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
),
|
||||
})),
|
||||
];
|
||||
}))
|
||||
|
||||
const RegisterUsers = () => {
|
||||
return (
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
@@ -83,36 +210,34 @@ const RegisterUsers = () => {
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Register Users
|
||||
Register Users
|
||||
</Text>
|
||||
|
||||
<HStack>
|
||||
<InputGroup
|
||||
startElement={
|
||||
<LuSearch fontSize={"xs"} style={{position:'relative',left:'10px'}} />
|
||||
}
|
||||
color={"#000"}
|
||||
>
|
||||
<Input
|
||||
p={4}
|
||||
w={300}
|
||||
bg={"#fff"}
|
||||
colorPalette={"blue"}
|
||||
_focus={{ border: "1px solid #02A0A0" }}
|
||||
rounded={"md"}
|
||||
size={"2xs"}
|
||||
fontSize={"2sm"}
|
||||
placeholder="Search..."
|
||||
bgColor={'#EEEEEE'}
|
||||
ps={8}
|
||||
/>
|
||||
</InputGroup>
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={(value) => {
|
||||
setSearchTerm(value);
|
||||
// setCurrentPage(1);
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
{/* <AddRegisterUsers /> */}
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={registerUser}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
</MainFrame>
|
||||
|
||||
122
src/Pages/ManageUsers/RegisterUsers/ViewRegisterUsers.tsx
Normal file
122
src/Pages/ManageUsers/RegisterUsers/ViewRegisterUsers.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Field, Input, Span, Stack } from "@chakra-ui/react";
|
||||
import View from "../../../components/ActionIcons/View";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { UserData } from "../../../Redux/Service/manage.user";
|
||||
|
||||
function ViewRegisterUsers({ data }: { data: UserData }) {
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Span><View /></Span>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'80vh'}
|
||||
overflow={'scroll'}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
View Details
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
First Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.first_name || ''}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Last Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none" pl={1}
|
||||
fontSize="12px" height="30px"
|
||||
value={data?.last_name || ''}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Gender
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px"
|
||||
value={data?.gender || ''}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
DOB
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px"
|
||||
value={data?.date_of_birth ? new Date(data.date_of_birth).toLocaleDateString('en-GB').replace(/\//g, '-') : 'N/A'}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Mobile Number
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.phone_number || ''}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Type Of User
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={data?.principal_type?.principal_type_title || 'N/A'}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Default Language
|
||||
</Field.Label>
|
||||
<Input
|
||||
bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px"
|
||||
value={data?.principle_language_linkss?.language?.language_name || 'N/A'}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewRegisterUsers;
|
||||
@@ -1,12 +1,221 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import EditAgencyMaster from "./EditAgencyMaster";
|
||||
// import ViewAgencyAddModel from "./ViewAgencyAddModel";
|
||||
import ViewAgencyMaster from "./ViewAgencyMaster";
|
||||
import { useAgencyMasterToggleMutation, useGetAgencyMasterQuery } from "../../../Redux/Service/agency.master.module.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { useDebounce } from "../../../components/Hooks/useDebounce";
|
||||
import { toaster, Toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Agency Name",
|
||||
"RC no.",
|
||||
"State",
|
||||
"RC Status",
|
||||
"Registered Office Address",
|
||||
"Website/Domain",
|
||||
"GST no.",
|
||||
"Agency Status",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Agency Name": "Lorem Ipsum",
|
||||
// "RC no.": "Lorem Ipsum",
|
||||
// "State": "Lorem Ipsum",
|
||||
// "RC Status": "Active",
|
||||
// "Registered Office Address": "Lorem Ipsum",
|
||||
// "Website/Domain": "Lorem Ipsum",
|
||||
// "GST no.": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <ViewAgencyMaster/>
|
||||
// <EditAgencyMaster />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"}/>
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const AgencyMaster = () => {
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
AgencyMaster
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default AgencyMaster
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [agencyMasterToggle] = useAgencyMasterToggleMutation()
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isError, isFetching } = useGetAgencyMasterQuery(queryArgs)
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await agencyMasterToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please try again later",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) =>
|
||||
agency?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agency?.rc_number.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agency?.state.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agency?.registered_office.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agency?.domain_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agency?.gst_number.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
// const activeCount = filteredData?.filter((a: any) => a.is_active === 1).length ?? 0;
|
||||
|
||||
const managepost = filteredData?.map((agency: any, index: number) => {
|
||||
// const isOnlyActive = activeCount === 1 && agency.is_active === 1;
|
||||
|
||||
return {
|
||||
id: agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Agency Name": agency.name,
|
||||
"RC no.": agency.rc_number,
|
||||
"State": agency.state,
|
||||
"RC Status": agency.rc_status,
|
||||
"Registered Office Address": agency.registered_office,
|
||||
"Website/Domain": agency.domain_name,
|
||||
"GST no.": agency.gst_number,
|
||||
"Agency Status": agency.is_domain_verified ? "Verified" : "Unverified",
|
||||
"is_active": agency.is_active,
|
||||
Action: (
|
||||
<HStack justifyContent="center">
|
||||
<ViewAgencyMaster agency={localData} id={agency.id} />
|
||||
<EditAgencyMaster editData={agency} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={"teal"}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id.toString(), Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
// disabled={isOnlyActive}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data?.data);
|
||||
}
|
||||
}, [data, localData, managepost]);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("Fetched data:", data);
|
||||
// console.log("Local data:", localData);
|
||||
// console.log("Managepost data:", managepost);
|
||||
// }, [data, localData, managepost]);
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Agency Master
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
{/* <InputGroup
|
||||
startElement={
|
||||
<LuSearch fontSize={"xs"} style={{ position: 'relative', left: '10px' }} />
|
||||
}
|
||||
color={"#000"}
|
||||
>
|
||||
<Input
|
||||
p={3}
|
||||
w={300}
|
||||
bg={"#fff"}
|
||||
colorPalette={"blue"}
|
||||
_focus={{ border: "1px solid #02A0A0" }}
|
||||
rounded={"md"}
|
||||
size={"xs"}
|
||||
fontSize={"sm"}
|
||||
placeholder="Search..."
|
||||
bgColor={'#EEEEEE'}
|
||||
ps={8}
|
||||
/>
|
||||
</InputGroup> */}
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <ViewAgencyAddModel refetch={refetch} /> */}
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost || []}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default AgencyMaster
|
||||
283
src/Pages/MasterModule/AgencyMaster/EditAgencyMaster.tsx
Normal file
283
src/Pages/MasterModule/AgencyMaster/EditAgencyMaster.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Field, Input, Stack } from "@chakra-ui/react";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUpdateAgencyMasterMutation } from "../../../Redux/Service/agency.master.module.service";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
// interface Organization {
|
||||
// id: number;
|
||||
// raid?: string;
|
||||
// name: string;
|
||||
// auth_signatory?: string;
|
||||
// state: string;
|
||||
// district?: string;
|
||||
// rc_number: number;
|
||||
// contact_details?: string;
|
||||
// registered_office: string;
|
||||
// branch_office?: string;
|
||||
// registered_email?: string;
|
||||
// other_email?: string;
|
||||
// registered_contact?: string;
|
||||
// website?: string;
|
||||
// domain_name: string;
|
||||
// staff_domain_name?: string;
|
||||
// gst_number: string;
|
||||
// rc_status?: "Active" | "Inactive"; // Assuming it's a status with limited values
|
||||
// }
|
||||
|
||||
type AgencyFormData = {
|
||||
id: string;
|
||||
name: string;
|
||||
state: string;
|
||||
rc_number: string;
|
||||
registered_office: string;
|
||||
domain_name: string;
|
||||
gst_number: string;
|
||||
};
|
||||
|
||||
|
||||
|
||||
function EditAgencyMaster<T extends AgencyFormData>({ editData, refetch }: { editData: T, refetch: VoidFunction }) {
|
||||
|
||||
const [formData, setFormData] = useState(editData);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [updateAgencyMaster, { isLoading }] = useUpdateAgencyMasterMutation()
|
||||
|
||||
console.log("Edit Data", editData);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData(editData);
|
||||
}, [editData]);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev: any) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// console.log("Updated Data:", formData);
|
||||
if (!formData.name.trim() ||
|
||||
!formData.rc_number ||
|
||||
!formData.state.trim() ||
|
||||
!formData.registered_office.trim() ||
|
||||
!formData.domain_name.trim() ||
|
||||
!formData.gst_number.trim()) {
|
||||
// console.log("Validation failed: Some fields are empty.");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Input fields cannot be empty",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsOpen(false);
|
||||
// Handle API call or further processing here
|
||||
const payload = {
|
||||
id: formData?.id,
|
||||
name: formData?.name,
|
||||
state: formData?.state,
|
||||
rc_number: formData?.rc_number,
|
||||
registered_office: formData?.registered_office,
|
||||
domain_name: formData?.domain_name,
|
||||
gst_number:formData?.gst_number,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await updateAgencyMaster(payload).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Data updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error updating template:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: `${error.data.message || "Failed to update"}`,
|
||||
type: "error",
|
||||
});
|
||||
// alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Button bg="transparent" color={"black"} h={"18px"} onClick={handleOpenModal}><Edit /></Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Agency name
|
||||
</Field.Label>
|
||||
<Input
|
||||
name="name"
|
||||
value={formData?.name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
RC No.
|
||||
</Field.Label>
|
||||
<Input
|
||||
name='rc_number'
|
||||
value={formData.rc_number}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
State
|
||||
</Field.Label>
|
||||
<Input
|
||||
name="state"
|
||||
value={formData.state}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Registered Office Address
|
||||
</Field.Label>
|
||||
<Input
|
||||
name="registered_office"
|
||||
value={formData.registered_office}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Website/Domain
|
||||
</Field.Label>
|
||||
<Input
|
||||
name="domain_name"
|
||||
value={formData.domain_name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
GST no.
|
||||
</Field.Label>
|
||||
<Input
|
||||
name="gst_number"
|
||||
value={formData.gst_number}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">
|
||||
RC Status
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={formData.rc_status}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/> */}
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color={"#fff"}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditAgencyMaster;
|
||||
204
src/Pages/MasterModule/AgencyMaster/ViewAgencyAddModel.tsx
Normal file
204
src/Pages/MasterModule/AgencyMaster/ViewAgencyAddModel.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { AgencyPost, useCreateAgencyMasterPostMutation } from "../../../Redux/Service/agency.master.module.service"
|
||||
import { useState } from "react"
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster"
|
||||
|
||||
function ViewAgencyAddModel({ refetch }: { refetch: VoidFunction }) {
|
||||
const [formData, setFormData] = useState<AgencyPost>({
|
||||
name: "",
|
||||
rc_number: "",
|
||||
state: "",
|
||||
registered_office: "",
|
||||
domain_name: "",
|
||||
gst_number: "",
|
||||
});
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createAgencyMasterPost] = useCreateAgencyMasterPostMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev: any) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
console.log("New Data:", formData);
|
||||
const requiredFields: (keyof AgencyPost)[] = ["name", "rc_number", "state", "registered_office", "domain_name", "gst_number"];
|
||||
const isEmptyField = requiredFields.some(field => !formData[field]?.trim());
|
||||
|
||||
if (isEmptyField) {
|
||||
console.log("Validation failed: Some fields are empty.");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "All required fields must be filled.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: formData.name
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await createAgencyMasterPost(payload).unwrap();
|
||||
if (response) {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Added successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to add data.",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please try again later",
|
||||
type: "error",
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text >Add</Text>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'80vh'}
|
||||
overflow={'scroll'}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Agency Name</Field.Label>
|
||||
<Input
|
||||
name="name"
|
||||
value={formData.name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">RC No.</Field.Label>
|
||||
<Input
|
||||
name='rc_number'
|
||||
value={formData.rc_number}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">State</Field.Label>
|
||||
<Input
|
||||
name="state"
|
||||
value={formData.state}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Registered Office Address</Field.Label>
|
||||
<Input
|
||||
name="registered_office"
|
||||
value={formData.registered_office}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Website/Domain</Field.Label>
|
||||
<Input
|
||||
name="domain_name"
|
||||
value={formData.domain_name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">GST no.</Field.Label>
|
||||
<Input
|
||||
name="gst_number"
|
||||
value={formData.gst_number}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">Action</Field.Label>
|
||||
<Input placeholder="" bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" /> */}
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ViewAgencyAddModel
|
||||
178
src/Pages/MasterModule/AgencyMaster/ViewAgencyMaster.tsx
Normal file
178
src/Pages/MasterModule/AgencyMaster/ViewAgencyMaster.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
// DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Field, Input, Span, Stack } from "@chakra-ui/react";
|
||||
// import { MdOutlineRemoveRedEye } from "react-icons/md";
|
||||
// import { Button } from "../../../components/ui/button";
|
||||
import View from "../../../components/ActionIcons/View";
|
||||
import { Agency } from "../../../Redux/Service/agency.master.module.service";
|
||||
|
||||
function ViewAgencyMaster({ agency, id }: { agency: Agency[], id:number }) {
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Span><View /></Span>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
View
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{agency.map((data) => (
|
||||
<DialogBody bg="white">
|
||||
{data.id === id && <Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Agency name
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
RC No.
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.rc_number}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
State
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.state}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
RC Status
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.rc_status}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Registered Office Address
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.registered_office}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Website/Domain
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.domain_name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
GST no.
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.gst_number}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/>
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">
|
||||
Action
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
disabled
|
||||
/> */}
|
||||
</Field.Root>
|
||||
</Stack>}
|
||||
</DialogBody>
|
||||
))}
|
||||
|
||||
{/* <DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color={"#fff"}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter> */}
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewAgencyMaster;
|
||||
@@ -1,12 +1,193 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import CountryAddModel from "./CountryAddModel";
|
||||
import EditCountryModel from "./EditCountryModel";
|
||||
import { CountryData, useCountryToggleMutation, useGetCountryMasterQuery } from "../../../Redux/Service/country.master";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { useDebounce } from "../../../components/Hooks/useDebounce";
|
||||
import { toaster, Toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Title": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <EditCountryModel />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"} />
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const Country = () => {
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
Country
|
||||
</MainFrame>
|
||||
)
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
// const { data, refetch } = useGetCountryMasterQuery(currentPage)
|
||||
const [countryToggle] = useCountryToggleMutation()
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isError, isFetching } = useGetCountryMasterQuery(queryArgs);
|
||||
console.log("Country Data", data?.data.data)
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const countryName = agency.en_name?.toLowerCase().includes(searchLower);
|
||||
const capitalName = agency.capital?.toLowerCase().includes(searchLower);
|
||||
return countryName || capitalName;
|
||||
});
|
||||
|
||||
const handleToggle = async (agencyId: number, currentStatus: string) => {
|
||||
const newStatus = currentStatus === '1' ? '0' : '1';
|
||||
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await countryToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please try again later.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Country
|
||||
|
||||
const managepost = filteredData?.flatMap((agency: CountryData, index: number) => ({
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Title": agency.en_name,
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
<EditCountryModel rowData={{ id: agency.id, en_name: agency.en_name, country_code: agency.country_code, phonecode: agency.phonecode, capital: agency.capital, currency: agency.currency, currency_name: agency.currency_name, currency_symbol: agency.currency_symbol }} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={'teal'}
|
||||
size={"xs"}
|
||||
checked={agency.is_active === '1'}
|
||||
onChange={() => handleToggle(agency.id, agency.is_active)}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}))
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Country
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
{/* <InputGroup
|
||||
startElement={
|
||||
<LuSearch fontSize={"xs"} style={{ position: 'relative', left: '10px' }} />
|
||||
}
|
||||
color={"#000"}
|
||||
>
|
||||
<Input
|
||||
p={3}
|
||||
w={300}
|
||||
bg={"#fff"}
|
||||
colorPalette={"blue"}
|
||||
_focus={{ border: "1px solid #02A0A0" }}
|
||||
rounded={"md"}
|
||||
size={"xs"}
|
||||
fontSize={"sm"}
|
||||
placeholder="Search..."
|
||||
bgColor={'#EEEEEE'}
|
||||
ps={8}
|
||||
/>
|
||||
</InputGroup> */}
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
// onChange={(value) => {
|
||||
// setSearchTerm(value);
|
||||
// // setCurrentPage(1);
|
||||
// refetch()
|
||||
// }}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<CountryAddModel refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default Country
|
||||
219
src/Pages/MasterModule/Country/CountryAddModel.tsx
Normal file
219
src/Pages/MasterModule/Country/CountryAddModel.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { useEffect, useState } from "react";
|
||||
import { PostCountry, useCreateCountryPostMutation } from "../../../Redux/Service/country.master";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
function CountryAddModel({refetch}: { refetch: VoidFunction }) {
|
||||
const [createCountryPost, { isLoading }] = useCreateCountryPostMutation()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [countryName, setCountryName] = useState<PostCountry>({
|
||||
en_name: '',
|
||||
country_code: '',
|
||||
phonecode: '',
|
||||
capital: '',
|
||||
currency: '',
|
||||
currency_name: '',
|
||||
currency_symbol: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setCountryName({
|
||||
en_name: '',
|
||||
country_code: '',
|
||||
phonecode: '',
|
||||
capital: '',
|
||||
currency: '',
|
||||
currency_name: '',
|
||||
currency_symbol: '',
|
||||
});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (countryName.en_name === "" || countryName.country_code === "" || countryName.phonecode === "" || countryName.capital === "" || countryName.currency === "" || countryName.currency_name === "" || countryName.currency_symbol === "") {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Input fields cannot be empty",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: PostCountry = {
|
||||
en_name: countryName.en_name,
|
||||
country_code: countryName.country_code,
|
||||
phonecode: countryName.phonecode,
|
||||
capital: countryName.capital,
|
||||
currency: countryName.currency,
|
||||
currency_name: countryName.currency_name,
|
||||
currency_symbol: countryName.currency_symbol,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await createCountryPost(payload).unwrap();
|
||||
if (response) {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Country added successfully",
|
||||
type: "success",
|
||||
});
|
||||
setIsOpen(false);
|
||||
refetch();
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to add Country",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Country</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Country Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.en_name}
|
||||
onChange={(e) => setCountryName({ ...countryName, en_name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Country Code</Field.Label>
|
||||
<Input
|
||||
placeholder="Please enter country code ex: IN, US"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.country_code}
|
||||
onChange={(e) => setCountryName({ ...countryName, country_code: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Phone Code</Field.Label>
|
||||
<Input
|
||||
placeholder="Please enter phone code ex: +91, +1"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.phonecode}
|
||||
onChange={(e) => setCountryName({ ...countryName, phonecode: e.target.value })}
|
||||
/>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Capital</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Capital City"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.capital}
|
||||
onChange={(e) => setCountryName({ ...countryName, capital: e.target.value })}
|
||||
/>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Currency</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Currency"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.currency}
|
||||
onChange={(e) => setCountryName({ ...countryName, currency: e.target.value })}
|
||||
/>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Currency name</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Currency Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.currency_name}
|
||||
onChange={(e) => setCountryName({ ...countryName, currency_name: e.target.value })}
|
||||
/>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Currency Symbol</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Currency Symbol"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={countryName.currency_symbol}
|
||||
onChange={(e) => setCountryName({ ...countryName, currency_symbol: e.target.value })}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
<Toaster />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default CountryAddModel
|
||||
161
src/Pages/MasterModule/Country/EditCountryModel.tsx
Normal file
161
src/Pages/MasterModule/Country/EditCountryModel.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Field, Input, Stack } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useUpdateCountryMutation } from "../../../Redux/Service/country.master";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export interface EditCountryModelProps {
|
||||
id?: number;
|
||||
en_name?: string;
|
||||
hi_name?: string;
|
||||
mr_name?: string;
|
||||
te_name?: string;
|
||||
ta_name?: string;
|
||||
bn_name?: string;
|
||||
or_name?: string;
|
||||
country_code?: string;
|
||||
phonecode?: string;
|
||||
capital?: string;
|
||||
currency?: string;
|
||||
currency_name?: string;
|
||||
currency_symbol?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function EditCountryModel({ rowData, refetch }: { rowData: EditCountryModelProps, refetch: VoidFunction }) {
|
||||
const [updateCountry, { isLoading }] = useUpdateCountryMutation()
|
||||
const [editData, setEditData] = useState(rowData)
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(rowData){
|
||||
setEditData(rowData)
|
||||
}
|
||||
}, [rowData])
|
||||
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (editData?.en_name === '') {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Input fields cannot be empty",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Only en_name is editable, so we only need to send that in the payload.
|
||||
const payload = {
|
||||
id: rowData?.id,
|
||||
en_name: editData?.en_name,
|
||||
country_code: rowData?.country_code,
|
||||
phonecode: rowData?.phonecode,
|
||||
capital: rowData?.capital,
|
||||
currency: rowData?.currency,
|
||||
currency_name: rowData?.currency_name,
|
||||
currency_symbol: rowData?.currency_symbol,
|
||||
};
|
||||
|
||||
// console.log('payload', payload)
|
||||
|
||||
try {
|
||||
const response = await updateCountry(payload).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Country updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
setIsOpen(false);
|
||||
refetch()
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update Country",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRoot placement="center" key={editData.id} open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Button bg="transparent" color={"black"} h={"18px"} onClick={handleOpenModal}><Edit /></Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Country
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={editData.en_name}
|
||||
onChange={(e) => setEditData({ ...editData, en_name: e.target.value })}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditCountryModel;
|
||||
155
src/Pages/MasterModule/DepartmentMaster/AddDepartmentMaster.tsx
Normal file
155
src/Pages/MasterModule/DepartmentMaster/AddDepartmentMaster.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { useState } from "react";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
import { useCreateDepartmentPostMutation, useGetDepartmentMasterDropDownQuery } from "../../../Redux/Service/department.master";
|
||||
|
||||
function AddDepartmentMaster({ refetch }: { refetch: VoidFunction }) {
|
||||
const [jobType, setJobType] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createDepartmentPost, { isLoading }] = useCreateDepartmentPostMutation()
|
||||
const { data } = useGetDepartmentMasterDropDownQuery()
|
||||
const [selectdDep, setSelectdDep] = useState<any>({
|
||||
id: '',
|
||||
en_name: '',
|
||||
});
|
||||
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!jobType.trim() || !selectdDep.id) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
en_name: jobType,
|
||||
industry_masters_xid: selectdDep.id,
|
||||
};
|
||||
|
||||
try {
|
||||
await createDepartmentPost(payload);
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
setJobType("");
|
||||
setSelectdDep({
|
||||
id: '',
|
||||
en_name: '',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
console.log("Selected Department", selectdDep);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Select Industry
|
||||
</Field.Label>
|
||||
<select
|
||||
value={selectdDep.id}
|
||||
onChange={(e) => {
|
||||
const selectedId = e.target.value;
|
||||
const selectedIndustry = data?.data.find((item: any) => item.id.toString() === selectedId);
|
||||
if (selectedIndustry) {
|
||||
setSelectdDep({
|
||||
id: selectedIndustry.id,
|
||||
en_name: selectedIndustry.en_name,
|
||||
});
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select department
|
||||
</option>
|
||||
{data?.data.map((item: any) => (
|
||||
<option value={item.id} key={item.id}>
|
||||
{item.en_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Department</Field.Label>
|
||||
<Input
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={jobType}
|
||||
onChange={(e) => setJobType(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AddDepartmentMaster
|
||||
162
src/Pages/MasterModule/DepartmentMaster/DepartmentMasterList.tsx
Normal file
162
src/Pages/MasterModule/DepartmentMaster/DepartmentMasterList.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
// import { InputGroup } from "../../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { useDepartmentToggleMutation, useGetDepartmentMasterQuery } from "../../../Redux/Service/department.master";
|
||||
import AddDepartmentMaster from "./AddDepartmentMaster";
|
||||
import EditDepartmentMaster from "./EditDepartmentMaster";
|
||||
import { useDebounce } from "../../../components/Hooks/useDebounce";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
|
||||
|
||||
const DepartmentMasterList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [departmentToggle] = useDepartmentToggleMutation()
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isError, isFetching } = useGetDepartmentMasterQuery(queryArgs)
|
||||
|
||||
console.log("Department Data", data?.data.data)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await departmentToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Someting went wrong.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const title = agency.en_name?.toLowerCase().includes(searchLower);
|
||||
return title;
|
||||
});
|
||||
|
||||
const managepost = filteredData?.map((agency: any, index: number) => ({
|
||||
'id': agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Title": agency.en_name,
|
||||
"is_active": agency.is_active,
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
{/* <ViewAgencyMaster agency={localData} id={agency.id} /> */}
|
||||
<EditDepartmentMaster localData={agency} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={"teal"}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id, Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("Fetched data:", data);
|
||||
// console.log("Local data:", localData);
|
||||
// console.log("Managepost data:", managepost);
|
||||
// }, [data, localData, managepost]);
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Department Master
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
{/* <ViewAgencyAddModel /> */}
|
||||
<AddDepartmentMaster refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name"]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost || []}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default DepartmentMasterList
|
||||
160
src/Pages/MasterModule/DepartmentMaster/EditDepartmentMaster.tsx
Normal file
160
src/Pages/MasterModule/DepartmentMaster/EditDepartmentMaster.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Box, Field, Input, Stack } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useState } from "react";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
import { useGetDepartmentMasterDropDownQuery, useUpdateDepartmentMutation } from "../../../Redux/Service/department.master";
|
||||
|
||||
function EditDepartmentMaster({ localData, refetch }: { localData: any, refetch: VoidFunction }) {
|
||||
const [jobtype, setJobType] = useState("");
|
||||
const [updateDepartment, { isLoading }] = useUpdateDepartmentMutation()
|
||||
const { data } = useGetDepartmentMasterDropDownQuery()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectdDep, setSelectdDep] = useState<any>({
|
||||
id: localData.industry_master.id,
|
||||
en_name: localData.industry_master.en_name,
|
||||
});
|
||||
|
||||
const handleOpenModal = () => {
|
||||
// const template = localData?.find((item: any) => item.id === id);
|
||||
if (localData) {
|
||||
setJobType(localData.en_name);
|
||||
setSelectdDep({
|
||||
id: localData.industry_master.id,
|
||||
en_name: localData.industry_master.en_name,
|
||||
})
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!jobtype.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: localData.id,
|
||||
industry_masters_xid: selectdDep.id != null ? selectdDep.id : localData.industry_master.id,
|
||||
en_name: jobtype
|
||||
};
|
||||
|
||||
try {
|
||||
await updateDepartment(payload).unwrap();
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
console.log("Dropdown Data", selectdDep);
|
||||
console.log("Dep Data", localData)
|
||||
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Box bg={"transparent"} onClick={handleOpenModal}>
|
||||
<Edit />
|
||||
</Box>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit Title
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Select Industry
|
||||
</Field.Label>
|
||||
<select
|
||||
value={selectdDep.id}
|
||||
onChange={(e) => {
|
||||
const selected = data?.data.find((item: any) => item.id === Number(e.target.value));
|
||||
if (selected) {
|
||||
setSelectdDep({ id: selected.id, en_name: selected.en_name });
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: "#EEEEEE",
|
||||
color: "black",
|
||||
border: "none",
|
||||
height: "30px",
|
||||
fontSize: "12px",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select department
|
||||
</option>
|
||||
{data?.data.map((item: any) => (
|
||||
<option value={item.id} key={item.id}>
|
||||
{item.en_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Department
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={jobtype}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={(e) => setJobType(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditDepartmentMaster;
|
||||
103
src/Pages/MasterModule/IndustryMaster/AddIndustryMaster.tsx
Normal file
103
src/Pages/MasterModule/IndustryMaster/AddIndustryMaster.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { useState } from "react";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
import { useCreateIndustryMasterPostMutation } from "../../../Redux/Service/industry.master.service";
|
||||
|
||||
function AddIndustryMaster({ refetch }: { refetch: VoidFunction }) {
|
||||
const [jobType, setJobType] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createIndustryMasterPost, { isLoading }] = useCreateIndustryMasterPostMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!jobType.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
en_name: jobType,
|
||||
categories_masters_xid:4
|
||||
};
|
||||
|
||||
try {
|
||||
await createIndustryMasterPost(payload);
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Job Type</Field.Label>
|
||||
<Input
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={jobType}
|
||||
onChange={(e) => setJobType(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AddIndustryMaster
|
||||
112
src/Pages/MasterModule/IndustryMaster/EditIndustryMaster.tsx
Normal file
112
src/Pages/MasterModule/IndustryMaster/EditIndustryMaster.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Box, Field, Input, Stack } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useState } from "react";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
import { useUpdateIndustryMasterMutation } from "../../../Redux/Service/industry.master.service";
|
||||
|
||||
function EditIndustryMaster({ id, localData, refetch, categories }: { id: number, localData: any, refetch: VoidFunction, categories: any }) {
|
||||
const [jobtype, setJobType] = useState("");
|
||||
const [updateIndustryMaster, { isLoading }] = useUpdateIndustryMasterMutation()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
const template = localData?.find((item: any) => item.id === id);
|
||||
if (template) {
|
||||
setJobType(template.en_name);
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!jobtype.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: id,
|
||||
en_name: jobtype,
|
||||
categories_masters_xid: categories
|
||||
};
|
||||
|
||||
try {
|
||||
await updateIndustryMaster(payload);
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Box bg={"transparent"} onClick={handleOpenModal}>
|
||||
<Edit />
|
||||
</Box>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Job Type
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={jobtype}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={(e) => setJobType(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditIndustryMaster;
|
||||
180
src/Pages/MasterModule/IndustryMaster/IndustryMasterList.tsx
Normal file
180
src/Pages/MasterModule/IndustryMaster/IndustryMasterList.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
// import { InputGroup } from "../../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useGetIndustryMasterQuery, useIndustryMasterToggleMutation } from "../../../Redux/Service/industry.master.service";
|
||||
import EditIndustryMaster from "./EditIndustryMaster";
|
||||
import AddIndustryMaster from "./AddIndustryMaster";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { useDebounce } from "../../../components/Hooks/useDebounce";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Agency Name": "Lorem Ipsum",
|
||||
// "RC no.": "Lorem Ipsum",
|
||||
// "State": "Lorem Ipsum",
|
||||
// "RC Status": "Active",
|
||||
// "Registered Office Address": "Lorem Ipsum",
|
||||
// "Website/Domain": "Lorem Ipsum",
|
||||
// "GST no.": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <ViewAgencyMaster/>
|
||||
// <EditAgencyMaster />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"}/>
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const IndustryMasterList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [industryMasterToggle] = useIndustryMasterToggleMutation()
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isError, isFetching } = useGetIndustryMasterQuery(queryArgs)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await industryMasterToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Someting went wrong.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const title = agency.en_name?.toLowerCase().includes(searchLower);
|
||||
return title;
|
||||
});
|
||||
|
||||
const managepost = filteredData?.map((agency: any, index: number) => ({
|
||||
'id': agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Title": agency.en_name,
|
||||
"is_active": agency.is_active,
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
{/* <ViewAgencyMaster agency={localData} id={agency.id} /> */}
|
||||
<EditIndustryMaster id={agency.id} localData={localData} refetch={refetch} categories={agency.categories_masters_xid} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={"teal"}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id, Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("Fetched data:", data);
|
||||
// console.log("Local data:", localData);
|
||||
// console.log("Managepost data:", managepost);
|
||||
// }, [data, localData, managepost]);
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Industry Master
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
{/* <ViewAgencyAddModel /> */}
|
||||
<AddIndustryMaster refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name"]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost || []}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default IndustryMasterList
|
||||
104
src/Pages/MasterModule/JobStatus/EditJobStatusModel.tsx
Normal file
104
src/Pages/MasterModule/JobStatus/EditJobStatusModel.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Box, Field, Input, Stack } from "@chakra-ui/react"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
// import { FaRegEdit } from "react-icons/fa";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useState } from "react";
|
||||
import { useUpdateJobStatusMutation } from "../../../Redux/Service/job.status";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
|
||||
function EditJobStatusModel({ localData, refetch }: { localData: any, refetch: VoidFunction }) {
|
||||
const [updateJobStatus, { isLoading }] = useUpdateJobStatusMutation()
|
||||
const [title, setTitle] = useState(localData.translation.title);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
console.log(localData);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
if (localData) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title field cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: localData?.id,
|
||||
title: title
|
||||
};
|
||||
|
||||
try {
|
||||
await updateJobStatus(payload).unwrap();
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Box bg={"transparent"} onClick={handleOpenModal}>
|
||||
<Edit />
|
||||
</Box>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Edit</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Job Status</Field.Label>
|
||||
<Input
|
||||
value={title}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot >
|
||||
)
|
||||
}
|
||||
|
||||
export default EditJobStatusModel
|
||||
@@ -1,12 +1,168 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import JobStatusAddModel from "./JobStatusAddModel";
|
||||
import EditJobStatusModel from "./EditJobStatusModel";
|
||||
import { useGetJobStatusQuery, useJobStatusToggleMutation } from "../../../Redux/Service/job.status";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { toaster, Toaster } from "../../../components/ui/toaster";
|
||||
import { useDebounce } from "../../../components/Hooks/useDebounce";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Title": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <EditJobStatusModel />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"} />
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const JobStatus = () => {
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
JobStatus
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default JobStatus
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isError, isFetching } = useGetJobStatusQuery(queryArgs)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [jobStatusToggle] = useJobStatusToggleMutation()
|
||||
console.log(data?.data.data)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await jobStatusToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Someting went wrong.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
return (agency.job_status_translation.map((item: any) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const title = item.title?.toLowerCase().includes(searchLower);
|
||||
return title;
|
||||
}))
|
||||
});
|
||||
|
||||
const managepost = filteredData?.flatMap((agency: any, index: number) => (agency.job_status_translation.map((translation: any) => ({
|
||||
'id': agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Title": translation.title,
|
||||
"is_active": agency.is_active,
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
{/* <ViewAgencyMaster agency={localData} id={agency.id} /> */}
|
||||
<EditJobStatusModel localData={{ ...agency, translation }} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={"teal"}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id, Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}))));
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Job Status
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<JobStatusAddModel refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default JobStatus
|
||||
103
src/Pages/MasterModule/JobStatus/JobStatusAddModel.tsx
Normal file
103
src/Pages/MasterModule/JobStatus/JobStatusAddModel.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { useCreateJobStatusPostMutation } from "../../../Redux/Service/job.status"
|
||||
import { toaster } from "../../../components/ui/toaster"
|
||||
import { useState } from "react"
|
||||
|
||||
function JobStatusAddModel({ refetch }: { refetch: VoidFunction }) {
|
||||
const [title, setTitle] = useState('')
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createJobStatusPost, { isLoading }] = useCreateJobStatusPostMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title field cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title: title,
|
||||
};
|
||||
|
||||
try {
|
||||
await createJobStatusPost(payload).unwrap();
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
setTitle('')
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Job Status</Field.Label>
|
||||
<Input
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default JobStatusAddModel
|
||||
111
src/Pages/MasterModule/JobType/EditJobModel.tsx
Normal file
111
src/Pages/MasterModule/JobType/EditJobModel.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Box, Field, Input, Stack } from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useState } from "react";
|
||||
import { useUpdateJobTypeMutation } from "../../../Redux/Service/job.type.service";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
|
||||
function EditJobeModel({ id, localData, refetch }: { id: number, localData: any, refetch: VoidFunction }) {
|
||||
const [jobtype, setJobType] = useState("");
|
||||
const [updateJobType, { isLoading }] = useUpdateJobTypeMutation()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
const template = localData?.find((item: any) => item.id === id);
|
||||
if (template) {
|
||||
setJobType(template.en_name);
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!jobtype.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: id,
|
||||
en_name: jobtype,
|
||||
};
|
||||
|
||||
try {
|
||||
await updateJobType(payload);
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Box bg={"transparent"} onClick={handleOpenModal}>
|
||||
<Edit />
|
||||
</Box>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Job Type
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={jobtype}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={(e) => setJobType(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditJobeModel;
|
||||
102
src/Pages/MasterModule/JobType/JobAddModel.tsx
Normal file
102
src/Pages/MasterModule/JobType/JobAddModel.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { useState } from "react";
|
||||
import { useCreateJobTypePostMutation } from "../../../Redux/Service/job.type.service";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
|
||||
function JobAddModel({ refetch }: { refetch: VoidFunction }) {
|
||||
const [jobType, setJobType] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createJobTypePost, { isLoading }] = useCreateJobTypePostMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!jobType.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
en_name: jobType,
|
||||
};
|
||||
|
||||
try {
|
||||
await createJobTypePost(payload);
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Job Type</Field.Label>
|
||||
<Input
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={jobType}
|
||||
onChange={(e) => setJobType(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default JobAddModel
|
||||
@@ -1,12 +1,166 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
// import { InputGroup } from "../../../components/ui/input-group";
|
||||
// import { LuSearch } from "react-icons/lu";
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import JobAddModel from "./JobAddModel";
|
||||
import EditJobeModel from "./EditJobModel";
|
||||
import { JobTypeData, useGetJobTypeQuery, useJobTypeToggleMutation } from "../../../Redux/Service/job.type.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { toaster, Toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Title": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <EditJobeModel />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"} />
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const JobType = () => {
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
JobType
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default JobType
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, refetch, isFetching, isError } = useGetJobTypeQuery(currentPage)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [jobTypeToggle] = useJobTypeToggleMutation()
|
||||
console.log('DATA', data?.data.data);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const title = agency.en_name?.toLowerCase().includes(searchLower);
|
||||
return title;
|
||||
});
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await jobTypeToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please try again later",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const managepost = filteredData?.map((agency: JobTypeData, index: number) => ({
|
||||
'id': (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Sr. No": index + 1,
|
||||
"Title": agency.en_name,
|
||||
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
<EditJobeModel id={agency.id} localData={localData} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={'teal'}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id.toString(), Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Job Type
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={(value) => {
|
||||
setSearchTerm(value);
|
||||
// setCurrentPage(1);
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<JobAddModel refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default JobType
|
||||
319
src/Pages/MasterModule/TemplateMaster/EditTemplateModel.tsx
Normal file
319
src/Pages/MasterModule/TemplateMaster/EditTemplateModel.tsx
Normal file
@@ -0,0 +1,319 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
} from "../../../components/ui/dialog";
|
||||
import {
|
||||
Box,
|
||||
Field,
|
||||
Input,
|
||||
Stack,
|
||||
} from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { FiUpload } from "react-icons/fi";
|
||||
import { useState } from "react";
|
||||
// import { FaRegEdit } from "react-icons/fa";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
// import { Template } from "../../../Redux/Service/template.master.service";
|
||||
import axios from "axios";
|
||||
|
||||
const IMGURL = import.meta.env.VITE_IMG_TEMPLATES
|
||||
const APIURL = import.meta.env.VITE_API_URL
|
||||
|
||||
function EditTemplateModel({ id, localData, refetch }: { id: number, localData: any, refetch: VoidFunction }) {
|
||||
const [title, setTitle] = useState("");
|
||||
const [subTitle, setSubTitle] = useState("");
|
||||
const [userType, setUserType] = useState<number | "">("");
|
||||
const [images, setImages] = useState<(File | string)[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const [objectURLs, setObjectURLs] = useState<string[]>([]); // Store object URLs separately
|
||||
// const [updateTemplateMaster] = useUpdateTemplateMasterMutation()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
// const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
console.log(images);
|
||||
|
||||
const handleImageChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const file = event.target.files[0];
|
||||
if (!["image/jpeg", "image/jpg", "image/png"].includes(file.type)) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Only JPEG, JPG, and PNG files are allowed.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// setImages((prevImages) => [...prevImages, file]);
|
||||
if (file) {
|
||||
setImages([file])
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenModal = () => {
|
||||
const template = localData?.find((item: any) => item.id === id);
|
||||
if (template) {
|
||||
// setSelectedTemplate(template);
|
||||
setTitle(template.post_template_translate.length > 0 ? template.post_template_translate[0].title : "");
|
||||
setSubTitle(template.post_template_translate.length > 0 ? template.post_template_translate[0].sub_title : "");
|
||||
setUserType(template.principle_type_xid?.toString() || "");
|
||||
|
||||
// Convert image URLs to File objects if needed
|
||||
const templateImages = template.post_template_image.map((img: any) => `${IMGURL}${img.image_name}`);
|
||||
setImages(templateImages);
|
||||
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title.trim() || !subTitle.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title and Subtitle cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (userType === "" || isNaN(Number(userType))) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please select a valid user type.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// const newImages = images.filter((image) => image instanceof File);
|
||||
|
||||
if (images.length === 0) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please upload at least one image.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("id", `${id}`);
|
||||
formData.append("principle_type_xid", `${userType}`);
|
||||
formData.append("title", title);
|
||||
formData.append("sub_title", subTitle);
|
||||
|
||||
images.forEach((image, index) => {
|
||||
if (image instanceof File) {
|
||||
formData.append(`image_name[${index}]`, image, image.name);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// await updateTemplateMaster(formData);
|
||||
if (token) {
|
||||
await axios.post(`${APIURL}/template-update`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'access-token': `${token}`,
|
||||
},
|
||||
// withCredentials: true,
|
||||
});
|
||||
}
|
||||
setIsOpen(false);
|
||||
setLoading(false);
|
||||
refetch()
|
||||
} catch (error: any) {
|
||||
console.error("Error updating template:", error);
|
||||
setLoading(false);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: `${error.response?.data?.message || "Please try again later."}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// const handleSubmit = async () => {
|
||||
// if (!title.trim() || !subTitle.trim()) {
|
||||
// toaster.create({
|
||||
// title: "Error",
|
||||
// description: "Title and Subtitle cannot be empty.",
|
||||
// type: "error",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (userType === "" || isNaN(Number(userType))) {
|
||||
// toaster.create({
|
||||
// title: "Error",
|
||||
// description: "Please select a valid user type.",
|
||||
// type: "error",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (images.length === 0) {
|
||||
// toaster.create({
|
||||
// title: "Error",
|
||||
// description: "Please upload at least one image.",
|
||||
// type: "error",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const existingImageUrls = images.filter((img) => typeof img === "string") as string[];
|
||||
// const newBase64Images = images.filter((img) => typeof img === "string" && img.startsWith("data:image")) as string[];
|
||||
|
||||
// const payload = {
|
||||
// id: id,
|
||||
// principle_type_xid: userType,
|
||||
// title,
|
||||
// sub_title: subTitle,
|
||||
// image_name: [...existingImageUrls, ...newBase64Images], // Send only Base64 strings
|
||||
// };
|
||||
|
||||
// try {
|
||||
// await updateTemplateMaster(payload)
|
||||
// setIsOpen(false)
|
||||
// } catch (error) {
|
||||
// console.error("Error creating template:", error);
|
||||
// alert("Failed to create template");
|
||||
// }
|
||||
// };
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Box bg={"transparent"} onClick={handleOpenModal}>
|
||||
<Edit />
|
||||
</Box>
|
||||
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Edit</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Title</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Title"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Subtitle</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter subtitle"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={subTitle}
|
||||
onChange={(e) => setSubTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Select User Type</Field.Label>
|
||||
<Box bgColor="#EEEEEE" borderRadius="md" p={1}>
|
||||
<select
|
||||
style={{
|
||||
width: "100%",
|
||||
background: "transparent",
|
||||
color: "black",
|
||||
border: "none",
|
||||
fontSize: "12px",
|
||||
height: "30px",
|
||||
outline: "none",
|
||||
}}
|
||||
value={userType}
|
||||
onChange={(e) => setUserType(Number(e.target.value))}
|
||||
>
|
||||
<option value="">Select User Type</option>
|
||||
<option value="2">Recruiter</option>
|
||||
<option value="3">Jobseeker</option>
|
||||
</select>
|
||||
</Box>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Images</Field.Label>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" px={3} bgColor="#EEEEEE" border="none" width="100%" height="50px" cursor="pointer" position="relative">
|
||||
<Input type="file" accept="image/*" opacity={0} position="absolute" bgColor="#EEEEEE" border="none" pl={1} width="100%" height="100%" cursor="pointer" onChange={handleImageChange} />
|
||||
<Box display="flex" gap={2} overflow="hidden">
|
||||
{images.length > 0 ? (
|
||||
images.map((img, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={img instanceof File ? URL.createObjectURL(img) : img}
|
||||
alt={`Uploaded ${index}`}
|
||||
style={{ maxHeight: "40px", maxWidth: "70px", objectFit: "contain" }}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Box width="70px" height="40px" /> // Placeholder to maintain layout
|
||||
)}
|
||||
</Box>
|
||||
<FiUpload color="#000" />
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* <Input placeholder="" bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" /> */}
|
||||
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={loading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot >
|
||||
);
|
||||
}
|
||||
|
||||
export default EditTemplateModel;
|
||||
263
src/Pages/MasterModule/TemplateMaster/TemplateAddModel.tsx
Normal file
263
src/Pages/MasterModule/TemplateMaster/TemplateAddModel.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Box, Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { FiUpload } from "react-icons/fi";
|
||||
import { useEffect, useState } from "react";
|
||||
// import { useCreateTemplatePostMutation } from "../../../Redux/Service/template.master.service";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster"
|
||||
import axios from "axios";
|
||||
|
||||
const APIURL = import.meta.env.VITE_API_URL
|
||||
|
||||
function TemplateAddModel({ refetch }: { refetch: VoidFunction }) {
|
||||
const [title, setTitle] = useState("");
|
||||
const [subTitle, setSubTitle] = useState("");
|
||||
const [userType, setUserType] = useState<number | "">("");
|
||||
const [images, setImages] = useState<(File | string)[]>([]);
|
||||
// const [createTemplatePost] = useCreateTemplatePostMutation()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setTitle("");
|
||||
setSubTitle("");
|
||||
setUserType("");
|
||||
setImages([]);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
if (!token) {
|
||||
console.error("No token found in localStorage!");
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true); // Open modal when clicking "Add"
|
||||
};
|
||||
|
||||
|
||||
const handleImageChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const file = event.target.files[0];
|
||||
if (!["image/jpeg", "image/jpg", "image/png"].includes(file.type)) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Only JPEG, JPG, and PNG files are allowed.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// setImages((prevImages) => [...prevImages, file]);
|
||||
if(file){
|
||||
setImages([file])
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title || !subTitle || images.length === 0) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please fill in all required fields and upload at least one image.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (userType === "" || isNaN(Number(userType))) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please select a valid user type.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// const payload = {
|
||||
// id: id,
|
||||
// principle_type_xid: userType,
|
||||
// title,
|
||||
// sub_title: subTitle,
|
||||
// image_name: images.filter((img) => typeof img === "string"), // Send only Base64 strings
|
||||
// };
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("principle_type_xid", `${userType}`);
|
||||
formData.append("title", title);
|
||||
formData.append("sub_title", subTitle);
|
||||
|
||||
images.forEach((image, index) => {
|
||||
if (image instanceof File) {
|
||||
formData.append(`image_name[${index}]`, image, image.name); // Ensure indexed naming
|
||||
}
|
||||
});
|
||||
|
||||
if (token) {
|
||||
const payload = JSON.parse(atob(token.split(".")[1]));
|
||||
console.log("Token Payload:", payload);
|
||||
}
|
||||
|
||||
try {
|
||||
// await createTemplatePost(formData)
|
||||
if (token) {
|
||||
await axios.post(`${APIURL}/template-store`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'access-token': `${token}`,
|
||||
},
|
||||
// withCredentials: true,
|
||||
});
|
||||
}
|
||||
refetch();
|
||||
setTitle("");
|
||||
setSubTitle("");
|
||||
setUserType("");
|
||||
setImages([]);
|
||||
setIsOpen(false)
|
||||
setIsLoading(false);
|
||||
} catch (error: any) {
|
||||
console.error("Error creating template:", error);
|
||||
// alert("Failed to create template");
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: `${error.response?.data?.message || "Please try again later."}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// console.log("Token stored:", window.localStorage.getItem("token"))
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Title</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Title"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Subtitle</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter subtitle"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={subTitle}
|
||||
onChange={(e) => setSubTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Select User Type</Field.Label>
|
||||
<Box bgColor="#EEEEEE" borderRadius="md" p={1}>
|
||||
<select
|
||||
style={{
|
||||
width: "100%",
|
||||
background: "transparent",
|
||||
color: "black",
|
||||
border: "none",
|
||||
fontSize: "12px",
|
||||
height: "30px",
|
||||
outline: "none",
|
||||
}}
|
||||
value={userType}
|
||||
onChange={(e) => setUserType(Number(e.target.value))}
|
||||
>
|
||||
<option value="">Select User Type</option>
|
||||
<option value="2">Recruiter</option>
|
||||
<option value="3">Jobseeker</option>
|
||||
</select>
|
||||
</Box>
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Images</Field.Label>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" px={3} bgColor="#EEEEEE" border="none" width="100%" height="50px" cursor="pointer" position="relative">
|
||||
<Input type="file" accept="image/*" opacity={0} position="absolute" bgColor="#EEEEEE" border="none" pl={1} width="100%" height="100%" cursor="pointer" onChange={handleImageChange} />
|
||||
<Box display="flex" gap={2} overflow="hidden">
|
||||
{images.length > 0 ? (
|
||||
images.map((img, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={img instanceof File ? URL.createObjectURL(img) : img}
|
||||
alt={`Uploaded ${index}`}
|
||||
style={{ maxHeight: "40px", maxWidth: "70px", objectFit: "contain" }}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Box width="70px" height="40px" /> // Placeholder to maintain layout
|
||||
)}
|
||||
</Box>
|
||||
<FiUpload color="#000" />
|
||||
</Box>
|
||||
|
||||
{/* <Input placeholder="" bgColor="#EEEEEE" color="black" border="none" pl={1} fontSize="12px" height="30px" /> */}
|
||||
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default TemplateAddModel
|
||||
@@ -1,12 +1,205 @@
|
||||
import { Box, HStack, Image, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
// import img from "../../../assets/waterfall.jpg"
|
||||
// import Templateimg from "../../../assets/Template_img.png"
|
||||
import TemplateAddModel from "./TemplateAddModel";
|
||||
import EditTemplateModel from "./EditTemplateModel";
|
||||
import { Template, useGetTemplateMasterQuery, useTemplateMasterToggleMutation } from "../../../Redux/Service/template.master.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { toaster, Toaster } from "../../../components/ui/toaster";
|
||||
import { delay } from "../../../components/Utils";
|
||||
|
||||
const APIURL = import.meta.env.VITE_IMG_TEMPLATES
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"User Type",
|
||||
"Images",
|
||||
"Action"
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Title": "Lorem Ipsum",
|
||||
// "Images": (
|
||||
// // <Image w={50} src={img} />
|
||||
// <HStack >
|
||||
// <Image rounded={'lg'} w={100} h={50} src={img} />
|
||||
// <Image rounded={'lg'} w={100} h={50} src={Templateimg} />
|
||||
// </HStack>
|
||||
// ),
|
||||
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <EditTemplateModel />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"} />
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const TemplateMaster = () => {
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
TemplateMaster
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default TemplateMaster
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, refetch, isFetching, isError } = useGetTemplateMasterQuery(currentPage)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [templateMasterToggle] = useTemplateMasterToggleMutation();
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
console.log('DATA', data?.data.data);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await templateMasterToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
await delay(500);
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please try again later",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) =>
|
||||
agency.post_template_translate[0].title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const activeCount = filteredData?.filter((a: any) => a.is_active === 1).length ?? 0;
|
||||
|
||||
const managepost = filteredData?.map((agency: Template, index: number) => {
|
||||
const isOnlyActive = activeCount === 1 && Number(agency.is_active) === 1;
|
||||
|
||||
return {
|
||||
'id': agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Title": agency.post_template_translate.length > 0
|
||||
? agency.post_template_translate[0].title
|
||||
: "N/A",
|
||||
"User Type": agency.principle_type_xid === 2 ? 'Recruiter' : 'Job Seeker',
|
||||
"Images": (
|
||||
// <Image w={50} src={img} />
|
||||
<HStack>
|
||||
{agency.post_template_image.map((img) => (
|
||||
<Image key={img.id} rounded={'lg'} w={100} h={50} src={`${APIURL}${img.image_name}`} />
|
||||
))}
|
||||
|
||||
{/* <Image rounded={'lg'} w={100} h={50} src={Templateimg} /> */}
|
||||
</HStack>
|
||||
),
|
||||
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
<EditTemplateModel id={agency.id} localData={localData} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={'teal'}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id.toString(), Number(agency.is_active ?? 0))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
disabled={isOnlyActive}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Template Master
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
{/* <InputGroup
|
||||
startElement={
|
||||
<LuSearch fontSize={"xs"} style={{ position: 'relative', left: '10px' }} />
|
||||
}
|
||||
color={"#000"}
|
||||
>
|
||||
<Input
|
||||
p={3}
|
||||
w={300}
|
||||
bg={"#fff"}
|
||||
colorPalette={"blue"}
|
||||
_focus={{ border: "1px solid #02A0A0" }}
|
||||
rounded={"md"}
|
||||
size={"xs"}
|
||||
fontSize={"sm"}
|
||||
placeholder="Search..."
|
||||
bgColor={'#EEEEEE'}
|
||||
ps={8}
|
||||
/>
|
||||
</InputGroup> */}
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<TemplateAddModel refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default TemplateMaster
|
||||
118
src/Pages/MasterModule/WorkspaceMode/EditWorkModel.tsx
Normal file
118
src/Pages/MasterModule/WorkspaceMode/EditWorkModel.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import {
|
||||
Box,
|
||||
Field,
|
||||
Input,
|
||||
Stack,
|
||||
} from "@chakra-ui/react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
// import { FaRegEdit } from "react-icons/fa";
|
||||
import Edit from "../../../components/ActionIcons/Edit";
|
||||
import { useState } from "react";
|
||||
import { toaster } from "../../../components/ui/toaster";
|
||||
import { useUpdateWorkSpaceMutation } from "../../../Redux/Service/workspace.mode";
|
||||
|
||||
function EditWorkModel({ localData, refetch }: {localData: any, refetch: VoidFunction}) {
|
||||
const [title, setTitle] = useState(localData?.en_name);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [updateWorkSpace, { isLoading }] = useUpdateWorkSpaceMutation()
|
||||
console.log("localData", localData)
|
||||
|
||||
const handleOpenModal = () => {
|
||||
// const template = localData?.find((item: any) => item.id === id);
|
||||
if (localData) {
|
||||
setIsOpen(true);
|
||||
// setTitle(localData?.localData.en_name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title field cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: localData?.id,
|
||||
en_name: title
|
||||
};
|
||||
|
||||
try {
|
||||
await updateWorkSpace(payload).unwrap();
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
<Box bg={"transparent"} onClick={handleOpenModal}>
|
||||
<Edit />
|
||||
</Box>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"auto"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Workspace Mode
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={title}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditWorkModel;
|
||||
110
src/Pages/MasterModule/WorkspaceMode/WorkAddModel.tsx
Normal file
110
src/Pages/MasterModule/WorkspaceMode/WorkAddModel.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { IoMdAdd } from "react-icons/io"
|
||||
import { Button } from "../../../components/ui/button"
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCreateWorkspacePostMutation } from "../../../Redux/Service/workspace.mode";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
function WorkAddModel({ refetch }: { refetch: VoidFunction }) {
|
||||
const [title, setTitle] = useState('')
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createWorkspacePost, { isLoading }] = useCreateWorkspacePostMutation()
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title.trim()) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Title field cannot be empty.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
en_name: title,
|
||||
};
|
||||
|
||||
try {
|
||||
await createWorkspacePost(payload);
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
setTitle('')
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setTitle("");
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
|
||||
<DialogRoot placement="center" open={isOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <Button bg={"transparent"} size="sm">
|
||||
<MdOutlineRemoveRedEye style={{ cursor: "pointer", fontSize: "16px" }} />
|
||||
</Button> */}
|
||||
<Button px={5} size={"xs"} bg={"#02A0A0"} onClick={handleOpenModal}>
|
||||
<IoMdAdd /> <Text>Add</Text>
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
height={'auto'}
|
||||
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">Add</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Workspace Mode</Field.Label>
|
||||
<Input
|
||||
placeholder=""
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot >
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkAddModel
|
||||
@@ -1,12 +1,163 @@
|
||||
import { Box, HStack, Text } from "@chakra-ui/react";
|
||||
import MainFrame from "../../../components/MainFrame"
|
||||
import DataTable from "../../../components/DataTable";
|
||||
import { Switch } from "../../../components/ui/switch";
|
||||
import WorkAddModel from "./WorkAddModel";
|
||||
import EditWorkModel from "./EditWorkModel";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useGetWorkSpaceModeQuery, useWorkspaceToggleMutation } from "../../../Redux/Service/workspace.mode";
|
||||
import SearchComponent from "../../../components/SearchComponent";
|
||||
import { Toaster, toaster } from "../../../components/ui/toaster";
|
||||
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Title",
|
||||
"Action"
|
||||
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Title": "Lorem Ipsum",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
// <EditWorkModel />
|
||||
// <Box>
|
||||
// <Switch colorPalette={'teal'} size={"xs"} />
|
||||
// </Box>
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const WorkspaceMode = () => {
|
||||
return (
|
||||
|
||||
<MainFrame >
|
||||
WorkspaceMode
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default WorkspaceMode
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, refetch, isError, isFetching } = useGetWorkSpaceModeQuery(currentPage)
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [workspaceToggle] = useWorkspaceToggleMutation()
|
||||
|
||||
console.log("Workspace Data", data?.data.data)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.data) {
|
||||
setLocalData(data?.data.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleToggle = async (agencyId: string, currentStatus: number) => {
|
||||
const newStatus = currentStatus ? 0 : 1;
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
|
||||
)
|
||||
);
|
||||
try {
|
||||
await workspaceToggle({ id: agencyId, is_active: newStatus }).unwrap();
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Status updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
} catch (error) {
|
||||
console.error("Error updating privacy policy:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please try again later.",
|
||||
type: "error",
|
||||
});
|
||||
setLocalData((prevData) =>
|
||||
prevData.map((agency) =>
|
||||
agency.id === agencyId ? { ...agency, is_active: currentStatus } : agency
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const title = agency.en_name?.toLowerCase().includes(searchLower);
|
||||
return title;
|
||||
});
|
||||
|
||||
const managepost = filteredData?.map((agency: any, index: number) => ({
|
||||
'id': agency.id,
|
||||
"Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1,
|
||||
"Title": agency.en_name.length > 12
|
||||
? agency.en_name.slice(0, 12) + '...'
|
||||
: agency.en_name,
|
||||
"is_active": agency.is_active,
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
{/* <ViewAgencyMaster agency={localData} id={agency.id} /> */}
|
||||
<EditWorkModel localData={agency} refetch={refetch} />
|
||||
<Box>
|
||||
<Switch
|
||||
colorPalette={"teal"}
|
||||
size={"xs"}
|
||||
onChange={() => handleToggle(agency.id, Number(agency.is_active))}
|
||||
checked={Boolean(Number(agency.is_active))}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
|
||||
<MainFrame>
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
Workspace Mode
|
||||
</Text>
|
||||
|
||||
<HStack >
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={(value) => {
|
||||
setSearchTerm(value);
|
||||
// setCurrentPage(1);
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<WorkAddModel refetch={refetch} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
export default WorkspaceMode
|
||||
@@ -2,9 +2,9 @@ import MainFrame from "../../components/MainFrame"
|
||||
|
||||
const MyProfile = () => {
|
||||
return (
|
||||
|
||||
|
||||
<MainFrame >
|
||||
MyProfile
|
||||
MyProfile
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
|
||||
248
src/Pages/Profile/ChangePassword.tsx
Normal file
248
src/Pages/Profile/ChangePassword.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
|
||||
import { DialogCloseTrigger, Field, IconButton, Input, Stack, Text } from "@chakra-ui/react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle } from "../../components/ui/dialog";
|
||||
// import EnterPassword from "./EnterPassword";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toaster, Toaster } from "../../components/ui/toaster";
|
||||
import { useNewPasswordSetMutation } from "../../Redux/Service/profile.password";
|
||||
import { LuEye, LuEyeOff } from "react-icons/lu";
|
||||
import { InputGroup } from "../../components/ui/input-group";
|
||||
|
||||
type EnterPasswordProps = {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
type FormData = {
|
||||
new_password: string;
|
||||
confirm_password: string;
|
||||
};
|
||||
|
||||
function Changepassword({ onClose, isOpen }: EnterPasswordProps) {
|
||||
const [showOldPassword, setShowOldPassword] = useState(false);
|
||||
const [showNewPassword, setShowNewPassword] = useState(false);
|
||||
const [newPasswordSet] = useNewPasswordSetMutation()
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setError,
|
||||
clearErrors,
|
||||
watch,
|
||||
reset,
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
}
|
||||
});
|
||||
|
||||
const newPassword = watch("new_password");
|
||||
const confirmPassword = watch("confirm_password");
|
||||
|
||||
useEffect(() => {
|
||||
if (newPassword && confirmPassword && newPassword !== confirmPassword) {
|
||||
setError("confirm_password", {
|
||||
type: "manual",
|
||||
message: "Passwords do not match"
|
||||
});
|
||||
} else if (confirmPassword) {
|
||||
clearErrors("confirm_password");
|
||||
}
|
||||
}, [newPassword, confirmPassword, setError, clearErrors]);
|
||||
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
|
||||
if (data.new_password === '' || data.confirm_password === '') {
|
||||
return
|
||||
}
|
||||
|
||||
if (data.new_password !== data.confirm_password) {
|
||||
setError("confirm_password", {
|
||||
type: "manual",
|
||||
message: "Passwords do not match"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('ERROR', errors)
|
||||
console.log('Change submitted:', data);
|
||||
|
||||
clearErrors("confirm_password");
|
||||
|
||||
setIsLoading(true);
|
||||
const payload = {
|
||||
new_password: data.new_password,
|
||||
confirm_password: data.confirm_password
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await newPasswordSet(payload).unwrap()
|
||||
|
||||
if (res.status === 'success') {
|
||||
toaster.create({
|
||||
title: "Password changed Successfully",
|
||||
type: "success",
|
||||
});
|
||||
onClose()
|
||||
} else {
|
||||
toaster.create({
|
||||
title: res.data.message || "Invalid password",
|
||||
type: "error",
|
||||
});
|
||||
|
||||
}
|
||||
reset()
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: error.data.message || "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DialogRoot
|
||||
placement="center"
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => !open && onClose()}
|
||||
>
|
||||
|
||||
{/* <DialogTrigger asChild>
|
||||
<Button bg="#02A0A0" size={'2xs'} color={"#fff"} px={2} >
|
||||
Change Password
|
||||
</Button>
|
||||
</DialogTrigger> */}
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
p={2}
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" pt={3} pb={2} >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>CHANGE PASSWORD</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<DialogBody bg="white" pt={5}>
|
||||
<Stack p={2} >
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">New password</Field.Label>
|
||||
<InputGroup
|
||||
width={'100%'}
|
||||
endElement={
|
||||
<IconButton
|
||||
aria-label={showOldPassword ? "Hide password" : "Show password"}
|
||||
size="sm"
|
||||
// variant="outline"
|
||||
onClick={() => setShowOldPassword(!showOldPassword)}
|
||||
_hover={{ bg: "transparent" }}
|
||||
height={'fit-content'}
|
||||
mr={2}
|
||||
>
|
||||
{showOldPassword ? <LuEye /> : <LuEyeOff />}
|
||||
</IconButton>
|
||||
}>
|
||||
<Input
|
||||
color="black"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
type={showOldPassword ? "text" : "password"}
|
||||
border={errors.new_password ? "1px solid red" : "1px solid grey"}
|
||||
{...register("new_password", {
|
||||
required: "Password is required",
|
||||
minLength: {
|
||||
value: 8,
|
||||
message: "Password must be at least 8 characters",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</InputGroup>
|
||||
{/* <IconButton
|
||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? <LuEye /> : <LuEyeOff />}
|
||||
</IconButton> */}
|
||||
|
||||
{errors.new_password && (
|
||||
<Text color="red.500" fontSize="xs" mt={1}>
|
||||
{errors.new_password.message}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Confirm password</Field.Label>
|
||||
<InputGroup
|
||||
width={'100%'}
|
||||
endElement={
|
||||
<IconButton
|
||||
aria-label={showNewPassword ? "Hide password" : "Show password"}
|
||||
size="sm"
|
||||
// variant="outline"
|
||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||
_hover={{ bg: "transparent" }}
|
||||
height={'fit-content'}
|
||||
mr={2}
|
||||
>
|
||||
{showNewPassword ? <LuEye /> : <LuEyeOff />}
|
||||
</IconButton>
|
||||
}>
|
||||
<Input
|
||||
color="black"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
type={showNewPassword ? "text" : "password"}
|
||||
border={errors.confirm_password ? "1px solid red" : "1px solid grey"}
|
||||
{...register("confirm_password", {
|
||||
required: "Please confirm your password",
|
||||
validate: (value) =>
|
||||
value === newPassword || "Passwords do not match",
|
||||
})}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{errors.confirm_password && (
|
||||
<Text color="red.500" fontSize="xs" mt={1}>
|
||||
{errors.confirm_password.message}
|
||||
</Text>
|
||||
)}
|
||||
</Field.Root>
|
||||
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
|
||||
>
|
||||
{/* <EnterPassword /> */}
|
||||
<Button
|
||||
loading={isLoading}
|
||||
mt={6}
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color="white"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
<Toaster />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Changepassword
|
||||
265
src/Pages/Profile/EnterOTP.tsx
Normal file
265
src/Pages/Profile/EnterOTP.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle } from "../../components/ui/dialog"
|
||||
import { Box, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { useEffect, useState } from "react";
|
||||
// import { BiSolidEdit } from "react-icons/bi";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
import { useGetProfileQuery, useResendOtpMutation, useVerifyOTPMutation } from "../../Redux/Service/profile.password";
|
||||
import { useForm } from "react-hook-form";
|
||||
import Changepassword from "./ChangePassword";
|
||||
|
||||
type EnterOTPProps = {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
type FormData = {
|
||||
otp: string[];
|
||||
};
|
||||
|
||||
|
||||
function EnterOTP({ onClose, isOpen }: EnterOTPProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [timer, setTimer] = useState(60);
|
||||
const [isResendDisabled, setIsResendDisabled] = useState(true);
|
||||
const [verifyOTP] = useVerifyOTPMutation()
|
||||
const [otpSuccess, setOtpSuccess] = useState(false);
|
||||
const [resendOtp] = useResendOtpMutation()
|
||||
const { data } = useGetProfileQuery()
|
||||
const id = data?.data.id
|
||||
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
otp: ["", "", "", ""] // Initialize with empty strings for each digit
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
|
||||
if (timer > 0 && isResendDisabled) {
|
||||
interval = setInterval(() => {
|
||||
setTimer((prevTimer) => prevTimer - 1);
|
||||
}, 1000);
|
||||
} else if (timer === 0) {
|
||||
setIsResendDisabled(false);
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [timer, isResendDisabled]);
|
||||
|
||||
const handleResendOTP = async () => {
|
||||
setIsResendDisabled(true);
|
||||
setTimer(60); // Reset timer to 1 minute
|
||||
|
||||
try {
|
||||
|
||||
const res = await resendOtp({ mobile_number: Number(id) }).unwrap()
|
||||
|
||||
if (res.status === 200) {
|
||||
toaster.create({
|
||||
title: "OTP resent successfully",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
toaster.create({
|
||||
title: res.data.message || "Failed to resend OTP",
|
||||
type: "error",
|
||||
});
|
||||
setIsResendDisabled(false); // Enable button if failed
|
||||
}
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: error.response?.data?.message || "Failed to resend OTP",
|
||||
type: "error",
|
||||
});
|
||||
setIsResendDisabled(false); // Enable button if error
|
||||
}
|
||||
};
|
||||
|
||||
// Handle change for OTP inputs
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number): void => {
|
||||
const value = e.target.value;
|
||||
|
||||
// Prevent non-numeric input
|
||||
if (/[^0-9]/.test(value)) return;
|
||||
|
||||
// Update the OTP state with the new value
|
||||
// const newOtp = [...otp];
|
||||
// newOtp[index] = value;
|
||||
// setOtp(newOtp);
|
||||
|
||||
if (value && index < 3) {
|
||||
const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement;
|
||||
if (nextInput) nextInput.focus();
|
||||
}
|
||||
|
||||
// Move focus to the next input automatically
|
||||
// if (value && index < otp.length - 1) {
|
||||
// const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement;
|
||||
// if (nextInput) nextInput.focus();
|
||||
// }
|
||||
|
||||
if (value === "" && index > 0) {
|
||||
const prevInput = document.getElementById(`otp-input-${index - 1}`) as HTMLInputElement;
|
||||
if (prevInput) prevInput.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
console.log('ERROR', errors)
|
||||
if (data.otp.length !== 4) {
|
||||
toaster.create({
|
||||
title: "OTP must be 4 digits",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fullOtp = data.otp.join('');
|
||||
console.log('OTP submitted:', fullOtp);
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await verifyOTP({ otp: fullOtp }).unwrap()
|
||||
|
||||
if (res.status === 'success') {
|
||||
toaster.create({
|
||||
title: "OTP Verified Successfully",
|
||||
type: "success",
|
||||
});
|
||||
setOtpSuccess(true);
|
||||
reset();
|
||||
} else {
|
||||
toaster.create({
|
||||
title: res.data.message || "Invalid OTP",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
reset()
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: error.data?.message || "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
setOtpSuccess(false);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRoot placement="center"
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => !open && onClose()}>
|
||||
{/* <DialogTrigger asChild>
|
||||
<Button w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
</DialogTrigger> */}
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
|
||||
// height={'80vh'}
|
||||
// overflow={'scroll'}
|
||||
p={2} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" pt={3} alignSelf="center">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>ENTER OTP</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<DialogBody bg="white" pt={2}>
|
||||
<Text color={"black"} textAlign={"center"}>OTP has been sent successfully</Text>
|
||||
{/* <Box display="flex" flexDirection="row" alignItems="center" justifyContent="center" p={3}>
|
||||
<BiSolidEdit color="black" />
|
||||
<Text color="black" textAlign="center" ml={2}>9619565889</Text>
|
||||
</Box> */}
|
||||
|
||||
|
||||
<Stack direction="row" justify="center" pt={2}>
|
||||
|
||||
{/* 4 OTP Inputs */}
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
id={`otp-input-${index}`}
|
||||
maxW="50px"
|
||||
color="black"
|
||||
textAlign="center"
|
||||
fontSize="20px"
|
||||
placeholder="0"
|
||||
border={errors.otp?.[index] ? "1px solid red" : "1px solid grey"}
|
||||
maxLength={1}
|
||||
{...register(`otp.${index}`, {
|
||||
required: "Digit required",
|
||||
pattern: {
|
||||
value: /^[0-9]$/,
|
||||
message: "Must be a single digit"
|
||||
},
|
||||
onChange: (e) => handleChange(e, index)
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<Box textAlign="center" mt={4}>
|
||||
{isResendDisabled ? (
|
||||
<Text color="gray.500">
|
||||
Resend OTP in {timer} seconds
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
color="#4746F4"
|
||||
textDecoration="underline"
|
||||
fontWeight="bold"
|
||||
cursor="pointer"
|
||||
onClick={handleResendOTP}
|
||||
>
|
||||
Resend OTP
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
|
||||
>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
mt={6}
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color="white"
|
||||
type="submit"
|
||||
>
|
||||
Verify OTP
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
{otpSuccess && (<Changepassword
|
||||
onClose={() => {
|
||||
setOtpSuccess(false);
|
||||
onClose()
|
||||
}}
|
||||
isOpen={otpSuccess}
|
||||
/>)}
|
||||
<Toaster />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EnterOTP
|
||||
129
src/Pages/Profile/EnterPassword.tsx
Normal file
129
src/Pages/Profile/EnterPassword.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"
|
||||
import { Field, Input, Stack, Text } from "@chakra-ui/react"
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import EnterOTP from "./EnterOTP";
|
||||
import { useProfilePasswordMutation } from "../../Redux/Service/profile.password";
|
||||
import { useState } from "react";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
|
||||
type FormData = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
function EnterPassword() {
|
||||
const [profilePassword] = useProfilePasswordMutation();
|
||||
const [isSuccess, setIsSuccess] = useState(false);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm<FormData>();
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
if (!data.password) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await profilePassword({ password: data.password }).unwrap();
|
||||
setIsSuccess(true);
|
||||
reset();
|
||||
setIsDialogOpen(false);
|
||||
// Handle success (e.g., show a success message or redirect)
|
||||
} catch (error: any) {
|
||||
// Handle error (e.g., show an error message)
|
||||
toaster.create({
|
||||
title: error?.data.message || "Invalid password",
|
||||
type: "error",
|
||||
});
|
||||
console.error("Password change failed:", error.data.message);
|
||||
setIsSuccess(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
|
||||
<DialogRoot placement="center"
|
||||
open={isDialogOpen}
|
||||
onOpenChange={(details) => setIsDialogOpen(details.open)}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button bg="#02A0A0" size={'2xs'} color={"#fff"} px={2} >
|
||||
Change Password
|
||||
</Button>
|
||||
|
||||
</DialogTrigger>
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: '90%', md: '400px' }}
|
||||
|
||||
// height={'80vh'}
|
||||
// overflow={'scroll'}
|
||||
p={2} // Reduced padding
|
||||
bgSize={'md'}
|
||||
>
|
||||
<DialogHeader bg="white" pt={3} pb={2} >
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>ENTER PASSWORD</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<DialogBody bg="white" pt={5}>
|
||||
<Stack p={2} >
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">Password</Field.Label>
|
||||
<Input
|
||||
color="black"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
type="password"
|
||||
border={errors.password ? "1px solid red" : "1px solid grey"}
|
||||
{...register("password", {
|
||||
required: "Password is required",
|
||||
minLength: {
|
||||
value: 8,
|
||||
message: "Password must be at least 8 characters",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.password && (
|
||||
<Text color="red.500" fontSize="xs" mt={1}>
|
||||
{errors.password.message}
|
||||
</Text>
|
||||
)}
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
|
||||
>
|
||||
{/* <Button w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Save
|
||||
</Button> */}
|
||||
<Button type="submit" w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</form>
|
||||
</DialogContent>
|
||||
</DialogRoot >
|
||||
{isSuccess && (<EnterOTP
|
||||
onClose={() => {
|
||||
setIsSuccess(false);
|
||||
setIsDialogOpen(false);
|
||||
}}
|
||||
isOpen={isSuccess}
|
||||
/>)
|
||||
}
|
||||
<Toaster />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default EnterPassword
|
||||
@@ -1,10 +1,147 @@
|
||||
import MainFrame from "../../components/MainFrame"
|
||||
import { Avatar, Box, HStack, Text, VStack } from "@chakra-ui/react";
|
||||
import { FaCamera } from "react-icons/fa";
|
||||
import EditableInput from "../../components/EditableInput";
|
||||
import MainFrame from "../../components/MainFrame";
|
||||
import { Field } from "../../components/ui/field";
|
||||
// import Changepassword from "./ChangePassword";
|
||||
import EnterPassword from "./EnterPassword";
|
||||
import { useGetProfileQuery } from "../../Redux/Service/profile.password";
|
||||
// import { useUpdateImageMutation } from "../../Redux/Service/myprofie.service";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
import axios from "axios";
|
||||
|
||||
const APIURL = import.meta.env.VITE_API_URL
|
||||
const PROFILEIMGURL = import.meta.env.VITE_IMG_PROFILE
|
||||
|
||||
const Profile = () => {
|
||||
const { data, refetch } = useGetProfileQuery()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [avatarSrc, setAvatarSrc] = useState<string>("");
|
||||
// const [updateImage] = useUpdateImageMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data.profile_photo) {
|
||||
setAvatarSrc(data.data.profile_photo);
|
||||
}
|
||||
}, [data?.data.profile_photo]);
|
||||
|
||||
|
||||
const handleClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
alert("File size exceeds 2MB limit.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Preview the image
|
||||
const previewUrl = URL.createObjectURL(file);
|
||||
setAvatarSrc(previewUrl);
|
||||
|
||||
// Prepare FormData
|
||||
const formData = new FormData();
|
||||
formData.append("profile_photo", file, file.name);
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
try {
|
||||
if (token) {
|
||||
await axios.post(`${APIURL}/profile-image-edit`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'access-token': `${token}`,
|
||||
},
|
||||
// withCredentials: true,
|
||||
});
|
||||
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating image:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
console.log('PROFILE DATA:', data?.data);
|
||||
|
||||
return (
|
||||
<MainFrame >
|
||||
Profile
|
||||
</MainFrame>
|
||||
<HStack alignItems={'flex-start'} justifyContent={'center'} pt={0} h={'89vh'} w={'100%'} >
|
||||
|
||||
<VStack w={'50%'} p={3} rounded={'lg'} mb={3}>
|
||||
|
||||
<HStack shadow={'md'} rounded={'lg'} justifyContent={'space-between'} alignItems={'flex-end'} w={'100%'} px={3} py={3} >
|
||||
<VStack w={'100%'} alignItems={'flex-start'} gap={0}>
|
||||
<Box mb={2} position="relative" width="fit-content"
|
||||
cursor="pointer" onClick={handleClick}>
|
||||
<Avatar.Root size={"2xl"} style={{ display: "flex", width: "50px", height: '50px', justifyContent: 'center' }}>
|
||||
<Avatar.Fallback />
|
||||
{/* <Avatar.Image src="https://i.pinimg.com/736x/d6/cd/0f/d6cd0ffd4634b0763d3958a7325ce26e.jpg" /> */}
|
||||
{avatarSrc && <Avatar.Image src={`${PROFILEIMGURL}${avatarSrc}`} />}
|
||||
</Avatar.Root>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="-2px"
|
||||
left={"39px"}
|
||||
p="2px"
|
||||
>
|
||||
<FaCamera color="#ccc" size={16} />
|
||||
</Box>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</Box>
|
||||
<Text color={"black"} as={'span'} fontSize={'sm'} fontWeight={"bold"}>{`${data?.data?.first_name.charAt(0).toUpperCase()}${data?.data.first_name.slice(1)}`}
|
||||
</Text>
|
||||
{/* <Text color="black" as={'span'} fontSize={'xs'}>
|
||||
Employee ID: <span>#1245679</span>
|
||||
</Text> */}
|
||||
</VStack>
|
||||
|
||||
<EnterPassword />
|
||||
{/* <Changepassword /> */}
|
||||
</HStack>
|
||||
|
||||
|
||||
<VStack w={"100%"} >
|
||||
<Field mt={4} label="First Name" fontSize="xs" required>
|
||||
<EditableInput value={`${data?.data.first_name}`} placeholder="Enter first name" isDisabled />
|
||||
</Field>
|
||||
|
||||
<Field mt={4} label="Last Name" fontSize="xs" required>
|
||||
<EditableInput value={`${data?.data.last_name}`} placeholder="Enter last name" isDisabled />
|
||||
</Field>
|
||||
|
||||
<Field mt={4} label="Mobile Number" fontSize="xs" required>
|
||||
<EditableInput value={`${data?.data.phone_number}`} placeholder="Mobile Number" type='number' isDisabled />
|
||||
</Field>
|
||||
|
||||
</VStack>
|
||||
</VStack>
|
||||
|
||||
</HStack>
|
||||
<Toaster />
|
||||
</MainFrame >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
197
src/Pages/SetNewPassword.tsx
Normal file
197
src/Pages/SetNewPassword.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
HStack,
|
||||
IconButton,
|
||||
Image,
|
||||
Input,
|
||||
Stack,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import logo from "../assets/logo.svg";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { toaster, Toaster } from "../components/ui/toaster";
|
||||
import { InputGroup } from "../components/ui/input-group";
|
||||
import { LuEye, LuEyeOff } from "react-icons/lu";
|
||||
|
||||
const SetNewPassword = () => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
const id = queryParams.get("id");
|
||||
const [showOldPassword, setShowOldPassword] = useState(false);
|
||||
const [showNewPassword, setShowNewPassword] = useState(false);
|
||||
|
||||
const handlePasswordSubmit = async () => {
|
||||
// Validation
|
||||
if (password.length < 8) {
|
||||
toaster.create({
|
||||
title: "Password must be at least 8 characters long",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
toaster.create({
|
||||
title: "Passwords do not match",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/update-password`,
|
||||
{
|
||||
password: password,
|
||||
confirm_password: confirmPassword,
|
||||
id: Number(id),
|
||||
}
|
||||
);
|
||||
|
||||
if (res.data.status === "success") {
|
||||
toaster.create({
|
||||
title: "Password updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
navigate("/login"); // Redirect to login page
|
||||
} else {
|
||||
toaster.create({
|
||||
title: res.data.message || "Failed to update password",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: error.response?.data?.message || "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<VStack w="100%" h="100vh" bg="#ffffff">
|
||||
<HStack
|
||||
boxShadow="rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"
|
||||
w="100%"
|
||||
ps={8}
|
||||
h="7%"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<Image w={50} src={logo} />
|
||||
</HStack>
|
||||
|
||||
<Center w="100%" h="93%" p={8}>
|
||||
<Box p={8} borderWidth={1} borderRadius="lg" boxShadow="lg" w={"400px"}>
|
||||
<Text
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
color="#313039"
|
||||
marginBottom={"20px"}
|
||||
>
|
||||
Create a Password
|
||||
</Text>
|
||||
|
||||
<Stack>
|
||||
<Box mb={3}>
|
||||
<Text color="black" fontSize="12px" mb={2}>
|
||||
New password
|
||||
</Text>
|
||||
<InputGroup
|
||||
width={"100%"}
|
||||
endElement={
|
||||
<IconButton
|
||||
aria-label={
|
||||
showOldPassword ? "Hide password" : "Show password"
|
||||
}
|
||||
size="sm"
|
||||
onClick={() => setShowOldPassword(!showOldPassword)}
|
||||
// _hover={{ bg: "transparent" }}
|
||||
bg={"transparent"}
|
||||
color={"#000"}
|
||||
height={"fit-content"}
|
||||
mr={2}
|
||||
>
|
||||
{showOldPassword ? <LuEyeOff /> : <LuEye />}
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
color="black"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
type={showOldPassword ? "password" : "text"}
|
||||
border="1px solid grey"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
size={"sm"}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text color="black" mb={2} fontSize="12px">
|
||||
Confirm password
|
||||
</Text>
|
||||
<InputGroup
|
||||
width={"100%"}
|
||||
endElement={
|
||||
<IconButton
|
||||
aria-label={
|
||||
showNewPassword ? "Hide password" : "Show password"
|
||||
}
|
||||
size="sm"
|
||||
// variant="outline"
|
||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||
bg={"transparent"}
|
||||
color={"#000"}
|
||||
mr={2}
|
||||
>
|
||||
{showNewPassword ? <LuEyeOff /> : <LuEye />}
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
color="black"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
type={showNewPassword ? "password" : "text"}
|
||||
border="1px solid grey"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
size={"sm"}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Button
|
||||
loading={isLoading}
|
||||
mt={6}
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color="white"
|
||||
onClick={handlePasswordSubmit}
|
||||
>
|
||||
Confirm Password
|
||||
</Button>
|
||||
</Box>
|
||||
</Center>
|
||||
|
||||
<Toaster />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetNewPassword;
|
||||
319
src/Pages/SubAdmin/AddModel.tsx
Normal file
319
src/Pages/SubAdmin/AddModel.tsx
Normal file
@@ -0,0 +1,319 @@
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { Field, Grid, Heading, Input, Stack, Text } from "@chakra-ui/react";
|
||||
import { IoMdAdd } from "react-icons/io";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { PermissionResponse, useCreateSubAdminPostMutation } from "../../Redux/Service/manage.subadmin.service";
|
||||
import { toaster, Toaster } from "../../components/ui/toaster";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function AddModel({ refetch, allPermissions }: { refetch: VoidFunction, allPermissions?: PermissionResponse }) {
|
||||
const [createSubAdminPost, { isLoading }] = useCreateSubAdminPostMutation();
|
||||
|
||||
// State fields
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
// const [userName, setUserName] = useState("");
|
||||
const [dateOfBirth, setDateOfBirth] = useState("");
|
||||
const [gender, setGender] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [phonenumber, setPhonenumber] = useState("");
|
||||
const [permissions, setPermission] = useState<number[]>([]);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
// const [ setIsOpen] = useState(false);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleCheckboxToggle = (permissionId: number) => {
|
||||
setPermission((prevData) =>
|
||||
prevData.includes(permissionId)
|
||||
? prevData.filter((id) => id !== permissionId)
|
||||
: [...prevData, permissionId]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setFirstName("");
|
||||
setLastName("");
|
||||
// setUserName("");
|
||||
setDateOfBirth("");
|
||||
setGender("");
|
||||
setEmail("");
|
||||
setPhonenumber("");
|
||||
setPermission([]);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (
|
||||
!firstName.trim() ||
|
||||
!lastName.trim() ||
|
||||
!dateOfBirth.trim() ||
|
||||
!gender.trim()
|
||||
) {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Please fill in all required fields",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
toaster.create({
|
||||
title: "Invalid Email",
|
||||
description: "Please enter a valid email address",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (phonenumber.length !== 10) {
|
||||
toaster.create({
|
||||
title: "Invalid Phone Number",
|
||||
description: "Phone number must be exactly 10 digits",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
// user_name: userName,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
date_of_birth: dateOfBirth,
|
||||
gender: gender,
|
||||
email_address: email,
|
||||
phone_number: phonenumber,
|
||||
permission: permissions.filter((id) => typeof id === "number"),
|
||||
// created_by: 1,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await createSubAdminPost(payload).unwrap();
|
||||
if (response) {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Sub-admin created successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch();
|
||||
setFirstName("");
|
||||
setLastName("");
|
||||
// setUserName("");
|
||||
setDateOfBirth("");
|
||||
setGender("");
|
||||
setIsOpen(false);
|
||||
}
|
||||
} catch (error:any) {
|
||||
console.error("Error creating sub-admin:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: error ? error.data.message : "Failed to create sub-admin",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
|
||||
<Button
|
||||
rounded={"md"}
|
||||
px={4} py={2}
|
||||
size={"xs"}
|
||||
bg={"#02A0A0"}
|
||||
onClick={handleOpenModal}>
|
||||
<IoMdAdd /> Add
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3}
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Add Sub Admin Account
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
First Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the First Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Last Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Last Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">
|
||||
Username
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Username"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={userName}
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
/> */}
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
DOB
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the DOB"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
type="date"
|
||||
value={dateOfBirth}
|
||||
onChange={(e) => setDateOfBirth(e.target.value)}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Gender
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Gender"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={gender}
|
||||
onChange={(e) => setGender(e.target.value)}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Email Address
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter Email address"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
type="email"
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Phone Number
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter phone number"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
type="tel"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={phonenumber}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (/^\d*$/.test(value)) { // Only allow digits
|
||||
setPhonenumber(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Heading mt={5} color={"#000"} fontSize={"sm"}>
|
||||
Permissions
|
||||
</Heading>
|
||||
</Field.Root>
|
||||
|
||||
<Grid templateColumns="repeat(2, 1fr)" gap={4}>
|
||||
{Array.isArray(allPermissions?.data?.permission)
|
||||
? allPermissions.data.permission.map((permission: any) => (
|
||||
<Checkbox
|
||||
size="sm"
|
||||
color="black"
|
||||
key={permission.id}
|
||||
checked={permissions.includes(permission.id)}
|
||||
onChange={() => handleCheckboxToggle(permission.id)}
|
||||
>
|
||||
<Text fontSize={12}>{permission.app_resource_title}</Text>
|
||||
</Checkbox>
|
||||
))
|
||||
: <Text fontSize={12} color="gray.500">Loading permissions...</Text>
|
||||
}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button
|
||||
size={"xs"}
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color={"#fff"}
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddModel;
|
||||
301
src/Pages/SubAdmin/EditSubAdmin.tsx
Normal file
301
src/Pages/SubAdmin/EditSubAdmin.tsx
Normal file
@@ -0,0 +1,301 @@
|
||||
import { Field, Grid, Heading, Input, Stack, Text } from "@chakra-ui/react";
|
||||
// import { TbEdit } from "react-icons/tb";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import Edit from "../../components/ActionIcons/Edit";
|
||||
import { PermissionResponse, useLazyViewSubAdminQuery, useUpdateSubAdminMutation } from "../../Redux/Service/manage.subadmin.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Toaster, toaster } from "../../components/ui/toaster";
|
||||
|
||||
const resourceIdToLabel: { [key: number]: string } = {
|
||||
1: 'Dashboard',
|
||||
2: 'Manage User',
|
||||
3: 'Manage Post',
|
||||
4: 'Manage Subadmin',
|
||||
5: 'Manage Jobs',
|
||||
6: 'Manage Groups',
|
||||
7: 'Manage Contact Us',
|
||||
8: 'Manage CMS',
|
||||
9: 'My Profile',
|
||||
10: 'Master Module',
|
||||
};
|
||||
|
||||
|
||||
interface ResourceActionLink {
|
||||
id: number;
|
||||
app_resource_xid: number;
|
||||
is_active: boolean;
|
||||
app_resource: {
|
||||
id: number,
|
||||
app_resource_title: string
|
||||
}
|
||||
}
|
||||
|
||||
function EditSubAdmin({ id, refetch, allPermissions }: { id: number, refetch: VoidFunction, allPermissions?: PermissionResponse }) {
|
||||
const [trigger, { data }] = useLazyViewSubAdminQuery();
|
||||
const [updateSubAdmin, {isLoading}] = useUpdateSubAdminMutation()
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [editData, setEditData] = useState<{
|
||||
id: string;
|
||||
unique_id?: string,
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
date_of_birth: string;
|
||||
gender?: string,
|
||||
permission: ResourceActionLink[];
|
||||
}>({
|
||||
id: '',
|
||||
unique_id: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
date_of_birth: '',
|
||||
gender: '',
|
||||
permission: [],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.length && allPermissions?.data?.permission?.length) {
|
||||
const subAdmin = data.data[0];
|
||||
|
||||
const activePermissionIds = subAdmin.get_resource_action_link
|
||||
.filter((perm: any) => perm.is_active)
|
||||
.map((perm: any) => perm.app_resource_xid);
|
||||
|
||||
const mergedPermissions: ResourceActionLink[] = allPermissions.data.permission.map((perm) => ({
|
||||
id: perm.id,
|
||||
app_resource_xid: perm.id,
|
||||
is_active: activePermissionIds.includes(perm.id),
|
||||
app_resource: {
|
||||
id: perm.id,
|
||||
app_resource_title: perm.app_resource_title,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
// Map the API response to editData
|
||||
setEditData({
|
||||
id: subAdmin.id.toString(),
|
||||
unique_id: subAdmin.unique_id,
|
||||
first_name: subAdmin.first_name,
|
||||
last_name: subAdmin.last_name,
|
||||
date_of_birth: formatDateOfBirth(subAdmin.date_of_birth),
|
||||
gender: subAdmin.gender,
|
||||
permission: mergedPermissions,
|
||||
});
|
||||
}
|
||||
}, [data, allPermissions]);
|
||||
|
||||
const formatDateOfBirth = (dob: string): string => {
|
||||
// Convert the date to the desired format with slashes
|
||||
const formattedDate = new Date(dob).toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
// Replace slashes with hyphens
|
||||
return formattedDate.replace(/\//g, '-');
|
||||
};
|
||||
|
||||
const handleOpenModal = () => {
|
||||
trigger(id)
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleCheckboxToggle = (permissionId: number) => {
|
||||
setEditData((prevData) => ({
|
||||
...prevData,
|
||||
permission: prevData.permission.map((permission) =>
|
||||
permission.app_resource_xid === permissionId
|
||||
? { ...permission, is_active: !permission.is_active }
|
||||
: permission
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
console.log('Updated Data:', editData);
|
||||
// Call your API here with the updated editData
|
||||
|
||||
const payload = {
|
||||
id: Number(editData.id),
|
||||
// unique_id: editData.unique_id,
|
||||
first_name: editData.first_name,
|
||||
last_name: editData.last_name,
|
||||
date_of_birth: editData.date_of_birth,
|
||||
gender: editData.gender,
|
||||
permission: editData.permission
|
||||
.filter((p) => p.is_active)
|
||||
.map((p) => p.app_resource_xid),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await updateSubAdmin(payload).unwrap();
|
||||
if (response?.status === "success") {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "FAQ updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Failed to update FAQ",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating template:", error);
|
||||
// alert("Failed to update template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogRoot placement="center" open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}>
|
||||
<DialogTrigger asChild>
|
||||
{/* <FaRegEdit style={{ cursor: "pointer" }} color="#000" /> */}
|
||||
|
||||
<Button bg="transparent" color={"black"} h={"18px"} onClick={handleOpenModal}><Edit /></Button>
|
||||
{/* <Button><FaRegEdit /></Button> */}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
Edit Sub Admin Account
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
{/* <Field.Label color="black" pt={1} fontSize="12px">
|
||||
ID
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the First Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={editData.unique_id}
|
||||
onChange={(e) => setEditData({ ...editData, unique_id: e.target.value })}
|
||||
/> */}
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
First Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the First Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={editData.first_name}
|
||||
onChange={(e) => setEditData({ ...editData, first_name: e.target.value })}
|
||||
/>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Last Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Last Name"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={editData.last_name}
|
||||
onChange={(e) => setEditData({ ...editData, last_name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
DOB
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the DOB"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={editData.date_of_birth}
|
||||
onChange={(e) => setEditData({ ...editData, date_of_birth: e.target.value })}
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Gender
|
||||
</Field.Label>
|
||||
<Input
|
||||
placeholder="Enter the Gender"
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
value={editData.gender}
|
||||
onChange={(e) => setEditData({ ...editData, gender: e.target.value })}
|
||||
/>
|
||||
<Heading mt={5} color={"#000"} fontSize={"sm"}>
|
||||
Permissions
|
||||
</Heading>
|
||||
</Field.Root>
|
||||
<Grid templateColumns="repeat(2, 1fr)" gap={4}>
|
||||
{editData.permission.map((permission) => {
|
||||
const label = resourceIdToLabel[permission.app_resource_xid];
|
||||
return (
|
||||
<Checkbox
|
||||
key={permission.id}
|
||||
size="sm"
|
||||
color="black"
|
||||
checked={permission.is_active}
|
||||
onChange={() => handleCheckboxToggle(permission.app_resource_xid)}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<Text fontSize={12}>{label}</Text>
|
||||
</Checkbox>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
<Button size={"xs"} w="100%" bg="#02A0A0" color={"#fff"} onClick={handleSubmit} disabled={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
<DialogCloseTrigger color="black" onClick={() => setIsOpen(false)} />
|
||||
</DialogContent>
|
||||
<Toaster />
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditSubAdmin;
|
||||
@@ -1,10 +1,204 @@
|
||||
import { Box, HStack, Image, Text } from "@chakra-ui/react"
|
||||
import MainFrame from "../../components/MainFrame"
|
||||
// import { InputGroup } from "../../components/ui/input-group"
|
||||
// import { LuSearch } from "react-icons/lu"
|
||||
import DataTable from "../../components/DataTable"
|
||||
import AlertDailog from "../../components/AlertDailog";
|
||||
// import { RiDeleteBin5Line } from "react-icons/ri";
|
||||
import AddModel from "./AddModel"
|
||||
import EditSubAdmin from "./EditSubAdmin"
|
||||
import ViewSubAdmin from "./ViewSubAdmin"
|
||||
import Delete from "../../components/ActionIcons/Delete"
|
||||
import { PermissionResponse, useDeleteSubAdminPostMutation, useGetPermissionQuery, useGetSubAdminQuery } from "../../Redux/Service/manage.subadmin.service"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Toaster, toaster } from "../../components/ui/toaster"
|
||||
import { useDebounce } from "../../components/Hooks/useDebounce"
|
||||
import SearchComponent from "../../components/SearchComponent"
|
||||
|
||||
|
||||
// table data
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sr. No",
|
||||
"Emp ID",
|
||||
"First Name",
|
||||
"last Name",
|
||||
"DOB",
|
||||
"Gender",
|
||||
"Action",
|
||||
];
|
||||
|
||||
// const managepost: any[] = [
|
||||
// ...Array.from({ length: 12 }, (_, i) => ({
|
||||
// "Sr. No": i + 1,
|
||||
// "Id": 12565,
|
||||
// "First Name": "Kamlesh",
|
||||
// "last Name": "Pandey",
|
||||
// "DOB": "12/01/1987",
|
||||
// "Gender": "Male",
|
||||
// "Action": (
|
||||
// <HStack justifyContent="center">
|
||||
|
||||
// <ViewSubAdmin />
|
||||
// <EditSubAdmin />
|
||||
|
||||
// <AlertDailog
|
||||
// AltertTiggerIcon={() => <Delete />}
|
||||
// alertText="Delete Users"
|
||||
// alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
// alertCaption="are you sure you want to delete ?"
|
||||
// onConfirm={() => {
|
||||
// console.log("User deleted:", i + 1);
|
||||
// }}
|
||||
// />
|
||||
// </HStack>
|
||||
// ),
|
||||
// })),
|
||||
// ];
|
||||
|
||||
const SubAdmin = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
// const { data, refetch } = useGetSubAdminQuery()
|
||||
const { data: permissions } = useGetPermissionQuery()
|
||||
const [localData, setLocalData] = useState<any[]>([]);
|
||||
const [allPermissions, setAllPermissions] = useState<PermissionResponse>();
|
||||
const [deleteModal, setDeleteModal] = useState(false)
|
||||
const [deleteSubAdminPost] = useDeleteSubAdminPostMutation()
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage };
|
||||
const { data, refetch, isError, isFetching } = useGetSubAdminQuery(queryArgs);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data.data) {
|
||||
setLocalData(data?.data.data);
|
||||
setAllPermissions(permissions);
|
||||
}
|
||||
}, [data, permissions]);
|
||||
|
||||
console.log("============================", allPermissions);
|
||||
console.log('localData', localData);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleDeleteAdmin = async (faqId: number) => {
|
||||
try {
|
||||
const response = await deleteSubAdminPost(faqId).unwrap();
|
||||
if (response.success) {
|
||||
toaster.create({
|
||||
title: "Success",
|
||||
description: "Sub Admin deleted successfully",
|
||||
type: "success",
|
||||
});
|
||||
refetch()
|
||||
console.log("Sub Admin deleted successfully:", response);
|
||||
}
|
||||
// Optionally, refetch data or update state after deletion
|
||||
} catch (error) {
|
||||
console.error("Error deleting Sub Admin:", error);
|
||||
toaster.create({
|
||||
title: "Error",
|
||||
description: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const formatDateOfBirth = (dob: string): string => {
|
||||
return new Date(dob).toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const filteredData = localData?.filter((agency) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const userName = agency.user_name?.toLowerCase().includes(searchLower);
|
||||
const firstName = agency.first_name?.toLowerCase().includes(searchLower);
|
||||
const lastName = agency.last_name?.toLowerCase().includes(searchLower);
|
||||
return userName || firstName || lastName;
|
||||
});
|
||||
|
||||
const managepost = filteredData?.map((agency: any, index: number) => ({
|
||||
'id': agency.id,
|
||||
"Sr. No": index + 1,
|
||||
"Emp ID": agency.unique_id,
|
||||
"First Name": agency.first_name,
|
||||
"last Name": agency.last_name,
|
||||
"DOB": formatDateOfBirth(agency.date_of_birth),
|
||||
"Gender": agency.gender,
|
||||
|
||||
"Action": (
|
||||
<HStack justifyContent="center">
|
||||
{/* <EditDetails rowData={{ id: agency.id, question: agency.question, answer: agency.answer }} refetch={refetch} /> */}
|
||||
<ViewSubAdmin id={agency.id} />
|
||||
<EditSubAdmin id={agency.id} refetch={refetch} allPermissions={permissions} />
|
||||
|
||||
<AlertDailog
|
||||
isOpen={deleteModal}
|
||||
AltertTiggerIcon={() => <Delete onClick={() => setDeleteModal(prev => !prev)} />}
|
||||
alertText="Delete sub admin"
|
||||
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
|
||||
alertCaption="are you sure you want to delete ?"
|
||||
onClose={() => setDeleteModal(false)}
|
||||
onConfirm={() => {
|
||||
// console.log("User deleted:", index + 1);
|
||||
setDeleteModal(false);
|
||||
handleDeleteAdmin(agency.id)
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<MainFrame >
|
||||
SubAdmin
|
||||
<Box>
|
||||
<HStack
|
||||
w={"100%"}
|
||||
justifyContent={"space-between"}
|
||||
mb={4}
|
||||
py={0}
|
||||
px={3}
|
||||
>
|
||||
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
|
||||
{/* Manage Post */}
|
||||
</Text>
|
||||
|
||||
<HStack>
|
||||
<SearchComponent
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* <Button bgColor={'#EEEEEE'} pl={3} pr={3}><IoMdAdd /> <Text>Add</Text></Button> */}
|
||||
<AddModel refetch={refetch} allPermissions={permissions} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DataTable
|
||||
sortableColumns={["Name", "Registration Date "]}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={managepost}
|
||||
paginationData={{
|
||||
current_page: data?.data.current_page || 1,
|
||||
last_page: data?.data.last_page || 1,
|
||||
per_page: data?.data.per_page || 10,
|
||||
total: data?.data.total || 0
|
||||
}}
|
||||
onPageChange={handlePageChange}
|
||||
isLoading={isFetching}
|
||||
isError={isError}
|
||||
/>
|
||||
</Box>
|
||||
<Toaster />
|
||||
</MainFrame>
|
||||
)
|
||||
}
|
||||
|
||||
218
src/Pages/SubAdmin/ViewSubAdmin.tsx
Normal file
218
src/Pages/SubAdmin/ViewSubAdmin.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
// import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
DialogBody,
|
||||
DialogCloseTrigger,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogRoot,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import {
|
||||
Field,
|
||||
Grid,
|
||||
Heading,
|
||||
Input,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
// import { MdOutlineRemoveRedEye } from "react-icons/md";
|
||||
// import { FaRegEdit } from "react-icons/fa";
|
||||
import View from "../../components/ActionIcons/View";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {useLazyViewSubAdminQuery } from "../../Redux/Service/manage.subadmin.service";
|
||||
|
||||
function ViewSubAdmin({ id, }: { id: number}) {
|
||||
const [trigger, { data }] = useLazyViewSubAdminQuery();
|
||||
|
||||
const handleView = () => {
|
||||
trigger(id)
|
||||
}
|
||||
|
||||
const viewSubAdmin = data?.data
|
||||
|
||||
const formatDateOfBirth = (dob: string): string => {
|
||||
return new Date(dob).toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
console.log('data', data?.data);
|
||||
return (
|
||||
<DialogRoot placement="center">
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={handleView}
|
||||
bg={'transparent'}
|
||||
color={"black"}
|
||||
>
|
||||
<View />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
{viewSubAdmin?.map((data: any) => (
|
||||
<DialogContent
|
||||
bg={"#fff"}
|
||||
// w={{ lg: "60%", md: "230px" }}
|
||||
w={{ base: "90%", md: "400px" }}
|
||||
height={"80vh"}
|
||||
overflow={"scroll"}
|
||||
overflowX="hidden"
|
||||
p={3} // Reduced padding
|
||||
bgSize={"md"}
|
||||
key={data.id}
|
||||
>
|
||||
<DialogHeader bg="white">
|
||||
<DialogTitle alignSelf="center" color="black" fontSize="14px">
|
||||
View Sub Admin Account
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody bg="white">
|
||||
<Stack py={3}>
|
||||
<Field.Root>
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
First Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.first_name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Last Name
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.last_name}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
ID
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.unique_id}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
DOB
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={formatDateOfBirth(data.date_of_birth)}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Field.Label color="black" pt={1} fontSize="12px">
|
||||
Gender
|
||||
</Field.Label>
|
||||
<Input
|
||||
value={data.gender}
|
||||
bgColor="#EEEEEE"
|
||||
color="black"
|
||||
border="none"
|
||||
pl={1}
|
||||
fontSize="12px"
|
||||
height="30px"
|
||||
readOnly
|
||||
/>
|
||||
<Heading mt={5} color={"#000"} fontSize={"sm"}>
|
||||
Permissions
|
||||
</Heading>
|
||||
</Field.Root>
|
||||
<Grid templateColumns="repeat(2, 1fr)" gap={4}>
|
||||
{data.get_resource_action_link.map((check: any) => (
|
||||
<Checkbox size={"sm"} color={"black"} checked={check.is_active} key={check.id}>
|
||||
<Text fontSize={12}>{check?.app_resource.app_resource_title}</Text>
|
||||
</Checkbox>
|
||||
// <>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// <Text fontSize={12}>Dashboard</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>Manage contact us</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>manage User</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>Manage CMS</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>Manage Post</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>Manage Reports</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>manage Sub-Admin</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// {" "}
|
||||
// <Text fontSize={12}>My profile</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// <Text fontSize={12}>Manage Jobs</Text>{" "}
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// <Text fontSize={12}> manage feedbacks</Text>
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// <Text fontSize={12}>Manage community</Text>{" "}
|
||||
// </Checkbox>
|
||||
// <Checkbox size={"sm"} color={"black"}>
|
||||
// <Text fontSize={12}> Notification</Text>
|
||||
// </Checkbox>
|
||||
// </>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
<DialogFooter display="flex" justifyContent="center" pt={"2"}>
|
||||
{/* <Button w="100%" bg="#02A0A0" color={"#fff"}>
|
||||
Save
|
||||
</Button> */}
|
||||
</DialogFooter>
|
||||
|
||||
<DialogCloseTrigger color="black" />
|
||||
</DialogContent>
|
||||
))}
|
||||
</DialogRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewSubAdmin;
|
||||
219
src/Pages/VerifyEnterOTP.tsx
Normal file
219
src/Pages/VerifyEnterOTP.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
HStack,
|
||||
Image,
|
||||
Input,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import logo from "../assets/logo.svg";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { toaster, Toaster } from "../components/ui/toaster";
|
||||
|
||||
const VerifyOTP = () => {
|
||||
const [otp, setOtp] = useState<string[]>(["", "", "", ""]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [timer, setTimer] = useState(60);
|
||||
const [isResendDisabled, setIsResendDisabled] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const phoneNumber = queryParams.get("phone");
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
|
||||
if (timer > 0 && isResendDisabled) {
|
||||
interval = setInterval(() => {
|
||||
setTimer((prevTimer) => prevTimer - 1);
|
||||
}, 1000);
|
||||
} else if (timer === 0) {
|
||||
setIsResendDisabled(false);
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [timer, isResendDisabled]);
|
||||
|
||||
|
||||
const handleResendOTP = async () => {
|
||||
setIsResendDisabled(true);
|
||||
setTimer(60); // Reset timer to 1 minute
|
||||
|
||||
try {
|
||||
const res = await axios.post(`${import.meta.env.VITE_API_URL}/send-otp`, {
|
||||
mobile_number: phoneNumber,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
toaster.create({
|
||||
title: "OTP resent successfully",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
toaster.create({
|
||||
title: res.data.message || "Failed to resend OTP",
|
||||
type: "error",
|
||||
});
|
||||
setIsResendDisabled(false); // Enable button if failed
|
||||
}
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: error.response?.data?.message || "Failed to resend OTP",
|
||||
type: "error",
|
||||
});
|
||||
setIsResendDisabled(false); // Enable button if error
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number): void => {
|
||||
const value = e.target.value;
|
||||
|
||||
// Prevent non-numeric input
|
||||
if (/[^0-9]/.test(value)) return;
|
||||
|
||||
// Update the OTP state with the new value
|
||||
const newOtp = [...otp];
|
||||
newOtp[index] = value;
|
||||
setOtp(newOtp);
|
||||
|
||||
// Move focus to the next input automatically
|
||||
if (value && index < otp.length - 1) {
|
||||
const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement;
|
||||
if (nextInput) nextInput.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleOtpSubmit = async () => {
|
||||
if (otp.length !== 4) {
|
||||
toaster.create({
|
||||
title: "OTP must be 4 digits",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fullOtp = otp.join('');
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await axios.post(`${import.meta.env.VITE_API_URL}/verify-otp`, {
|
||||
mobile_number: phoneNumber,
|
||||
otp: fullOtp,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
toaster.create({
|
||||
title: "OTP Verified Successfully",
|
||||
type: "success",
|
||||
});
|
||||
const userid = res.data.data.id;
|
||||
navigate(`/forgot-password/reset-password?id=${userid}`);
|
||||
} else {
|
||||
toaster.create({
|
||||
title: res.data.message || "Invalid OTP",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toaster.create({
|
||||
title: error.response?.data?.message || "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<VStack w="100%" h="100vh" bg="#ffffff">
|
||||
<HStack
|
||||
boxShadow="rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"
|
||||
w="100%"
|
||||
ps={8}
|
||||
h="7%"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<Image w={50} src={logo} />
|
||||
</HStack>
|
||||
|
||||
<Center w="100%" h="93%" p={8}>
|
||||
<Box p={8} borderWidth={1} borderRadius="lg" boxShadow="lg">
|
||||
<Text fontSize="24px" fontWeight="bold" color="#313039" textAlign="center">
|
||||
Enter OTP
|
||||
</Text>
|
||||
|
||||
<Text fontSize="14px" color="gray.600" textAlign="center" mt={2}>
|
||||
OTP sent to {phoneNumber}
|
||||
</Text>
|
||||
|
||||
<HStack gap={4} mt={6} justify="center">
|
||||
{otp.map((digit, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
id={`otp-input-${index}`}
|
||||
value={digit}
|
||||
maxW="50px"
|
||||
color={"black"}
|
||||
textAlign="center"
|
||||
fontSize="20px"
|
||||
placeholder="0"
|
||||
border="1px solid grey"
|
||||
onChange={(e) => handleChange(e, index)}
|
||||
maxLength={1} // Only allows 1 character per input
|
||||
/>
|
||||
))}
|
||||
</HStack>
|
||||
{/* <Box textAlign="center">
|
||||
<Text
|
||||
color="#4746F4"
|
||||
textDecoration="underline"
|
||||
fontWeight="bold"
|
||||
mt={3}
|
||||
cursor="pointer"
|
||||
display="inline-block"
|
||||
px={2}
|
||||
>
|
||||
Resend OTP
|
||||
</Text>
|
||||
</Box> */}
|
||||
<Box textAlign="center" mt={4}>
|
||||
{isResendDisabled ? (
|
||||
<Text color="gray.500">
|
||||
Resend OTP in {timer} seconds
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
color="#4746F4"
|
||||
textDecoration="underline"
|
||||
fontWeight="bold"
|
||||
cursor="pointer"
|
||||
onClick={handleResendOTP}
|
||||
>
|
||||
Resend OTP
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
loading={isLoading}
|
||||
mt={6}
|
||||
w="100%"
|
||||
bg="#02A0A0"
|
||||
color="white"
|
||||
onClick={handleOtpSubmit}
|
||||
>
|
||||
Verify OTP
|
||||
</Button>
|
||||
</Box>
|
||||
</Center>
|
||||
|
||||
<Toaster />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyOTP;
|
||||
112
src/Redux/Service/agency.master.module.service.ts
Normal file
112
src/Redux/Service/agency.master.module.service.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface Agency {
|
||||
id: number;
|
||||
name: string;
|
||||
state: string;
|
||||
rc_status: string;
|
||||
rc_number: string;
|
||||
domain_name: string;
|
||||
gst_number: string;
|
||||
is_active: boolean,
|
||||
registered_office: string,
|
||||
}
|
||||
|
||||
export interface AgencyData {
|
||||
current_page: number;
|
||||
data: Agency[];
|
||||
first_page_url: string;
|
||||
from: number;
|
||||
last_page: number;
|
||||
last_page_url: string;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface AgencyResponse {
|
||||
status: "success" | "error";
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: AgencyData;
|
||||
}
|
||||
|
||||
export interface UpdatePrivacyPolicyPayload {
|
||||
id: number,
|
||||
is_active: boolean,
|
||||
}
|
||||
|
||||
export interface AgencyPost {
|
||||
raid?: string;
|
||||
name: string;
|
||||
auth_signatory?: string;
|
||||
state: string;
|
||||
district?: string;
|
||||
rc_number: string;
|
||||
contact_details?: string;
|
||||
registered_office: string;
|
||||
branch_office?: string;
|
||||
registered_email?: string;
|
||||
other_email?: string;
|
||||
registered_contact?: string;
|
||||
website?: string;
|
||||
domain_name: string;
|
||||
staff_domain_name?: string;
|
||||
gst_number: string;
|
||||
rc_status?: "Active" | "Inactive"; // Assuming it's a status with limited values
|
||||
}
|
||||
|
||||
|
||||
export const agencyMasterModule = createApi({
|
||||
reducerPath: "agencyMasterModule",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
createAgencyMasterPost: builder.mutation<AgencyPost, Partial<AgencyPost>>({
|
||||
query: (data) => ({
|
||||
url: "/agency-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
|
||||
// getAgencyMaster: builder.query<AgencyResponse, number>({
|
||||
// query: (page = 1) => `/agency-master?page=${page}`
|
||||
// }),
|
||||
|
||||
getAgencyMaster: builder.query<AgencyResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
|
||||
return `/agency-master?${params.toString()}`;
|
||||
},
|
||||
}),
|
||||
|
||||
agencyMasterToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/agency-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
updateAgencyMaster: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/agency-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useCreateAgencyMasterPostMutation,
|
||||
useGetAgencyMasterQuery,
|
||||
useAgencyMasterToggleMutation,
|
||||
useUpdateAgencyMasterMutation,
|
||||
} = agencyMasterModule;
|
||||
|
||||
75
src/Redux/Service/apiSlice.tsx
Normal file
75
src/Redux/Service/apiSlice.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
import { BaseQueryFn, FetchArgs, FetchBaseQueryError } from "@reduxjs/toolkit/query";
|
||||
import { logout } from "./authSlice"; // Import logout action from authSlice
|
||||
import { RootState } from "../Store";
|
||||
|
||||
const baseQuery = fetchBaseQuery({
|
||||
baseUrl: `${import.meta.env.VITE_API_URL}`,
|
||||
prepareHeaders: (headers, { getState }) => {
|
||||
|
||||
const token = (getState() as RootState).auth.token; // Get token from Redux store
|
||||
// Encode Basic Auth Credentials
|
||||
const username = import.meta.env.VITE_USER_NAME||''; // Replace with actual username
|
||||
const password = import.meta.env.VITE_PASSWORD||''; // Replace with actual password
|
||||
const basicAuth = `${username} : ${password}`; // Encode to Base64
|
||||
|
||||
if (token) {
|
||||
headers.set("Authorization", `Basic ${basicAuth}`);
|
||||
headers.set("access-token", `${token}`);
|
||||
|
||||
}
|
||||
headers.set("Content-Type", "application/json");
|
||||
return headers;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// ✅ Handle 401 Errors (Auto Logout)
|
||||
export const baseQueryWithReauth: BaseQueryFn<
|
||||
string | FetchArgs,
|
||||
unknown,
|
||||
FetchBaseQueryError
|
||||
> = async (args, api, extraOptions) => {
|
||||
const result = await baseQuery(args, api, extraOptions);
|
||||
|
||||
if (result.error && result.error.status === 401) {
|
||||
api.dispatch(logout()); // Logout user on 401 error
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export const dashboard = createApi({
|
||||
reducerPath: "api",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
|
||||
getPosts: builder.query<Post[], void>({ query: () => "/posts" }),
|
||||
|
||||
|
||||
// 🔹 POST: Create a new post
|
||||
logOut: builder.mutation<void, void>({
|
||||
query: () => ({
|
||||
url: "/logout",
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
|
||||
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetPostsQuery, useLogOutMutation } = dashboard;
|
||||
|
||||
export type Post = {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
27
src/Redux/Service/authSlice.tsx
Normal file
27
src/Redux/Service/authSlice.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
type AuthState = {
|
||||
token: string | null;
|
||||
};
|
||||
|
||||
const initialState: AuthState = {
|
||||
token: localStorage.getItem("token"), // Load token from localStorage
|
||||
};
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState,
|
||||
reducers: {
|
||||
setToken: (state, action: PayloadAction<string>) => {
|
||||
state.token = action.payload;
|
||||
localStorage.setItem("token", action.payload); // ✅ Store token in localStorage
|
||||
},
|
||||
logout: (state) => {
|
||||
state.token = null;
|
||||
localStorage.removeItem("token"); // ✅ Remove token from localStorage on logout
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setToken, logout } = authSlice.actions;
|
||||
export default authSlice.reducer;
|
||||
120
src/Redux/Service/country.master.ts
Normal file
120
src/Redux/Service/country.master.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface CountryData {
|
||||
id: number;
|
||||
en_name: string;
|
||||
hi_name: string;
|
||||
mr_name: string;
|
||||
te_name: string;
|
||||
ta_name: string;
|
||||
bn_name: string;
|
||||
or_name: string;
|
||||
country_code: string;
|
||||
phonecode: string;
|
||||
capital: string;
|
||||
currency: string;
|
||||
currency_name: string;
|
||||
currency_symbol: string;
|
||||
is_active: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: CountryData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryEdit {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: CountryData[];
|
||||
}
|
||||
|
||||
|
||||
export type PostCountry = {
|
||||
en_name: string;
|
||||
country_code: string;
|
||||
phonecode: string;
|
||||
capital: string;
|
||||
currency: string;
|
||||
currency_name: string;
|
||||
currency_symbol: string;
|
||||
};
|
||||
|
||||
|
||||
export const countryMaster = createApi({
|
||||
reducerPath: "countryMaster",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createCountryPost: builder.mutation<PostCountry, Partial<PostCountry>>({
|
||||
query: (data) => ({
|
||||
url: "/country-add",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
// getCountryMaster: builder.query<ApiResponse, number>({
|
||||
// query: (page = 1) => `/country-list?page=${page}`,
|
||||
// }),
|
||||
|
||||
getCountryMaster: builder.query<ApiResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
|
||||
return `/country-list?${params.toString()}`;
|
||||
},
|
||||
}),
|
||||
|
||||
getCountryMasterEdit: builder.query<CountryEdit, number>({
|
||||
query: (id) => `/country-edit/${id}`,
|
||||
}),
|
||||
|
||||
updateCountry: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/country-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
countryToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/country-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
// deleteFaqPost: builder.mutation<{ status: string; message: string }, { id: number }>({
|
||||
// query: ({ id }) => ({
|
||||
// url: `/faq-delete`,
|
||||
// method: "POST",
|
||||
// body: { id },
|
||||
// }),
|
||||
// }),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetCountryMasterQuery,
|
||||
useGetCountryMasterEditQuery,
|
||||
useCreateCountryPostMutation,
|
||||
useUpdateCountryMutation,
|
||||
useCountryToggleMutation,
|
||||
// useDeleteFaqPostMutation
|
||||
} = countryMaster;
|
||||
77
src/Redux/Service/dashBoard.ts
Normal file
77
src/Redux/Service/dashBoard.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export type TotalUser = {
|
||||
data: {
|
||||
totalRecruiterCount: number;
|
||||
totalCustomerCount: string;
|
||||
totalUserCount: string;
|
||||
recruitersByMonth: Record<string, number>;
|
||||
customersByMonth: Record<string, number>;
|
||||
};
|
||||
};
|
||||
|
||||
export type PastUser = {
|
||||
data: {
|
||||
past24hourRecruiterCount: number;
|
||||
past24hourCustomercount: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type NewUser = {
|
||||
data: {
|
||||
newRecuiterCount: number;
|
||||
newCustomerCount: number;
|
||||
newTotalUserCount: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type AgencyList = {
|
||||
data: [
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
created_at: string;
|
||||
is_active: boolean
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export type FaqList = {
|
||||
data: [
|
||||
{
|
||||
id: number;
|
||||
faqs_xid: number;
|
||||
language_master_xid: number;
|
||||
question: string;
|
||||
answer: string;
|
||||
is_active: boolean
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
export const dashBoard = createApi({
|
||||
reducerPath: "dashBoard",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
getTotalUser: builder.query<TotalUser, void>({ query: () => "/dashboard-total-user" }),
|
||||
getPastUser: builder.query<PastUser, void>({ query: () => "/dashboard-past-user" }),
|
||||
getNewUser: builder.query<NewUser, void>({ query: () => "/dashboard-new-user" }),
|
||||
getAgencyList: builder.query<AgencyList, void>({ query: () => "/dashboard-agency-list" }),
|
||||
getFaqList: builder.query<FaqList, void>({ query: () => "/dashboard-faq-list" }),
|
||||
|
||||
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetTotalUserQuery,
|
||||
useGetPastUserQuery,
|
||||
useGetNewUserQuery,
|
||||
useGetAgencyListQuery,
|
||||
useGetFaqListQuery,
|
||||
} = dashBoard;
|
||||
30
src/Redux/Service/deactivated.account.service.ts
Normal file
30
src/Redux/Service/deactivated.account.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react"; // add /react for auto-generated hooks
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
interface DeactivatedData {
|
||||
id: number;
|
||||
email: string;
|
||||
first_name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
data: {
|
||||
data: DeactivatedData[];
|
||||
};
|
||||
}
|
||||
|
||||
export const deactivatedAccounts = createApi({
|
||||
reducerPath: "deactivatedAccounts",
|
||||
baseQuery: baseQueryWithReauth,
|
||||
tagTypes: ["Deactivated"],
|
||||
endpoints: (builder) => ({
|
||||
getContact: builder.query<ApiResponse, void>({
|
||||
query: () => "/manage-user-deactivate-list",
|
||||
providesTags: ["Deactivated"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// ✅ Export the auto-generated hook
|
||||
export const { useGetContactQuery } = deactivatedAccounts;
|
||||
119
src/Redux/Service/department.master.ts
Normal file
119
src/Redux/Service/department.master.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface DepartmentData {
|
||||
id: number;
|
||||
industry_masters_xid: number;
|
||||
en_name: string;
|
||||
hi_name: string;
|
||||
mr_name: string;
|
||||
te_name: string;
|
||||
ta_name: string;
|
||||
bn_name: string;
|
||||
or_name: string;
|
||||
is_active: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: DepartmentData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryEdit {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: DepartmentData[];
|
||||
}
|
||||
|
||||
export interface DropDown{
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
id: number;
|
||||
en_name: string;
|
||||
}[]
|
||||
}
|
||||
|
||||
export type PostDepartment = {
|
||||
en_name: string;
|
||||
industry_masters_xid: number;
|
||||
};
|
||||
|
||||
|
||||
export const departmentMaster = createApi({
|
||||
reducerPath: "departmentMaster",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createDepartmentPost: builder.mutation<PostDepartment, Partial<PostDepartment>>({
|
||||
query: (data) => ({
|
||||
url: "/department-master-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
// getDepartmentMaster: builder.query<ApiResponse, number>({
|
||||
// query: (page = 1) => `/department-master-list?page=${page}`,
|
||||
// }),
|
||||
|
||||
getDepartmentMaster: builder.query<ApiResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
|
||||
return `/department-master-list?${params.toString()}`;
|
||||
},
|
||||
}),
|
||||
|
||||
getDepartmentMasterDropDown: builder.query<DropDown, void>({
|
||||
query: () => `/industry-master-get-category`,
|
||||
}),
|
||||
|
||||
updateDepartment: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/department-master-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
departmentToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/department-master-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
// deleteFaqPost: builder.mutation<{ status: string; message: string }, { id: number }>({
|
||||
// query: ({ id }) => ({
|
||||
// url: `/faq-delete`,
|
||||
// method: "POST",
|
||||
// body: { id },
|
||||
// }),
|
||||
// }),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetDepartmentMasterQuery,
|
||||
useGetDepartmentMasterDropDownQuery,
|
||||
useCreateDepartmentPostMutation,
|
||||
useUpdateDepartmentMutation,
|
||||
useDepartmentToggleMutation,
|
||||
// useDeleteFaqPostMutation
|
||||
} = departmentMaster;
|
||||
91
src/Redux/Service/faqs.service.ts
Normal file
91
src/Redux/Service/faqs.service.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface FaqData {
|
||||
id: number;
|
||||
principal_type_xid: number;
|
||||
is_active: number;
|
||||
translations:{
|
||||
id: string,
|
||||
faqs_xid: number,
|
||||
language_master_xid: number,
|
||||
question: string,
|
||||
answer: string,
|
||||
}[]
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: FaqData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export type Post = {
|
||||
principal_type_xid: number;
|
||||
language_code: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
};
|
||||
|
||||
|
||||
export const faqs = createApi({
|
||||
reducerPath: "faqs",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createFaqPost: builder.mutation<Post, Partial<Post>>({
|
||||
query: (data) => ({
|
||||
url: "/faq-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
getFaq: builder.query<ApiResponse, number>({
|
||||
query: (page = 1) => `/faq-list?page=${page}`,
|
||||
}),
|
||||
|
||||
updateFaq: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/faq-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
faqToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/faq-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
|
||||
deleteFaqPost: builder.mutation<{ status: string; message: string }, { id: number }>({
|
||||
query: ({ id }) => ({
|
||||
url: `/faq-delete`,
|
||||
method: "POST",
|
||||
body: { id },
|
||||
}),
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetFaqQuery,
|
||||
useCreateFaqPostMutation,
|
||||
useUpdateFaqMutation,
|
||||
useFaqToggleMutation,
|
||||
useDeleteFaqPostMutation
|
||||
} = faqs;
|
||||
102
src/Redux/Service/industry.master.service.ts
Normal file
102
src/Redux/Service/industry.master.service.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
interface IndustryMasterResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: PaginationData;
|
||||
}
|
||||
|
||||
interface PaginationData {
|
||||
current_page: number;
|
||||
data: IndustryMaster[];
|
||||
first_page_url: string;
|
||||
from: number;
|
||||
last_page: number;
|
||||
last_page_url: string;
|
||||
links: PaginationLink[];
|
||||
next_page_url: string | null;
|
||||
path: string;
|
||||
per_page: number;
|
||||
prev_page_url: string | null;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface IndustryMaster {
|
||||
id: number;
|
||||
categories_masters_xid: number,
|
||||
is_active: boolean;
|
||||
en_name: string;
|
||||
hi_name: string;
|
||||
mr_name: string,
|
||||
te_name: string,
|
||||
ta_name: string,
|
||||
bn_name: string,
|
||||
or_name: string,
|
||||
}
|
||||
|
||||
|
||||
interface PaginationLink {
|
||||
url: string | null;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
en_name: string,
|
||||
categories_masters_xid: number
|
||||
}
|
||||
|
||||
export const industryMaster = createApi({
|
||||
reducerPath: "industryMaster",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createIndustryMasterPost: builder.mutation<Post, Partial<Post>>({
|
||||
query: (data) => ({
|
||||
url: "/industry-master-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
// getIndustryMaster: builder.query<IndustryMasterResponse, number>({
|
||||
// query: (page = 1) => `/industry-master-list?page=${page}`,
|
||||
// }),
|
||||
|
||||
getIndustryMaster: builder.query<IndustryMasterResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
|
||||
return `/industry-master-list?${params.toString()}`;
|
||||
},
|
||||
}),
|
||||
|
||||
updateIndustryMaster: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "industry-master-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
industryMasterToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/industry-master-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetIndustryMasterQuery,
|
||||
useCreateIndustryMasterPostMutation,
|
||||
useUpdateIndustryMasterMutation,
|
||||
useIndustryMasterToggleMutation,
|
||||
} = industryMaster;
|
||||
101
src/Redux/Service/job.status.ts
Normal file
101
src/Redux/Service/job.status.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface JobStatusData {
|
||||
id: number;
|
||||
is_active: string;
|
||||
job_status_translation:{
|
||||
job_status_xid: number,
|
||||
title:string,
|
||||
language_xid:number
|
||||
}[],
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: JobStatusData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryEdit {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: JobStatusData[];
|
||||
}
|
||||
|
||||
|
||||
export type PostJobStatus = {
|
||||
title: string
|
||||
};
|
||||
|
||||
|
||||
export const jobStatus = createApi({
|
||||
reducerPath: "jobStatus",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createJobStatusPost: builder.mutation<PostJobStatus, Partial<PostJobStatus>>({
|
||||
query: (data) => ({
|
||||
url: "/job-status-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
// getJobStatus: builder.query<ApiResponse, number>({
|
||||
// query: (page = 1) => `/job-status-list?page=${page}`,
|
||||
// }),
|
||||
|
||||
getJobStatus: builder.query<ApiResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
|
||||
return `/job-status-list?${params.toString()}`;
|
||||
},
|
||||
}),
|
||||
|
||||
updateJobStatus: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/job-status-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
jobStatusToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/job-status-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
// deleteFaqPost: builder.mutation<{ status: string; message: string }, { id: number }>({
|
||||
// query: ({ id }) => ({
|
||||
// url: `/faq-delete`,
|
||||
// method: "POST",
|
||||
// body: { id },
|
||||
// }),
|
||||
// }),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetJobStatusQuery,
|
||||
useCreateJobStatusPostMutation,
|
||||
useUpdateJobStatusMutation,
|
||||
useJobStatusToggleMutation,
|
||||
// useDeleteFaqPostMutation
|
||||
} = jobStatus;
|
||||
90
src/Redux/Service/job.type.service.ts
Normal file
90
src/Redux/Service/job.type.service.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
interface JobTypeResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: PaginationData;
|
||||
}
|
||||
|
||||
interface PaginationData {
|
||||
current_page: number;
|
||||
data: JobTypeData[];
|
||||
first_page_url: string;
|
||||
from: number;
|
||||
last_page: number;
|
||||
last_page_url: string;
|
||||
links: PaginationLink[];
|
||||
next_page_url: string | null;
|
||||
path: string;
|
||||
per_page: number;
|
||||
prev_page_url: string | null;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface JobTypeData {
|
||||
id: number;
|
||||
is_active: boolean;
|
||||
en_name: string;
|
||||
hi_name: string;
|
||||
mr_name:string,
|
||||
te_name:string,
|
||||
ta_name:string,
|
||||
bn_name:string,
|
||||
or_name:string,
|
||||
}
|
||||
|
||||
|
||||
interface PaginationLink {
|
||||
url: string | null;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
en_name: string
|
||||
}
|
||||
|
||||
export const jobType = createApi({
|
||||
reducerPath: "jobType",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createJobTypePost: builder.mutation<Post, Partial<Post>>({
|
||||
query: (data) => ({
|
||||
url: "/job-type-add",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
getJobType: builder.query<JobTypeResponse, number>({
|
||||
query: (page = 1) => `/job-type?page=${page}`,
|
||||
}),
|
||||
|
||||
updateJobType: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "job-type-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
jobTypeToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/job-type-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetJobTypeQuery,
|
||||
useCreateJobTypePostMutation,
|
||||
useUpdateJobTypeMutation,
|
||||
useJobTypeToggleMutation,
|
||||
} = jobType;
|
||||
88
src/Redux/Service/manage.aboutus.service.ts
Normal file
88
src/Redux/Service/manage.aboutus.service.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
import { AboutUsResponse } from "../../Types/aboutUsType";
|
||||
export const aboutUs = createApi({
|
||||
reducerPath: "aboutUs",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
|
||||
// 🔹 GET: Fetch all posts
|
||||
getAboutUs: builder.query<AboutUsResponse, void>({
|
||||
query: () => "/about-us",
|
||||
}),
|
||||
|
||||
|
||||
// 🔹 GET: Fetch a single post by ID
|
||||
getPostById: builder.query<Post, number>({
|
||||
query: (id) => `/posts/${id}`,
|
||||
}),
|
||||
|
||||
// 🔹 POST: Create a new post
|
||||
createPost: builder.mutation<Post, Partial<Post>>({
|
||||
query: (data) => ({
|
||||
url: "/posts",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 PUT: Update an existing post
|
||||
// updateAboutUs: builder.mutation<UpdateAboutUsResponse, UpdateAboutUsRequest>({
|
||||
// query: ({ id, updatedData }) => ({
|
||||
// url: `/posts/${id}`,
|
||||
// method: "POST",
|
||||
// body: updatedData,
|
||||
// }),
|
||||
// }),
|
||||
// 🔹 PUT: Update an About Us entry
|
||||
updateAboutUs: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/about-us/update", // ✅ Updated URL
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
// 🔹 DELETE: Remove a post by ID
|
||||
deletePost: builder.mutation<{ success: boolean }, number>({
|
||||
query: (id) => ({
|
||||
url: `/posts/${id}`,
|
||||
method: "DELETE",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetAboutUsQuery,
|
||||
useUpdateAboutUsMutation,
|
||||
useGetPostByIdQuery,
|
||||
useCreatePostMutation,
|
||||
useDeletePostMutation
|
||||
} = aboutUs;
|
||||
|
||||
// Define Post type
|
||||
export type Post = {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
|
||||
export type UpdateAboutUsRequest={
|
||||
id: number; updatedData: string,language_code:string
|
||||
}
|
||||
|
||||
|
||||
export type UpdateAboutUsResponse={
|
||||
id: number; updatedData: string,language_code:string
|
||||
}
|
||||
|
||||
|
||||
export type AboutUs = {
|
||||
id: number;
|
||||
language_master_xid: number;
|
||||
content: string;
|
||||
is_active: boolean;
|
||||
};
|
||||
|
||||
45
src/Redux/Service/manage.contactus.service.ts
Normal file
45
src/Redux/Service/manage.contactus.service.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
interface ContactData {
|
||||
id: number;
|
||||
email: string;
|
||||
first_name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
data: {
|
||||
data: ContactData[];
|
||||
};
|
||||
}
|
||||
|
||||
export const manageContactUs = createApi({
|
||||
reducerPath: "manageContactUs",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
tagTypes: ["Contact"],
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
getContact: builder.query<ApiResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
return `/contact-us?${params.toString()}`;
|
||||
},
|
||||
providesTags: ["Contact"],
|
||||
}),
|
||||
|
||||
pendingRequest: builder.mutation({
|
||||
query: (body) => ({
|
||||
url: `/contact-us-response`,
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: ["Contact"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetContactQuery, usePendingRequestMutation } = manageContactUs;
|
||||
26
src/Redux/Service/manage.groups.service.ts
Normal file
26
src/Redux/Service/manage.groups.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export const manageGroups = createApi({
|
||||
reducerPath: "manageGroups",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
|
||||
getPosts: builder.query<Post[], void>({ query: () => "/posts" }),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const { } = manageGroups;
|
||||
|
||||
export type Post = {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
147
src/Redux/Service/manage.jobs.service.ts
Normal file
147
src/Redux/Service/manage.jobs.service.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface JobStatusData {
|
||||
id: number;
|
||||
job_title: string;
|
||||
workspace_mode_xid: number;
|
||||
industry_xid: number;
|
||||
department_xid: number;
|
||||
company_name: string;
|
||||
ctc_currency_symbol: string;
|
||||
ctc_amount: string;
|
||||
experience: string;
|
||||
job_location: string;
|
||||
country_xid: number;
|
||||
job_type_xid: number;
|
||||
skill_description: string;
|
||||
job_description: string;
|
||||
job_image: string | null;
|
||||
industry: {
|
||||
id: number;
|
||||
en_name: string;
|
||||
};
|
||||
department: {
|
||||
id: number;
|
||||
en_name: string;
|
||||
};
|
||||
workspace: {
|
||||
id: number;
|
||||
en_name: string;
|
||||
};
|
||||
country: {
|
||||
id: number;
|
||||
en_name: string;
|
||||
};
|
||||
job_type: {
|
||||
id: number;
|
||||
en_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: JobStatusData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryEdit {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: JobStatusData[];
|
||||
}
|
||||
|
||||
|
||||
export type PostJobStatus = {
|
||||
title: string
|
||||
};
|
||||
|
||||
export type WorkSpace = {
|
||||
id: number;
|
||||
en_name: string;
|
||||
};
|
||||
|
||||
export const manageJobs = createApi({
|
||||
reducerPath: "manageJobs",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
// getManageJobs: builder.query<ApiResponse, number>({
|
||||
// query: (page = 1) => `/manage-jobs-list?page=${page}`,
|
||||
// }),
|
||||
|
||||
getManageJobs: builder.query<ApiResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
|
||||
return `/manage-jobs-list?${params.toString()}`;
|
||||
},
|
||||
}),
|
||||
|
||||
updateJobs: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/manage-jobs-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
viewJobs: builder.query<ApiResponse, number>({
|
||||
query: () => `/manage-jobs-list`,
|
||||
}),
|
||||
|
||||
deleteJobsPost: builder.mutation<{ status: string; message: string }, { id: number }>({
|
||||
query: ({ id }) => ({
|
||||
url: `/manage-jobs-delete`,
|
||||
method: "POST",
|
||||
body: { id },
|
||||
}),
|
||||
}),
|
||||
|
||||
// Modes
|
||||
getWorkspaceModes: builder.query({
|
||||
query: () => `/manage-jobs-get-workspace`,
|
||||
}),
|
||||
|
||||
getIndustry: builder.query({
|
||||
query: () => `/manage-jobs-get-industry`,
|
||||
}),
|
||||
|
||||
getDepartment: builder.query({
|
||||
query: () => `/manage-jobs-get-department`,
|
||||
}),
|
||||
|
||||
getCountry: builder.query({
|
||||
query: () => `/manage-jobs-get-country`,
|
||||
}),
|
||||
|
||||
getManageJobType: builder.query({
|
||||
query: () => `/job-type`,
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetManageJobsQuery,
|
||||
useLazyViewJobsQuery,
|
||||
useDeleteJobsPostMutation,
|
||||
useUpdateJobsMutation,
|
||||
useGetWorkspaceModesQuery,
|
||||
useGetIndustryQuery,
|
||||
useGetDepartmentQuery,
|
||||
useGetCountryQuery,
|
||||
useGetManageJobTypeQuery
|
||||
} = manageJobs;
|
||||
82
src/Redux/Service/manage.post.service.ts
Normal file
82
src/Redux/Service/manage.post.service.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface JobStatusData {
|
||||
id: number;
|
||||
is_active: string;
|
||||
created_at:string,
|
||||
job_status_translation:{
|
||||
job_status_xid: number,
|
||||
title:string,
|
||||
language_xid:number
|
||||
}[],
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: JobStatusData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryEdit {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: JobStatusData[];
|
||||
}
|
||||
|
||||
|
||||
export type PostJobStatus = {
|
||||
title: string
|
||||
};
|
||||
|
||||
|
||||
export const managePosts = createApi({
|
||||
reducerPath: "managePosts",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
// createJobStatusPost: builder.mutation<PostJobStatus, Partial<PostJobStatus>>({
|
||||
// query: (data) => ({
|
||||
// url: "/job-status-store",
|
||||
// method: "POST",
|
||||
// body: data,
|
||||
// }),
|
||||
// }),
|
||||
// 🔹 GET: Fetch all posts
|
||||
getManagePosts: builder.query<ApiResponse, number>({
|
||||
query: (page = 1) => `/manage-post-list?page=${page}`,
|
||||
}),
|
||||
|
||||
postStatusToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/manage-post-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
// deleteFaqPost: builder.mutation<{ status: string; message: string }, { id: number }>({
|
||||
// query: ({ id }) => ({
|
||||
// url: `/faq-delete`,
|
||||
// method: "POST",
|
||||
// body: { id },
|
||||
// }),
|
||||
// }),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetManagePostsQuery,
|
||||
usePostStatusToggleMutation,
|
||||
// useDeleteFaqPostMutation
|
||||
} = managePosts;
|
||||
205
src/Redux/Service/manage.subadmin.service.ts
Normal file
205
src/Redux/Service/manage.subadmin.service.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
interface PaginationLink {
|
||||
url: string | null;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
id: number;
|
||||
unique_id: string;
|
||||
user_name: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
date_of_birth: string;
|
||||
gender: string;
|
||||
}
|
||||
|
||||
interface PaginatedData {
|
||||
current_page: number;
|
||||
data: UserData[];
|
||||
first_page_url: string;
|
||||
from: number;
|
||||
last_page: number;
|
||||
last_page_url: string;
|
||||
links: PaginationLink[];
|
||||
next_page_url: string | null;
|
||||
path: string;
|
||||
per_page: number;
|
||||
prev_page_url: string | null;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: PaginatedData;
|
||||
}
|
||||
|
||||
export type Permission = {
|
||||
id: number;
|
||||
app_resource_title: string;
|
||||
is_active: boolean;
|
||||
created_by: string | null;
|
||||
modified_by: string | null;
|
||||
deleted_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type PermissionResponse = {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
permission: Permission[];
|
||||
};
|
||||
};
|
||||
// export type SubAdminPost = {
|
||||
// id: number;
|
||||
// first_name: string,
|
||||
// last_name: string,
|
||||
// unique_id: string,
|
||||
// date_of_birth: string,
|
||||
// gender: string,
|
||||
// }
|
||||
|
||||
interface ResourceActionLink {
|
||||
id: number;
|
||||
principal_xid: number;
|
||||
role_xid: number;
|
||||
app_resource_xid: number;
|
||||
is_active: boolean;
|
||||
created_by: number | null;
|
||||
modified_by: number | null;
|
||||
deleted_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
app_resource: {
|
||||
id: number;
|
||||
app_resource_title: string
|
||||
}
|
||||
}
|
||||
|
||||
interface SubAdmin {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
unique_id: string;
|
||||
date_of_birth: string;
|
||||
gender: string;
|
||||
get_resource_action_link: ResourceActionLink[];
|
||||
}
|
||||
|
||||
interface SubAdminView {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: SubAdmin[];
|
||||
}
|
||||
|
||||
interface CreateSubAdminPayload {
|
||||
// user_name: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
date_of_birth: string;
|
||||
gender: string;
|
||||
email_address: string;
|
||||
phone_number: string;
|
||||
// created_by: number;
|
||||
}
|
||||
|
||||
interface CreateSubAdminResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: UserData;
|
||||
}
|
||||
|
||||
export const manageSubAdmin = createApi({
|
||||
reducerPath: "manageSubAdmin",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
tagTypes: ["SubAdmin"],
|
||||
endpoints: (builder) => ({
|
||||
|
||||
createFaqPost: builder.mutation<Post, Partial<Post>>({
|
||||
query: (data) => ({
|
||||
url: "/faq-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
|
||||
getSubAdmin: builder.query<ApiResponse, { page?: number; search?: string }>({
|
||||
query: ({ page, search }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page) params.append("page", page.toString());
|
||||
if (search) params.append("search", search);
|
||||
return `/sub-admin?${params.toString()}`
|
||||
}
|
||||
}),
|
||||
|
||||
getPermission: builder.query<PermissionResponse, void>({
|
||||
query: () => `/resources`,
|
||||
}),
|
||||
|
||||
viewSubAdmin: builder.query<SubAdminView, number>({
|
||||
query: (id) => `/sub-admin-view/${id}`,
|
||||
}),
|
||||
|
||||
updateSubAdmin: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/sub-admin-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
createSubAdminPost: builder.mutation<
|
||||
CreateSubAdminResponse,
|
||||
CreateSubAdminPayload
|
||||
>({
|
||||
query: (data) => ({
|
||||
url: "/sub-admin-create",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["SubAdmin"], // Add this to invalidate cache
|
||||
}),
|
||||
|
||||
faqToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/faq-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
deleteSubAdminPost: builder.mutation<{ success: boolean }, number>({
|
||||
query: (id) => ({
|
||||
url: `/sub-admin-delete`,
|
||||
method: "POST",
|
||||
body: { id },
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetSubAdminQuery,
|
||||
useGetPermissionQuery,
|
||||
useLazyViewSubAdminQuery,
|
||||
useUpdateSubAdminMutation,
|
||||
useDeleteSubAdminPostMutation,
|
||||
useCreateSubAdminPostMutation,
|
||||
} = manageSubAdmin;
|
||||
|
||||
export type Post = {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
125
src/Redux/Service/manage.user.ts
Normal file
125
src/Redux/Service/manage.user.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export interface UserData {
|
||||
id: number;
|
||||
principal_type_xid: number
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
phone_number: string;
|
||||
gender: string;
|
||||
date_of_birth: string;
|
||||
is_active: boolean;
|
||||
principal_type: {
|
||||
id: number;
|
||||
principal_type_title: string;
|
||||
},
|
||||
principle_language_linkss: {
|
||||
id: number;
|
||||
language_xid: number;
|
||||
iam_principal_xid: number;
|
||||
language: {
|
||||
id: number,
|
||||
language_name: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
per_page: number,
|
||||
to: number,
|
||||
data: UserData[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryEdit {
|
||||
status: string;
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: UserData[];
|
||||
}
|
||||
|
||||
|
||||
export type PostCountry = {
|
||||
en_name: string;
|
||||
country_code: string;
|
||||
phonecode: string;
|
||||
capital: string;
|
||||
currency: string;
|
||||
currency_name: string;
|
||||
currency_symbol: string;
|
||||
};
|
||||
|
||||
|
||||
export const registerUser = createApi({
|
||||
reducerPath: "registerUser",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
createUser: builder.mutation<PostCountry, Partial<PostCountry>>({
|
||||
query: (data) => ({
|
||||
url: "/manage-user-store",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
getManageUser: builder.query<ApiResponse, number>({
|
||||
query: (page = 1) => `/manage-user-list?page=${page}`,
|
||||
}),
|
||||
|
||||
getDeactivateUser: builder.query<ApiResponse, number>({
|
||||
query: (page = 1) => `/manage-user-deactivate-list?page=${page}`,
|
||||
}),
|
||||
|
||||
updateUser: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/manage-user-update",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
userToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/manage-user-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
userDeactivateToggle: builder.mutation({
|
||||
query: ({ id, is_active }) => ({
|
||||
url: `/manage-user-deactivate-status`,
|
||||
method: "POST",
|
||||
body: { id, is_active },
|
||||
}),
|
||||
}),
|
||||
|
||||
deleteUser: builder.mutation({
|
||||
query: ({ id }) => ({
|
||||
url: `/manage-user-delete`,
|
||||
method: "POST",
|
||||
body: { id },
|
||||
}),
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetManageUserQuery,
|
||||
useCreateUserMutation,
|
||||
useUpdateUserMutation,
|
||||
useUserToggleMutation,
|
||||
useGetDeactivateUserQuery,
|
||||
useUserDeactivateToggleMutation,
|
||||
useDeleteUserMutation
|
||||
} = registerUser;
|
||||
37
src/Redux/Service/myprofie.service.ts
Normal file
37
src/Redux/Service/myprofie.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export const myProfile = createApi({
|
||||
reducerPath: "myProfile",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
|
||||
// getPosts: builder.query<Post[], void>({ query: () => "/profile-image-edit" }),
|
||||
|
||||
updateImage: builder.mutation({
|
||||
query: (formData: FormData) => {
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
return {
|
||||
url: "/profile-image-edit",
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: {
|
||||
"access-token": `${token}`,
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useUpdateImageMutation,
|
||||
} = myProfile;
|
||||
24
src/Redux/Service/privacy.policy.service.ts
Normal file
24
src/Redux/Service/privacy.policy.service.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice"; // Ensure this is correctly configured
|
||||
import { PrivacyPolicyResponse } from "../../Types/privacyPolicyTypes";
|
||||
|
||||
export const privacyPolicy = createApi({
|
||||
reducerPath: "privacyPolicy",
|
||||
baseQuery: baseQueryWithReauth, // Ensure this returns a valid `BaseQueryFn`
|
||||
endpoints: (builder) => ({
|
||||
getPrivacyPolicy: builder.query<PrivacyPolicyResponse, void>({ // Fix types here
|
||||
query: () => "/privacy-policy",
|
||||
}),
|
||||
|
||||
updatePrivacyPolicy: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/privacy-policy/update", // ✅ Updated URL
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hook
|
||||
export const { useGetPrivacyPolicyQuery, useUpdatePrivacyPolicyMutation } = privacyPolicy;
|
||||
26
src/Redux/Service/privacy.service.ts
Normal file
26
src/Redux/Service/privacy.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
export const privacy = createApi({
|
||||
reducerPath: "privacy",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
|
||||
getPosts: builder.query<Post[], void>({ query: () => "/posts" }),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const { } = privacy;
|
||||
|
||||
export type Post = {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
69
src/Redux/Service/profile.password.ts
Normal file
69
src/Redux/Service/profile.password.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQueryWithReauth } from "./apiSlice";
|
||||
|
||||
interface profileGet {
|
||||
data: {
|
||||
id: number,
|
||||
first_name: string,
|
||||
last_name: string,
|
||||
phone_number: string,
|
||||
profile_photo: string,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type ProfilePass = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
|
||||
export const profile = createApi({
|
||||
reducerPath: "profile",
|
||||
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
|
||||
endpoints: (builder) => ({
|
||||
profilePassword: builder.mutation<ProfilePass, Partial<ProfilePass>>({
|
||||
query: (data) => ({
|
||||
url: "/profile-password",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
// 🔹 GET: Fetch all posts
|
||||
getProfile: builder.query<profileGet, void>({
|
||||
query: () => "/profile-view",
|
||||
}),
|
||||
|
||||
verifyOTP: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/profile-otp",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
newPasswordSet: builder.mutation({
|
||||
query: (updatedData) => ({
|
||||
url: "/profile-change-password",
|
||||
method: "POST",
|
||||
body: updatedData,
|
||||
}),
|
||||
}),
|
||||
|
||||
resendOtp: builder.mutation({
|
||||
query: ({ data }) => ({
|
||||
url: `/profile-resend-otp`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetProfileQuery,
|
||||
useProfilePasswordMutation,
|
||||
useVerifyOTPMutation,
|
||||
useNewPasswordSetMutation,
|
||||
useResendOtpMutation
|
||||
} = profile;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user