Merge pull request 'Yasin' (#22) from Yasin into Sprint-9

Reviewed-on: #22
This commit is contained in:
2024-12-06 07:18:57 +00:00
13 changed files with 1321 additions and 19 deletions

View File

@@ -13,8 +13,9 @@ import {
Portal,
Text,
useColorMode,
useDisclosure,
} from "@chakra-ui/react";
import React, { useContext } from "react";
import React, { useContext, useRef } from "react";
import { Link, useNavigate } from "react-router-dom";
import { IoMdDownload } from "react-icons/io";
import * as XLSX from "xlsx";
@@ -23,6 +24,7 @@ import GlobalStateContext from "../Contexts/GlobalStateContext";
import { MdOutlineDarkMode, MdOutlineLightMode } from "react-icons/md";
import logoMini from "../assets/propic.png";
import { BsBack } from "react-icons/bs";
import ChangePassword from "../Pages/ChangePassword";
const HeaderMain = ({
link,
@@ -35,6 +37,8 @@ const HeaderMain = ({
}) => {
const navigate = useNavigate();
const { colorMode, toggleColorMode } = useContext(GlobalStateContext);
const { isOpen, onOpen, onClose } = useDisclosure();
const firstField = useRef();
return (
<Box
@@ -66,11 +70,11 @@ const HeaderMain = ({
<PopoverBody onClick={()=> navigate('/profile')} className="web-text-medium pointer link">
Profile
</PopoverBody>
<Link to={"/help-and-support"}>
<Box onClick={onOpen}>
<PopoverBody className="web-text-medium pointer ">
Help & Support
Change Password
</PopoverBody>
</Link>
</Box>
<PopoverFooter
onClick={logOutHandler}
className="web-text-medium pointer link"
@@ -112,6 +116,11 @@ const HeaderMain = ({
{/* <Box onClick={() => toggleColorMode()} as="span" p={2} rounded={'lg'} className="link pointer">
{colorMode === "light"? <MdOutlineDarkMode /> :<MdOutlineLightMode />}
</Box> */}
<ChangePassword
isOpen={isOpen}
onClose={onClose}
firstField={firstField}
/>
</Box>
</Box>
);

View File

@@ -0,0 +1,66 @@
import { Box, Text } from "@chakra-ui/react";
import React, { useRef } from "react";
import audioClick from "../assets/click-151673.mp3";
const RoleSwitchButton = ({ isSwitchOn, setIsSwitchOn }) => {
// const [isSwitchOn, setIsSwitchOn] = useState(false);
// const audio = useRef();
const switchOnChangeHandle = () => {
setIsSwitchOn(!isSwitchOn);
// if (audio.current) {
// audio.current.play();
// }
};
return (
<Box display="flex" alignItems="center">
<Box
as="button"
display="flex"
justifyContent="normal"
alignItems="center"
// justifyContent={isSwitchOn ? "flex-end" : "flex-start"}
width="85px"
height="24px"
borderRadius="20px"
backgroundColor={isSwitchOn ? "#00ffcc" : "#b3ff99"}
onClick={switchOnChangeHandle}
position="relative"
fontSize="12px"
fontWeight="100"
transition="background-color 0.2s"
_before={{
content: '""',
position: "absolute",
width: "20px",
height: "20px",
borderRadius: "50%",
backgroundColor: "#fff",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)",
transform: isSwitchOn ? "translateX(61px)" : "translateX(0)",
transition: "transform 0.2s",
left:'2px',
top:'2px'
}}
>
<Text
fontWeight="500"
zIndex={1}
position="absolute"
mb={0}
color={isSwitchOn ? "#000" : "#000"}
left={isSwitchOn ? "10px" : "auto"}
right={isSwitchOn ? "auto" : "10px"}
>
{isSwitchOn ? "Maker" : "Checker"}
</Text>
</Box>
{/* <audio ref={audio} src={audioClick} /> */}
</Box>
);
};
export default RoleSwitchButton;

View File

@@ -0,0 +1,202 @@
import {
Button,
DrawerFooter,
FormControl,
FormErrorMessage,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Stack,
useToast,
} from "@chakra-ui/react";
import * as yup from "yup";
import React, { useState, useEffect, useContext } from "react";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { v4 as uuidv4 } from "uuid";
import { useParams } from "react-router-dom";
import CustomAlertDialog from "../Components/CustomAlertDialog";
import ToastBox from "../Components/ToastBox";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import CurrencyInput from "../Components/CurrencyInput";
const ioNav = yup.object().shape({
transactionDate: yup.string().required("Date is required"),
transactionAmount: yup.string().required("New NAV is required"),
comments: yup
.string()
.notRequired()
.max(200, "Approve Comment cannot be more than 200 characters"),
});
const ChangePassword = ({
isOpen,
onClose,
firstField,
actionId,
setActionId,
data,
}) => {
const params = useParams();
const id = params?.id;
const [file, setFile] = useState("");
const [fileName, setFileName] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [alert, setAlert] = useState(false);
const toast = useToast();
const [showPassword, setShowPassword] = useState(false);
const [subject, setSubject] = useState("");
const togglePasswordVisibility = () => setShowPassword(!showPassword);
// ======================[ Cotext Api ]
const { IODetails } = useContext(GlobalStateContext);
const found = data?.find((item) => item?.id === actionId);
// const [addNavDetails] = useAddNavDetailsMutation()
// const {
// data
// } = useGetArtifactsQuery(id)
const {
control,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm({
resolver: yupResolver(ioNav),
});
// const onSubmit = async (data) => {
// setIsLoading(true);
// try {
// const res = await addNavDetails({ data, id });
// if (res?.data?.statusCode === 201) {
// setIsLoading(false);
// toast({
// render: () => <ToastBox message={res?.data?.message} />,
// });
// handleClose();
// } else if (res?.error?.status === 400) {
// toast({
// render: () => (
// <ToastBox message={res?.error?.data?.message} status={"error"} />
// ),
// });
// handleClose();
// }
// } catch (error) {
// console.log(error);
// }
// };
const handleSave = () => {
handleSubmit(onSubmit)();
};
const handleClose = () => {
setIsLoading(false);
setAlert(false);
onClose();
};
return (
<>
<Modal
// closeOnOverlayClick={false}
isOpen={isOpen}
onClose={onClose}
initialFocusRef={firstField}
>
<ModalOverlay />
<ModalContent>
<ModalHeader fontSize={"md"}>Change Password</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<Stack spacing={4}>
<FormControl isInvalid={errors.ChangePassword} isRequired>
<FormLabel fontSize={"sm"} mb={1} fontWeight={500}>Current Password</FormLabel>
<Input
size={"sm"}
onChange={(e) => setSubject(e.target.value)}
focusBorderColor="forestGreen.300"
rounded={4}
type={showPassword ? "text" : "password"}
/>
<FormErrorMessage fontSize={"xs"} fontWeight={500}>
{errors.ChangePassword?.message}
</FormErrorMessage>
</FormControl>
<FormControl isInvalid={errors.newPassword} isRequired>
<FormLabel fontSize={"sm"} mb={1}>New Password</FormLabel>
<Input
size={"sm"}
onChange={(e) => setSubject(e.target.value)}
focusBorderColor="forestGreen.300"
rounded={4}
type="text"
/>
<FormErrorMessage fontSize={"xs"} fontWeight={500}>
{errors.newPassword?.message}
</FormErrorMessage>
</FormControl>
<FormControl isInvalid={errors.conformPassword} isRequired>
<FormLabel fontSize={"sm"} mb={1}>Re-Type New Password</FormLabel>
<Input
size={"sm"}
onChange={(e) => setSubject(e.target.value)}
focusBorderColor="forestGreen.300"
rounded={4}
type="text"
/>
<FormErrorMessage fontSize={"xs"} fontWeight={500}>
{errors.conformPassword?.message}
</FormErrorMessage>
</FormControl>
</Stack>
</ModalBody>
<DrawerFooter mb={5}>
<Button
// variant="outline"
bg={"#e0ebeb"}
rounded={"sm"}
size={"sm"}
mr={3}
onClick={handleClose}
>
Cancel
</Button>
<Button
colorScheme={"forestGreen"}
rounded={"sm"}
size={"sm"}
onClick={() => setAlert(true)}
fontWeight={400}
>
Save
</Button>
</DrawerFooter>
</ModalContent>
</Modal>
<CustomAlertDialog
isOpen={alert}
onClose={() => setAlert(false)}
alertHandler={handleSave}
message={"Are you sure you want to change password?"}
isLoading={isLoading}
/>
</>
);
};
export default ChangePassword;

View File

@@ -0,0 +1,178 @@
import {
Button,
DrawerFooter,
FormControl,
FormErrorMessage,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Stack,
useToast,
} from "@chakra-ui/react";
import * as yup from "yup";
import React, { useState, useEffect, useContext } from "react";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { v4 as uuidv4 } from "uuid";
import { useParams } from "react-router-dom";
import CustomAlertDialog from "../Components/CustomAlertDialog";
import ToastBox from "../Components/ToastBox";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import CurrencyInput from "../Components/CurrencyInput";
const ioNav = yup.object().shape({
transactionDate: yup.string().required("Date is required"),
transactionAmount: yup.string().required("New NAV is required"),
comments: yup
.string()
.notRequired()
.max(200, "Approve Comment cannot be more than 200 characters"),
});
const ForgetPassword = ({
isOpen,
onClose,
firstField,
actionId,
setActionId,
data,
}) => {
const params = useParams();
const id = params?.id;
const [file, setFile] = useState("");
const [fileName, setFileName] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [alert, setAlert] = useState(false);
const toast = useToast();
const [showPassword, setShowPassword] = useState(false);
const [subject, setSubject] = useState("");
// ======================[ Cotext Api ]
const { IODetails } = useContext(GlobalStateContext);
const found = data?.find((item) => item?.id === actionId);
// const [addNavDetails] = useAddNavDetailsMutation()
// const {
// data
// } = useGetArtifactsQuery(id)
const {
control,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm({
resolver: yupResolver(ioNav),
});
// const onSubmit = async (data) => {
// setIsLoading(true);
// try {
// const res = await addNavDetails({ data, id });
// if (res?.data?.statusCode === 201) {
// setIsLoading(false);
// toast({
// render: () => <ToastBox message={res?.data?.message} />,
// });
// handleClose();
// } else if (res?.error?.status === 400) {
// toast({
// render: () => (
// <ToastBox message={res?.error?.data?.message} status={"error"} />
// ),
// });
// handleClose();
// }
// } catch (error) {
// console.log(error);
// }
// };
const handleSave = () => {
handleSubmit(onSubmit)();
};
const handleClose = () => {
setIsLoading(false);
setAlert(false);
onClose();
};
return (
<>
<Modal
// closeOnOverlayClick={false}
isOpen={isOpen}
onClose={onClose}
initialFocusRef={firstField}
>
<ModalOverlay />
<ModalContent>
<ModalHeader fontSize={"md"}>Forget Password</ModalHeader>
<ModalCloseButton />
<ModalBody pb={4}>
<Stack spacing={4}>
<FormControl isInvalid={errors.ChangePassword}>
<FormLabel fontSize={"sm"} mb={3} fontWeight={500}>Email, Phone, or UserName</FormLabel>
<Input
size={"md"}
onChange={(e) => setSubject(e.target.value)}
focusBorderColor="forestGreen.300"
rounded={4}
// type={showPassword ? "text" : "password"}
type="text"
/>
<FormErrorMessage fontSize={"xs"} fontWeight={500}>
{errors.ChangePassword?.message}
</FormErrorMessage>
</FormControl>
</Stack>
</ModalBody>
<DrawerFooter mb={5}>
{/* <Button
// variant="outline"
bg={"#e0ebeb"}
rounded={"sm"}
size={"sm"}
mr={3}
onClick={handleClose}
>
Cancel
</Button> */}
<Button
w={"100%"}
colorScheme={"forestGreen"}
rounded={"md"}
size={"md"}
onClick={() => setAlert(true)}
fontWeight={400}
>
Send Login Link
</Button>
</DrawerFooter>
</ModalContent>
</Modal>
<CustomAlertDialog
isOpen={alert}
onClose={() => setAlert(false)}
alertHandler={handleSave}
message={"Are you sure you want to change password?"}
isLoading={isLoading}
/>
</>
);
};
export default ForgetPassword;

View File

@@ -4,10 +4,7 @@ import {
Box,
Button,
HStack,
Input,
Table,
Tag,
Tbody,
Input,
Text,
Th,
Tooltip,
@@ -22,7 +19,7 @@ import { OPACITY_ON_LOAD } from "../../../../Layout/animations";
import NormalTable from "../../../../Components/DataTable/NormalTable";
import GlobalStateContext from "../../../../Contexts/GlobalStateContext";
import CustomAlertDialog from "../../../../Components/CustomAlertDialog";
import * as XLSX from "xlsx";
import * as XLSX from "xlsx";
import ToastBox from "../../../../Components/ToastBox";
import AddCashDetails from "../AddCashDetails";
import { debounce } from "../../../Admin/Contact";

View File

@@ -182,7 +182,7 @@ const Investors = ({ data }) => {
{item?.clientReference_id}
</Text>
),
"First name": (
"First Name": (
<Text
justifyContent={slideFromRight ? "right" : "center"}
as={"span"}
@@ -193,7 +193,7 @@ const Investors = ({ data }) => {
{item.firstName}
</Text>
),
"Last name": (
"Last Name": (
<Text
justifyContent={slideFromRight ? "right" : "center"}
as={"span"}
@@ -204,7 +204,7 @@ const Investors = ({ data }) => {
{item.lastName}
</Text>
),
"Investment amount": (
"Investment Amount": (
<Text
justifyContent={slideFromRight ? "right" : "left"}
as={"span"}
@@ -313,7 +313,7 @@ const Investors = ({ data }) => {
})}`}
</Text>
),
"Total return on Investment": (
"Total Return on Investment": (
<Text
justifyContent={slideFromRight ? "right" : "center"}
as={"span"}

View File

@@ -3,7 +3,7 @@ import Input01 from "../Components/Inputs/Input01";
import logo from "../assets/logo2.png";
import { useDispatch, useSelector } from "react-redux";
import { loginUser } from "../Redux/Slice/auth";
import { useContext, useEffect, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import Button01 from "../Components/Buttons/Button01";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
@@ -12,12 +12,15 @@ import Loader01 from "../Components/Loaders/Loader01";
import Asset1 from "../assets/asset1.png";
import Asset2 from "../assets/asset2.png";
import {
Box,
Button,
FormControl,
FormLabel,
Input,
InputGroup,
InputRightElement,
Text,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import GlobalStateContext from "../Contexts/GlobalStateContext";
@@ -28,6 +31,7 @@ import { useLoginMutation } from "../Services/token.serivce";
// import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
import ForgetPassword from "./ForgetPassword";
const validationSchema = Yup.object().shape({
@@ -50,6 +54,8 @@ const Login = () => {
const dispatch = useDispatch();
const [login] = useLoginMutation()
const { isOpen, onOpen, onClose } = useDisclosure();
const firstField = useRef();
useEffect(() => {
@@ -205,7 +211,7 @@ const Login = () => {
)}
</FormControl>
<FormControl className="mb-4">
<FormControl className="mb-2">
<FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
Password <span className="text-danger">*</span>
</FormLabel>
@@ -238,6 +244,9 @@ const Login = () => {
</span>
)}
</FormControl>
<Box fontSize={"sm"} display={"flex"} justifyContent={"end"} mt={0}>
<Text fontWeight={500} cursor={"pointer"} onClick={onOpen}>Forget Password?</Text>
</Box>
<Button
isLoading={isLoading}
@@ -317,6 +326,11 @@ const Login = () => {
src={Asset2}
alt="bg-img"
/>
<ForgetPassword
isOpen={isOpen}
onClose={onClose}
firstField={firstField}
/>
</div>
);
};

View File

@@ -0,0 +1,375 @@
import {
Badge,
Box,
Button,
HStack,
Input,
Switch,
Text,
Tooltip,
useToast,
} from "@chakra-ui/react";
import React, { useContext, useEffect, useState } from "react";
import { Link, Link as RouterLink } from "react-router-dom";
import { AddIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons";
import { useNavigate } from "react-router-dom";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import NormalTable from "../../Components/DataTable/NormalTable";
import Pagination from "../../Components/Pagination";
import GlobalStateContext from "../../Contexts/GlobalStateContext";
import CustomAlertDialog from "../../Components/CustomAlertDialog";
import ToastBox from "../../Components/ToastBox";
import { TABLE_PAGINATION } from "../../Constants/Paginations";
import { generateSerialNumber } from "../../Constants/Constants";
import { useGetSponserMasterQuery } from "../../Services/io.service";
import {
useGetSubAdminMasterQuery,
useToggleStatusMutation,
} from "../../Services/subadmin.service";
import RoleSwitchButton from "../../Components/RoleSwitchButton";
export const formatDate = (date) => {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0"); // Months are 0-indexed
const day = String(d.getDate()).padStart(2, "0");
return `${day}/${month}/${year}`;
};
const SubAdmin = () => {
const navigate = useNavigate();
const toast = useToast();
const [isLoading, setIsLoading] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [actionId, setActionId] = useState(false);
const [mouseEntered, setMouseEntered] = useState(false);
const [mouseEnteredId, setMouseEnteredId] = useState("");
// const [deleteSponser] = useDeleteSponserMutation();
const { sponser, setSponser, slideFromRight } =
useContext(GlobalStateContext);
// =========================== [Use State] =============================
const [pageSize, setPageSize] = useState(TABLE_PAGINATION?.size);
const [currentPage, setCurrentPage] = useState(TABLE_PAGINATION?.page);
const [searchTerm, setSearchTerm] = useState("");
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
const [isSwitchOn, setIsSwitchOn] = useState(true);
// Debounce the search term to avoid making a request on every keystroke
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchTerm(searchTerm);
}, 500); // Adjust delay as needed
return () => {
clearTimeout(handler);
};
}, [searchTerm]);
const {
data: subAdmin,
error,
isLoading: isSponserLoading,
} = useGetSubAdminMasterQuery();
const [toggleStatus] = useToggleStatusMutation();
// useEffect(() => {
// if (subAdmin?.data) {
// setIsSwitchOn(subAdmin?.role?.role);
// console.log(subAdmin);
// }
// }, [subAdmin]);
// ==============================[Table Filter]========================
const filteredData = subAdmin?.data?.filter((item) => {
const name = item.firstName;
const searchLower = searchTerm?.toLowerCase();
const nameMatches = name?.toLowerCase().includes(searchLower);
return nameMatches;
});
const handleToggleStatus = async (isMaker, id) => {
console.log("hit");
const data = {
role_xid: isMaker ? 1 : 2,
};
try {
const res = await toggleStatus(id, data).unwrap();
if (res?.error) {
toast({
render: () => (
<ToastBox status={"error"} message={res?.error?.data?.message} />
),
});
} else if (res) {
toast({
render: () => (
<ToastBox
status={"success"}
message={res?.message || "Status updated successfully!"}
/>
),
});
}
} catch (error) {
toast({
render: () => (
<ToastBox
status={"error"}
message={error?.data?.message || "Something went wrong"}
/>
),
});
}
};
// ====================================================[Table Setup]================================================================
const tableHeadRow = [
"Sr No",
"First Name",
"Last Name",
"Email Address",
"Role",
"Action",
];
const extractedArray = subAdmin?.data?.map((item, index) => ({
"Sr No": (
<Text
w={"24px"}
justifyContent={slideFromRight ? "right" : "left"}
as={"span"}
color={"gray.600"}
className="d-flex align-items-center fw-bold web-text-small"
>
{/* {item.id} */}
{generateSerialNumber(index, currentPage, pageSize)}
</Text>
),
"First Name": (
<Text
justifyContent={slideFromRight ? "right" : "left"}
as={"span"}
color={"teal.900"}
fontWeight={"500"}
className="d-flex align-items-center web-text-small"
>
{item?.firstName}
</Text>
),
"Last Name": (
<Text
justifyContent={slideFromRight ? "right" : "left"}
as={"span"}
color={"teal.900"}
fontWeight={"500"}
className="d-flex align-items-center web-text-small"
>
{item?.lastName}
</Text>
),
"Email Address": (
<Box isTruncated={true}>
<Text as={"span"} color={"teal.900"} fontWeight={"500"}>
{item?.emailAddress}
</Text>
</Box>
),
Role: (
<Box minW={24} isTruncated={true}>
<Badge
py={"2px"}
px={"5px"}
me={2}
bg={item?.role[0]?.role === "Maker" ? "#00ffcc" : "#b3ff99"}
>
{item?.role[0]?.role}
</Badge>
<Switch
onChange={() =>
handleToggleStatus(item?.role[0]?.role === "Maker", item?.id)
}
isChecked={item?.role[0]?.role === "Maker"}
// colorScheme={item?.role[0]?.role === "Maker" ? "green" : "teal"}
sx={{
".chakra-switch__track": {
bg: item?.role[0]?.role === "Maker" ? "#00ffcc" : "#b3ff99", // "Off" state color
},
}}
/>
{/* <RoleSwitchButton
setIsSwitchOn={setIsSwitchOn}
isSwitchOn={item?.role[0]?.role === "Maker"}
onClick={() => handleToggleStatus(item?.role[0]?.role=== "Maker")}
/> */}
</Box>
),
Action: (
<Box display={"flex"} justifyContent={"center"} gap={2}>
<Tooltip
rounded={"sm"}
fontSize={"xs"}
label="Edit"
bg="#fff"
color={"blue.500"}
placement="top"
>
<Button
onClick={() => navigate(`/subadmin/subadmin-update/${item.id}`)}
// _hover={{ color: "blue.500" }}
// color="blue.400"
rounded={"sm"}
size={"xs"}
colorScheme="blue"
>
<EditIcon />
</Button>
</Tooltip>
{/* <Tooltip
rounded={"sm"}
fontSize={"xs"}
label="Delete"
bg="#fff"
color={"red.500"}
placement="top"
>
<Button
onClick={() => {
setActionId(item?.id);
setDeleteAlert(true);
}}
// _hover={{ color: "red.500" }}
// color="red"
rounded={"sm"}
size={"xs"}
colorScheme="red"
variant={"solid"}
>
<DeleteIcon />
</Button>
</Tooltip> */}
</Box>
),
}));
// =========================== [ Delete Function ] =================================
// const handleDelete = async () => {
// console.log(actionId);
// setIsLoading(true);
// try {
// const response = await deleteSponser(actionId);
// console.log(response?.data);
// if (response?.error?.data?.code === 400) {
// toast({
// render: () => (
// <ToastBox
// message={response?.error?.data?.message}
// status={"error"}
// />
// ),
// });
// setIsLoading(false);
// setDeleteAlert(false);
// } else if (
// response?.data?.statusCode === 201 ||
// response?.data?.statusCode === 200
// ) {
// toast({
// render: () => (
// <ToastBox message={response?.data?.message} status={"success"} />
// ),
// });
// setIsLoading(false);
// setDeleteAlert(false);
// }
// } catch (error) {}
// };
console.log(isSponserLoading);
return (
<Box {...OPACITY_ON_LOAD} overflowY={"scroll"} height={"100vh"} pb={38}>
<Box bg="white.500">
<HStack
display={"flex"}
justifyContent={"space-between"}
ps={1}
pe={1}
pb={4}
pt={4}
spacing="24px"
>
{/* =======================[Search Input]======================== */}
<Input
type="search"
width={300}
placeholder="Search..."
size="sm"
rounded="sm"
focusBorderColor="green.500"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<HStack display={"flex"} alignItems={"center"}>
{/* ====================[Pagination]===================== */}
<Pagination
isLoading={isSponserLoading}
pageSize={pageSize}
setPageSize={setPageSize}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
totalItems={subAdmin?.data?.totalItems}
/>
{/* =====================[Add Button]===================== */}
<Link to={"/subadmin/subadmin-update"}>
<Button
leftIcon={<AddIcon />}
colorScheme={"forestGreen"}
rounded={"sm"}
fontSize={"xs"}
size={"sm"}
>
Add
</Button>
</Link>
</HStack>
</HStack>
</Box>
{/* =================== [Data Table] ===================== */}
<NormalTable
emptyMessage={`We don't have any Sponers `}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={isSponserLoading}
viewActionId={actionId}
setViewActionId={setActionId}
setMouseEnteredId={setMouseEnteredId}
setMouseEntered={setMouseEntered}
/>
{/* ======================== [Modal] ======================== */}
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
message={"Are you sure you want to delete sponers?"}
// alertHandler={handleDelete}
isLoading={isLoading}
/>
</Box>
);
};
export default SubAdmin;

View File

@@ -0,0 +1,354 @@
import React, { useContext, useEffect, useState } from "react";
import { Box, Button, Text, useToast } from "@chakra-ui/react";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNavigate, useParams } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { ArrowBackIcon } from "@chakra-ui/icons";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import FormInputMain from "../../Components/FormInputMain";
import ToastBox from "../../Components/ToastBox";
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
import CustomAlertDialog from "../../Components/CustomAlertDialog";
import RoleSwitchButton from "../../Components/RoleSwitchButton";
import {
useCreateSubAdminMutation,
useGetSubAdminByIdQuery,
useUpdateSubAdminMutation,
} from "../../Services/subadmin.service";
import { useGetSponserByIdQuery } from "../../Services/io.service";
// ======================= [validation] =========================
export const addSubAdmin = yup.object().shape({
firstName: yup
.string()
.required("First Name is required")
.min(3, "First Name must be at least 3 characters long")
.max(50, "First Name cannot exceed 50 characters")
.matches(/^[^\d]+$/, "First Name cannot contain numbers"),
lastName: yup
.string()
.required("Last Name name in arabic is required"),
emailAddress: yup.string().email("Invalid email address").notRequired(),
// .test("emailValidity", "Email address is invalid", async function (value) {
// if (!value) {
// return true; // Allow if the field is empty
// }
// return await checkEmailValidity(value);
// }),
});
// ==================== [debounce] ========================
export function debounce(func, delay) {
let debounceTimer;
return function (...args) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(this, args), delay);
};
}
const SubAdminUpdateCreate = () => {
const toast = useToast();
const params = useParams();
const navigate = useNavigate();
const id = params?.id;
// =====================[useState]=======================
const [isLoadingBtn, setIsLoadingBtn] = useState(false);
const [alert, setAlert] = useState(false);
const [form, setForm] = useState();
const [isSwitchOn, setIsSwitchOn] = useState(true);
const [createSubAdmin] = useCreateSubAdminMutation();
const [updateSubAdmin] = useUpdateSubAdminMutation();
// Fetch sponsor data only if id exists
const {
data: subAdminByIdData,
error,
isLoading,
} = useGetSubAdminByIdQuery(id, { skip: !id });
// ======================== [validators] ===========================
const {
control,
watch,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: yupResolver(addSubAdmin),
});
// ========================== [useEffect] ================================
useEffect(() => {
if (subAdminByIdData?.data) {
reset({
firstName: subAdminByIdData?.data?.firstName,
lastName: subAdminByIdData?.data?.lastName,
emailAddress: subAdminByIdData?.data?.emailAddress,
});
setIsSwitchOn(subAdminByIdData?.data?.role[0]?.role==="Maker");
console.log(subAdminByIdData?.data?.role);
}
}, [subAdminByIdData, reset]);
if (false) {
return <FullscreenLoaders />;
}
// ============================ [API]===============================
const handleConfirm = async () => {
setIsLoadingBtn(true);
const id = params?.id;
console.log(isSwitchOn);
if (id) {
try {
const formData = {
...form,
role_xid: isSwitchOn?2:1,
};
await updateSubAdmin({ data: formData, id }).then((response) => {
if (response?.data?.statusCode) {
toast({
render: () => <ToastBox message={response?.data?.message} />,
});
setIsLoadingBtn(false);
setAlert(false);
navigate("/subadmin");
} else if (response?.error?.status === 400) {
toast({
render: () => (
<ToastBox
message={response?.error?.data?.message}
status={"error"}
/>
),
});
setIsLoadingBtn(false);
setAlert(false);
}
});
} catch (error) {
console.log(error);
setIsLoadingBtn(false);
navigate("/subadmin");
}
} else {
try {
const formData = {
...form,
role_xid: isSwitchOn?2:1,
};
await createSubAdmin(formData).then((response) => {
console.log(response);
if (response?.data?.statusCode === 201) {
toast({
render: () => <ToastBox message={response?.data?.message} />,
});
setIsLoadingBtn(false);
navigate("/subadmin");
} else if (response?.error?.status === 400) {
toast({
render: () => (
<ToastBox
message={response?.error?.data?.message}
status={"error"}
/>
),
});
setIsLoadingBtn(false);
setAlert(false);
}
});
} catch (error) {
console.log(error);
setIsLoadingBtn(false);
navigate("/subadmin");
}
}
};
// ====================== [Update Form Object] =========================
const formFields = [
{
label: "First Name",
placeHolder: " ",
name: "firstName",
type: "text",
isRequired: true,
section: "",
maxLength: 50,
helperText: `Maximum length should be 50 characters. You have entered ${
watch()?.firstName?.length || 0
} characters.`,
},
{
label: "Last Name",
name: "lastName",
placeHolder: " ",
type: "text",
isRequired: true,
section: "",
arabic: true,
right: true,
maxLength: 55,
helperText: `Maximum length should be 55 characters. You have entered ${
watch()?.lastName?.length || 0
} characters.`,
},
{
label: "Email address",
name: "emailAddress",
placeHolder: " ",
type: "email",
// isRequired: true,
section: "",
},
];
// ==================== [Create Form Object] =======================
const formEditFields = [
{
label: "First Name",
placeHolder: " ",
name: "firstName",
type: "text",
isRequired: true,
section: "",
maxLength: 55,
helperText: `Maximum length should be 55 characters. You have entered ${
watch()?.firstName?.length || 0
} characters.`,
},
{
label: "Last Name",
name: "lastName",
placeHolder: " ",
type: "text",
isRequired: true,
section: "",
arabic: true,
maxLength: 55,
helperText: `Maximum length should be 55 characters. You have entered ${
watch()?.lastName?.length || 0
} characters.`,
},
{
label: "Email Address",
name: "emailAddress",
placeHolder: " ",
type: "email",
// isRequired: true,
section: "",
},
];
// ====================== [Group Create Fields] =========================
const groupedEditFields = formEditFields.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
// ====================== [Group Update Fields] =======================
const groupedFields = formFields.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
// ==================== [On Submit] ========================
console.log(errors);
const onSubmit = async (data) => {
console.log("Hit");
if (Object.keys(errors).length === 0) {
setForm(data);
setAlert(true);
}
};
return isLoading ? (
<FullscreenLoaders />
) : (
<Box {...OPACITY_ON_LOAD} overflowY={"scroll"} height={"100vh"} pb={14}>
{/* ===================== [Switch Button] ======================== */}
<Box
display={"flex"}
justifyContent={"space-between"}
alignItems={"center"}
mt={5}
px={4}
>
<Text
fontSize={"sm"}
mb={0}
onClick={() => navigate(-1)}
cursor={"pointer"}
>
<ArrowBackIcon fontSize={"xl"} me={2} />
Add Details
</Text>
<RoleSwitchButton
isSwitchOn={isSwitchOn}
setIsSwitchOn={setIsSwitchOn}
/>
</Box>
{/* ====================== [Form Input] ====================== */}
<FormInputMain
groupedFields={params?.id ? groupedEditFields : groupedFields}
control={control}
errors={errors}
onSubmit={handleSubmit(onSubmit)}
submitTitle={params?.id ? "Update" : "Submit"}
></FormInputMain>
{/* ======================= [Modal] =========================== */}
<CustomAlertDialog
isOpen={alert}
onClose={() => setAlert(false)}
alertHandler={handleConfirm}
message={
id
? "Are you sure you want to update this?"
: "Are you sure you want to add this?"
}
isLoading={isLoadingBtn}
/>
{/* <DummyComponent /> */}
</Box>
);
};
export default SubAdminUpdateCreate;

View File

@@ -233,6 +233,11 @@ export const nav = [
path: "/bank-details",
icon: RiBankLine,
},
{
title: "Sub Admin",
path: "/subadmin",
icon: RiFileUserLine,
},
],
type: "accordion",
Icon: MdOutlineAdminPanelSettings,

View File

@@ -46,6 +46,8 @@ import EmailNotification from "../Pages/EmailNotification/EmailNotification";
import User from "../Pages/User/User";
import AddUser from "../Pages/User/AddUser";
import Profile from "../Pages/Profile/Profile";
import SubAdmin from "../Pages/SubAdmin/SubAdmin";
import SubAdminUpdateCreate from "../Pages/SubAdmin/SubAdminUpdateCreate";
export const RouteLink = [
// =============[ Tanami ]================
@@ -123,6 +125,9 @@ export const RouteLink = [
// { path: "/bank-details", Component: UnderConstruction },
{ path: "/bank-details/edit-bank-details/:id", Component: EditBankDetails },
{ path: "/profile", Component: Profile },
{ path: "/subadmin", Component: SubAdmin },
{ path: "/subadmin/subadmin-update/:id", Component: SubAdminUpdateCreate },
{ path: "/subadmin/subadmin-update", Component: SubAdminUpdateCreate },
@@ -134,8 +139,5 @@ export const RouteLink = [
// { path: "/fawateer-approver", Component: ApproveRequest },
// { path: "/approver-history", Component: ApproveHistory },
];

View File

@@ -0,0 +1,98 @@
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./token.serivce";
// Define a service using a base URL and expected endpoints
export const sabAdminMaster = createApi({
reducerPath: "sabAdminMaster",
baseQuery: baseQuery,
tagTypes: ["getSubAdmin", "prePopulate"],
endpoints: (builder) => ({
// ======[Get All]=====
getSubAdminMaster: builder.query({
query: () => `/subadmin/admin/getAll`,
providesTags: ["getSubAdmin"],
}),
// // ========[ Create ]========
createSubAdmin: builder.mutation({
query: (data) => ({
url: `/subadmin/admin/create`,
method: "POST",
body: data,
}),
invalidatesTags: ["getSubAdmin","prePopulate"],
}),
// // ========[Update Sponser]========
updateSubAdmin: builder.mutation({
query: ({ data, id }) => ({
url: `/subadmin/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getSubAdmin"],
}),
getSubAdminById: builder.query({
query: (id) => `/subadmin/admin/${id}`,
}),
// // ========[Toggle Status]========
toggleStatus: builder.mutation({
query: (id, data) => ({
url: `/subadmin/admin/toggle-role/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getSubAdmin"],
}),
// // ========[Get Active]========
// getActiveSponserMaster: builder.query({
// query: () => `/sponsor/admin/active`,
// }),
// getSponserMasterActive: builder.query({
// query: () => "/sponsor/admin/active",
// }),
// // ======[Get ID]=====
// getSponserById: builder.query({
// query: (id) => `/sponsor/admin/${id}`,
// }),
// // ========[Update Sponser]========
// updateSponser: builder.mutation({
// query: ({ data, id }) => ({
// url: `/sponsor/admin/${id}`,
// method: "PATCH",
// body: data,
// }),
// invalidatesTags: ["getSponser"],
// }),
}),
});
// Export hooks for usage in functional components
export const {
useGetSubAdminMasterQuery,
useCreateSubAdminMutation,
useUpdateSubAdminMutation,
useGetSubAdminByIdQuery,
useToggleStatusMutation
} = sabAdminMaster;

View File

@@ -17,6 +17,7 @@ import { deleteRequest } from "../Services/delete.request.service";
import { banInvestorDetails } from "../Services/ban.investor.service";
import { fawateerRequest } from "../Services/fawateer.request.service";
import { fawateerMaker } from "../Services/fawateer.maker.service";
import { sabAdminMaster } from "../Services/subadmin.service";
export const store = configureStore({
reducer: {
@@ -35,6 +36,7 @@ export const store = configureStore({
[banInvestorDetails.reducerPath]: banInvestorDetails.reducer,
[fawateerRequest.reducerPath]: fawateerRequest.reducer,
[fawateerMaker.reducerPath]: fawateerMaker.reducer,
[sabAdminMaster.reducerPath]: sabAdminMaster.reducer,
// Add other reducers as needed
},
@@ -59,7 +61,7 @@ export const store = configureStore({
banInvestorDetails.middleware,
fawateerRequest.middleware,
fawateerMaker.middleware,
sabAdminMaster.middleware,
),
});