From 3061b8834322b1d7fd6c4cc94eb7fb41588067a2 Mon Sep 17 00:00:00 2001 From: deepvasoya Date: Wed, 21 May 2025 16:02:43 +0530 Subject: [PATCH] feat: admin staff management --- src/hooks/useQuery.jsx | 10 + src/hooks/useScript.js | 55 ++ src/routes/index.js | 11 +- src/services/auth.services.js | 107 ++- .../ForgotPassword/ForgotPasswordForm.jsx | 150 ++++ .../ForgotPassword/forgotPasswordStyles.js | 189 +++++ src/views/ForgotPassword/index.jsx | 82 ++ src/views/ResetPassword/ResetPasswordForm.jsx | 259 +++++++ src/views/ResetPassword/index.jsx | 94 +++ .../ResetPassword/resetPasswordStyles.js | 211 ++++++ src/views/StaffManagement/index.jsx | 699 ++++++++++-------- src/views/StaffManagement/staffStyles.js | 119 +++ 12 files changed, 1647 insertions(+), 339 deletions(-) create mode 100644 src/hooks/useQuery.jsx create mode 100644 src/hooks/useScript.js create mode 100644 src/views/ForgotPassword/ForgotPasswordForm.jsx create mode 100644 src/views/ForgotPassword/forgotPasswordStyles.js create mode 100644 src/views/ForgotPassword/index.jsx create mode 100644 src/views/ResetPassword/ResetPasswordForm.jsx create mode 100644 src/views/ResetPassword/index.jsx create mode 100644 src/views/ResetPassword/resetPasswordStyles.js create mode 100644 src/views/StaffManagement/staffStyles.js diff --git a/src/hooks/useQuery.jsx b/src/hooks/useQuery.jsx new file mode 100644 index 0000000..63dc613 --- /dev/null +++ b/src/hooks/useQuery.jsx @@ -0,0 +1,10 @@ +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; + +const useQuery = () => { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +}; + +export default useQuery; diff --git a/src/hooks/useScript.js b/src/hooks/useScript.js new file mode 100644 index 0000000..5e9fdb2 --- /dev/null +++ b/src/hooks/useScript.js @@ -0,0 +1,55 @@ +import { useState, useEffect } from 'react'; + +const useScript = ({ + src, + async = true, + type = 'text/javascript', + noModule = false, +}) => { + const [status, setStatus] = useState(src ? 'loading' : 'idle'); + + useEffect(() => { + if (!src) { + setStatus('idle'); + return; + } + + // Check if the script is already present + let script = document.querySelector(`script[src="${src}"]`); + + if (!script) { + // Create script element and add it to the document head + script = document.createElement('script'); + script.src = src; + script.async = async; + script.type = type; + script.noModule = noModule; + + // Set initial status + setStatus('loading'); + + // Script event listeners + const onScriptLoad = () => setStatus('ready'); + const onScriptError = () => setStatus('error'); + + script.addEventListener('load', onScriptLoad); + script.addEventListener('error', onScriptError); + + document.head.appendChild(script); + + // Cleanup function + return () => { + script.removeEventListener('load', onScriptLoad); + script.removeEventListener('error', onScriptError); + document.head.removeChild(script); + }; + } else { + // If script is already present, update status accordingly + setStatus('ready'); + } + }, [src, async, type, noModule]); + + return status; +}; + +export default useScript; diff --git a/src/routes/index.js b/src/routes/index.js index 6d7efa8..084fa0f 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,10 +10,11 @@ import MockPayment from "../views/MockPayment"; import Users from "../views/User"; import ClinicSetup from "../views/ClinicSetup"; import ClinicTranscripts from "../views/ClinicTranscripts"; -import ContractManagement from "../views/ContractManagement"; import MasterDataManagement from "../views/MasterData"; import PaymentManagement from "../views/PaymentManagement"; import ClinicDocUpdater from "../views/ClinicDocUpdate"; +import ForgotPassword from "../views/ForgotPassword"; +import ResetPassword from "../views/ResetPassword"; export const routesData = [ { @@ -43,6 +44,14 @@ export const routesData = [ path: "signup/your-details", component: YourDetailsForm, }, + { + path: "forgotpassword", + component: ForgotPassword, + }, + { + path: 'reset-password', + component: ResetPassword, + }, ], }, ]; diff --git a/src/services/auth.services.js b/src/services/auth.services.js index da183d3..833785a 100644 --- a/src/services/auth.services.js +++ b/src/services/auth.services.js @@ -1,38 +1,83 @@ import { axiosInstance } from "../config/api"; export const signup = (data) => { - const url = '/auth/register'; - return new Promise((resolve, reject) => { - axiosInstance - .post(url, data) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; + const url = "/auth/register"; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; +export const createAdmin = (data) => { + const url = "/admin/user"; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const getAdmins = (params) => { + + let searchParams = new URLSearchParams(); + searchParams.append("size", params?.pagination?.pageSize ?? 10); + searchParams.append("page", params?.pagination.pageIndex ?? 0); + searchParams.append("search", params?.globalFilter ?? ""); + + const url = `/admin/?${searchParams.toString()}`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; export const getEmailOtp = (data) => { - const url = '/auth/send-otp'; - return new Promise((resolve, reject) => { - axiosInstance - .post(url, null, { params: data }) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; + const url = "/auth/send-otp"; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, null, { params: data }) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; - export const verifyOtp = (data) => { - const url = '/auth/verify-otp'; - return new Promise((resolve, reject) => { - axiosInstance - .post(url, data) - .catch((err) => { - if(err.status==200){ - resolve(err) - }else{ - reject(err) - } - }) - .then((response) => resolve(response)) - }); - }; \ No newline at end of file +export const verifyOtp = (data) => { + const url = "/auth/verify-otp"; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .catch((err) => { + if (err.status == 200) { + resolve(err); + } else { + reject(err); + } + }) + .then((response) => resolve(response)); + }); +}; + +export const forgotPassword = (data) => { + const url = "/auth/admin/forget-password"; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, null, { params: data }) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const resetPassword = (data) => { + const url = "/auth/admin/reset-password"; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; diff --git a/src/views/ForgotPassword/ForgotPasswordForm.jsx b/src/views/ForgotPassword/ForgotPasswordForm.jsx new file mode 100644 index 0000000..90cd8c9 --- /dev/null +++ b/src/views/ForgotPassword/ForgotPasswordForm.jsx @@ -0,0 +1,150 @@ +import { LoadingButton } from '@mui/lab'; +import { Box, InputLabel, TextField, Typography } from '@mui/material'; +import { useFormik } from 'formik'; +import { useRef } from 'react'; +import * as Yup from 'yup'; +import { NOTIFICATION } from '../../constants'; +import { forgotPassword } from '../../services/auth.services'; +import { pushNotification } from '../../utils/notification'; +import { useStyles } from './forgotPasswordStyles'; + +const validationSchema = Yup.object().shape({ + email: Yup.string() + .email('Invalid Email Address') + .required('Email is required'), +}); + +function ForgotPasswordForm({ onSubmit, setEmail }) { + const classes = useStyles(); + + const emailRef = useRef(null); + + const formik = useFormik({ + initialValues: { email: '' }, + validationSchema, + onSubmit: async (values) => { + try { + const response = await forgotPassword(values); + if (response.status === 200) { + // setTimer(15); + setEmail(values.email); + // setShowButton(false); + pushNotification(response?.data?.message, NOTIFICATION.SUCCESS); + onSubmit(); + } + } catch (error) { + if (error.response?.data?.message) { + formik.setErrors({ + email: error?.response?.data?.message, + }); + emailRef.current.focus(); + } + } finally { + formik.setSubmitting(false); + } + }, + }); + + const handleSubmitClick = async (e) => { + e.preventDefault(); + const formikError = await formik.validateForm(); + const errors = Object.keys(formikError); + + if (errors.length) { + // Find the first invalid field and focus it + const firstErrorField = errors[0]; + const firstErrorRef = emailRef?.current; + + if (firstErrorRef) { + // Scroll to the first invalid field smoothly + if (typeof firstErrorRef?.scrollIntoView === 'function') { + firstErrorRef?.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + } + + // Focus the field after a slight delay (to ensure scrolling completes first) + setTimeout(() => firstErrorRef.focus(), 300); + } + + // Show error notification + if (formik?.touched[firstErrorField]) + pushNotification(formikError[firstErrorField], NOTIFICATION.ERROR); + } + + formik.handleSubmit(); + }; + + return ( + +
+ + + + Please provide the email address that you used when you signed up + for your account. + + + + + Email Address* + + + + + {/* )} */} + + We will send you an email that will allow you to reset your password. + + + Reset Password + + {/* + + If you no longer have access to this email account, please contact + our support team at + + + {CONTACT_EMAIL} + + */} +
+
+ ); +} + +export default ForgotPasswordForm; diff --git a/src/views/ForgotPassword/forgotPasswordStyles.js b/src/views/ForgotPassword/forgotPasswordStyles.js new file mode 100644 index 0000000..9de1bb3 --- /dev/null +++ b/src/views/ForgotPassword/forgotPasswordStyles.js @@ -0,0 +1,189 @@ +import makeStyles from '@mui/styles/makeStyles'; +import backgroundImage from '../../assets/images/background/background.jpg'; +import { pxToRem } from '../../theme/typography'; + +export const useStyles = makeStyles((theme) => ({ + root: { + height: '100vh', + backgroundImage: `url(${backgroundImage})`, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + card: { + width: '370px', + maxHeight: '835px', + [theme.breakpoints.up('sm')]: { + width: '500px', + }, + [theme.breakpoints.up('md')]: { + width: '500px', + }, + [theme.breakpoints.up('lg')]: { + width: '500px', + }, + [theme.breakpoints.up('xl')]: { + width: '500px', + maxHeight: '735px', + }, + boxShadow: 'none', + borderRadius: theme.spacing(3.8), + opacity: '0.95', + }, + forgotPasswordHeader: { + marginTop: theme.spacing(4.5), + }, + right: { + textAlign: 'center', + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + }, + form: { + width: '65%', + }, + formBody: { + justifyContent: 'start', + display: 'flex', + flexDirection: 'column', + [theme.breakpoints.up('md')]: { + // height: '162.5px', + }, + [theme.breakpoints.up('lg')]: { + // height: '234px', + }, + [theme.breakpoints.up('xl')]: { + // height: '325px', + }, + + '& .MuiTextField-root': { + marginTop: theme.spacing(0.8), + [theme.breakpoints.up('md')]: { + marginTop: theme.spacing(0.8), + }, + [theme.breakpoints.up('lg')]: { + marginTop: theme.spacing(0.8), + }, + [theme.breakpoints.up('xl')]: { + marginTop: theme.spacing(4), + }, + }, + }, + formHeader: { + marginTop: theme.spacing(1.2), + marginBottom: theme.spacing(3), + }, + formFooter: { + marginBottom: theme.spacing(4.0), + }, + heading: { + fontFamily: 'Inter-Bold', + // width: '70%', + textAlign: 'center', + display: 'flex', + justifyContent: 'flex-start', + lineHeight: 1.2, + fontSize: pxToRem(27), + [theme.breakpoints.up('md')]: { + fontSize: pxToRem(27), + }, + [theme.breakpoints.up('lg')]: { + fontSize: pxToRem(27), + }, + [theme.breakpoints.up('xl')]: { + fontSize: pxToRem(27), + }, + }, + subHeading: { + lineHeight: 1.1, + textAlign: 'left', + fontSize: pxToRem(12), + marginTop: theme.spacing(0), + [theme.breakpoints.up('md')]: { + fontSize: pxToRem(13), + marginTop: theme.spacing(0), + }, + [theme.breakpoints.up('lg')]: { + fontSize: pxToRem(13), + marginTop: theme.spacing(0), + }, + [theme.breakpoints.up('xl')]: { + fontSize: pxToRem(13), + marginTop: theme.spacing(0), + }, + }, + labelStyle: { + marginBottom: '0px', + fontSize: pxToRem(3.2), + color: theme.palette.grey[10], + // paddingBottom: '20px', + }, + inputTitle: { + '&.MuiTypography-body1': { + fontSize: pxToRem(12), + color: theme.palette.grey[10], + }, + fontSize: pxToRem(12), + textAlign: 'left', + // opacity: '0.54', + color: theme.palette.grey[10], + marginBottom: theme.spacing(0.6), + marginLeft: theme.spacing(0.6), + }, + icon: { + cursor: 'pointer', + paddingLeft: '30px', + display: 'flex', + alignItems: 'center', + width: '50px', + [theme.breakpoints.up('md')]: { + width: '50px', + }, + [theme.breakpoints.up('lg')]: { + width: '50px', + }, + [theme.breakpoints.up('xl')]: { + width: '50px', + }, + }, + submitButton: { + fontFamily: 'Inter-Regular', + fontWeight: 500, + marginTop: theme.spacing(1.0), + marginBottom: theme.spacing(3.0), + fontSize: pxToRem(12.8), + justifyContent: 'center', + borderRadius: '6px', + padding: '20px', + [theme.breakpoints.up('md')]: { + padding: '20px', + fontSize: pxToRem(12.8), + }, + [theme.breakpoints.up('lg')]: { + padding: '20px', + fontSize: pxToRem(12.8), + }, + [theme.breakpoints.up('xl')]: { + height: '10px', + fontSize: pxToRem(12.8), + }, + }, + sendEmailText: { + marginTop: theme.spacing(2.0), + textAlign: 'left', + fontSize: pxToRem(10.4), + }, + emailText: { + lineHeight: 1.1, + fontSize: pxToRem(12.48), + }, + emailSentMainDiv: { + width: '70%', + paddingTop: theme.spacing(3.4), + paddingBottom: theme.spacing(7.0), + fontSize: pxToRem(12.8), + }, +})); diff --git a/src/views/ForgotPassword/index.jsx b/src/views/ForgotPassword/index.jsx new file mode 100644 index 0000000..777a3d6 --- /dev/null +++ b/src/views/ForgotPassword/index.jsx @@ -0,0 +1,82 @@ +import { ArrowBackOutlined } from '@mui/icons-material'; +import { Box, Card, Grid, Typography } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import ForgotPasswordForm from './ForgotPasswordForm'; +import { useStyles } from './forgotPasswordStyles'; +import { useState } from 'react'; + +function ForgotPassword() { + const navigate = useNavigate(); + const classes = useStyles(); + const [resetPasswordLinkSend, setResetPasswordLinkSend] = useState(false); + const [email, setEmail] = useState(''); + + const handleFormSubmit = () => { + setResetPasswordLinkSend(true); + }; + const handleGoBackClick = () => { + navigate('/auth/login'); + }; + return ( + + + + {!resetPasswordLinkSend ? ( + + + + + + + + Reset your Password + + + + + + ) : ( + + {/* */} + + Email Sent + + + {/* {` An email with instructions on how to reset your password has + been sent to ${email}. It is valid for next 24 hrs. + Check your spam or junk folder if you don’t see the email in + your inbox.`} */} + {`An email with instructions on how to reset your password has + been sent to `} + {email} + {`. It is valid for the next 24 hrs. Check your spam or junk folder if you don’t see the email in + your inbox.`} + + {/* */} + + )} + + + + ); +} + +export default ForgotPassword; diff --git a/src/views/ResetPassword/ResetPasswordForm.jsx b/src/views/ResetPassword/ResetPasswordForm.jsx new file mode 100644 index 0000000..5e91938 --- /dev/null +++ b/src/views/ResetPassword/ResetPasswordForm.jsx @@ -0,0 +1,259 @@ +import { LoadingButton } from '@mui/lab'; +import { + Box, + IconButton, + InputLabel, + TextField, + Typography, +} from '@mui/material'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import { useStyles } from './resetPasswordStyles'; +import { useRef, useState } from 'react'; +import PasswordValidation from '../Login/component/PasswordValidation'; +import { VisibilityOffOutlined, VisibilityOutlined } from '@mui/icons-material'; +import useQuery from '../../hooks/useQuery'; +import { + passwordLengthRegex, + passwordLetterRegex, + passwordNumberRegex, + passwordSpacesRegex, +} from '../../utils/regex'; +import { pushNotification } from '../../utils/notification'; +import { NOTIFICATION } from '../../constants'; +import { resetPassword } from '../../services/auth.services'; + +const validationSchema = Yup.object().shape({ + password: Yup.string() + .matches(passwordLengthRegex, 'Password must be at least 8 characters') + .matches(passwordLetterRegex, 'Password must contain at least one letter') + .matches(passwordNumberRegex, 'Password must contain at least one number') + .matches(passwordSpacesRegex, 'Password must not start or end with a space') + .required('Password is required'), + confirmPassword: Yup.string() + .oneOf([Yup.ref('password'), null], 'Passwords must match') + .required('Passwords must match'), +}); + +function ResetPasswordForm({ onSubmit }) { + const query = useQuery(); + const classes = useStyles(); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + + const fieldRefs = { + password: useRef(null), + confirmPassword: useRef(null), + }; + + const formik = useFormik({ + initialValues: { password: '', confirmPassword: '' }, + validationSchema, + onSubmit: (values) => handleSubmit(values, formik), + }); + + const handleSubmit = async (values, formik) => { + try { + values.token = query.get('token'); + + const requestBody = { + token: values.token, + password: values.password, + }; + + const response = await resetPassword(requestBody); + if (response.status === 200) { + pushNotification(response?.data?.message, NOTIFICATION.SUCCESS); + } + onSubmit(); + } catch (error) { + return; + } finally { + formik.setSubmitting(false); + formik.resetForm(); + } + }; + const handleTogglePasswordVisibility = (field) => { + if (field === 'password') { + setShowPassword((prevShowPassword) => !prevShowPassword); + } else if (field === 'confirmPassword') { + setShowConfirmPassword( + (prevShowConfirmPassword) => !prevShowConfirmPassword + ); + } + }; + + const handleSubmitClick = async (e) => { + e.preventDefault(); + const formikErrors = await formik.validateForm(); + const errors = Object.keys(formikErrors); + + if (errors.length) { + // Find the first invalid field and focus it + const firstErrorField = errors[0]; + const firstErrorRef = fieldRefs[firstErrorField]?.current; + + if (firstErrorRef) { + // Scroll to the first invalid field smoothly + if (typeof firstErrorRef?.scrollIntoView === 'function') { + firstErrorRef?.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + } + + // Focus the field after a slight delay (to ensure scrolling completes first) + setTimeout(() => firstErrorRef.focus(), 300); + } + + // Show error notification + if (formik?.touched[firstErrorField]) + pushNotification(formikErrors[firstErrorField], NOTIFICATION.ERROR); + } + + formik.handleSubmit(); + }; + + return ( + +
+ + + + Note: Password must be at least 8 characters, contain 1 letter and + 1 number, with no spaces at the beginning/end. + + + + + Enter New Password + + + handleTogglePasswordVisibility('password')} + edge="end" + > + {!showPassword ? ( + + ) : ( + + )} + + ), + }} + /> + {/* Password validation display */} + {!formik.isValid && ( + <> + + Note: Password must be + + + + + + + + + )} + + + + + Confirm New Password + + + + handleTogglePasswordVisibility('confirmPassword') + } + edge="end" + > + {!showConfirmPassword ? ( + + ) : ( + + )} + + ), + }} + /> + + + Save New Password + +
+
+ ); +} + +export default ResetPasswordForm; diff --git a/src/views/ResetPassword/index.jsx b/src/views/ResetPassword/index.jsx new file mode 100644 index 0000000..1f63b5a --- /dev/null +++ b/src/views/ResetPassword/index.jsx @@ -0,0 +1,94 @@ +import { Box, Card, Grid, Typography } from '@mui/material'; +import { useLocation, useNavigate } from 'react-router-dom'; +import ResetPasswordForm from './ResetPasswordForm'; +import { useStyles } from './resetPasswordStyles'; +import { useState } from 'react'; +import ResetPasswordSuccessIcon from '../../assets/images/icon/ResetPasswordSuccessIcon.png'; +import { LoadingButton } from '@mui/lab'; + +function ResetPassword() { + const navigate = useNavigate(); + const location = useLocation(); + const classes = useStyles(); + const [resetPasswordLinkSend, setResetPasswordLinkSend] = useState(false); + const handleFormSubmit = () => { + setResetPasswordLinkSend(true); + }; + + // Function to get URL parameters + const getQueryParams = () => new URLSearchParams(location.search); + + // Determine if we are resetting or creating a password based on URL params + const queryParams = getQueryParams(); + const isCreatePassword = queryParams.get('createPassword') === 'true'; + + const headingText = !isCreatePassword ? 'Reset' : 'Create'; + const successMessage = !isCreatePassword + ? 'Password Reset Successful' + : 'Create Password Successful'; + + return ( + + + + {!resetPasswordLinkSend ? ( + + + + + + {headingText} your Password + + + + + + ) : ( + + + + + + + {successMessage} + + + + Awesome, you have successfully{' '} + {!isCreatePassword ? 'updated' : 'created'} your password. + + + navigate('/auth/login')} + > + Back to Login + + + + )} + + + + ); +} + +export default ResetPassword; diff --git a/src/views/ResetPassword/resetPasswordStyles.js b/src/views/ResetPassword/resetPasswordStyles.js new file mode 100644 index 0000000..f2a8852 --- /dev/null +++ b/src/views/ResetPassword/resetPasswordStyles.js @@ -0,0 +1,211 @@ +import makeStyles from '@mui/styles/makeStyles'; +import backgroundImage from '../../assets/images/background/background.jpg'; +import { pxToRem } from '../../theme/typography'; + +export const useStyles = makeStyles((theme) => ({ + root: { + height: '100vh', + backgroundImage: `url(${backgroundImage})`, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + card: { + width: '370px', + maxHeight: '835px', + [theme.breakpoints.up('sm')]: { + width: '500px', + }, + [theme.breakpoints.up('md')]: { + width: '500px', + }, + [theme.breakpoints.up('lg')]: { + width: '500px', + }, + [theme.breakpoints.up('xl')]: { + width: '500px', + maxHeight: '735px', + }, + boxShadow: 'none', + borderRadius: theme.spacing(3.8), + opacity: '0.95', + }, + forgotPasswordHeader: { + marginTop: theme.spacing(4.5), + }, + right: { + textAlign: 'center', + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + }, + form: { + width: '65%', + }, + formBody: { + justifyContent: 'center', + display: 'flex', + flexDirection: 'column', + [theme.breakpoints.up('md')]: { + // height: '162.5px', + }, + [theme.breakpoints.up('lg')]: { + // height: '234px', + }, + [theme.breakpoints.up('xl')]: { + // height: '325px', + }, + + '& .MuiTextField-root': { + marginTop: theme.spacing(0.4), + [theme.breakpoints.up('md')]: { + marginTop: theme.spacing(0.4), + }, + [theme.breakpoints.up('lg')]: { + marginTop: theme.spacing(0.4), + }, + [theme.breakpoints.up('xl')]: { + marginTop: theme.spacing(0.4), + }, + }, + }, + formHeader: { + marginTop: theme.spacing(1.2), + marginBottom: theme.spacing(3), + }, + heading: { + fontFamily: 'Inter-Bold', + // width: '70%', + textAlign: 'center', + display: 'flex', + justifyContent: 'flex-start', + lineHeight: 1.2, + fontSize: pxToRem(27), + [theme.breakpoints.up('md')]: { + fontSize: pxToRem(27), + }, + [theme.breakpoints.up('lg')]: { + fontSize: pxToRem(27), + }, + [theme.breakpoints.up('xl')]: { + fontSize: pxToRem(27), + }, + }, + successIcon: { + display: 'flex', + justifyContent: 'center', + marginTop: theme.spacing(1.1), + }, + resetPasswordheading: { + // width: '70%', + display: 'flex', + fontFamily: 'Inter-Bold', + justifyContent: 'center', + marginTop: theme.spacing(3.8), + lineHeight: 1.2, + fontSize: pxToRem(27), + [theme.breakpoints.up('md')]: { + fontSize: pxToRem(27), + }, + [theme.breakpoints.up('lg')]: { + fontSize: pxToRem(27), + }, + [theme.breakpoints.up('xl')]: { + fontSize: pxToRem(27), + }, + }, + subHeading: { + width: '90%', + lineHeight: 1.1, + textAlign: 'left', + fontSize: pxToRem(12), + marginTop: theme.spacing(0), + marginBottom: theme.spacing(1.0), + [theme.breakpoints.up('md')]: { + fontSize: pxToRem(13), + marginTop: theme.spacing(0), + }, + [theme.breakpoints.up('lg')]: { + fontSize: pxToRem(13), + marginTop: theme.spacing(0), + }, + [theme.breakpoints.up('xl')]: { + fontSize: pxToRem(13), + marginTop: theme.spacing(0), + }, + }, + resetPasswordTextField: { + marginTop: '0px', + }, + confirmResetBox: { + marginTop: theme.spacing(1.2), + }, + passwordCheckListTitle: { + color: 'grey', + fontSize: pxToRem(14.4), + fontStyle: 'italic', + textAlign: 'left', + marginBottom: theme.spacing(0.4), + }, + passwordCheckList: { + color: 'grey', + fontSize: pxToRem(14.4), + paddingTop: '2px', + fontStyle: 'italic', + }, + labelStyle: { + marginBottom: '0px', + fontSize: pxToRem(3.2), + textAlign: 'left', + color: theme.palette.grey[10], + // paddingBottom: '20px', + }, + inputTitle: { + '&.MuiTypography-body1': { + fontSize: pxToRem(12), + color: theme.palette.grey[10], + }, + fontSize: pxToRem(12), + // opacity: '0.54', + color: theme.palette.grey[10], + marginBottom: theme.spacing(0.6), + marginLeft: theme.spacing(0.6), + }, + submitButton: { + fontFamily: 'Inter-Regular', + fontWeight: 500, + marginTop: theme.spacing(2.2), + marginBottom: theme.spacing(4.0), + fontSize: pxToRem(12.8), + justifyContent: 'center', + borderRadius: '6px', + padding: '20px', + [theme.breakpoints.up('md')]: { + padding: '20px', + fontSize: pxToRem(12.8), + }, + [theme.breakpoints.up('lg')]: { + padding: '20px', + fontSize: pxToRem(12.8), + }, + [theme.breakpoints.up('xl')]: { + height: '10px', + fontSize: pxToRem(12.8), + }, + }, + resetPasswordSuccessIcon: { + width: '17%', + }, + sendEmailText: { + marginTop: theme.spacing(2.0), + textAlign: 'center', + fontSize: pxToRem(10.4), + }, + emailText: { + lineHeight: 1.1, + fontSize: pxToRem(12.48), + }, +})); diff --git a/src/views/StaffManagement/index.jsx b/src/views/StaffManagement/index.jsx index b2b699e..369a4dd 100644 --- a/src/views/StaffManagement/index.jsx +++ b/src/views/StaffManagement/index.jsx @@ -1,9 +1,9 @@ -import AddIcon from '@mui/icons-material/Add'; -import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; -import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; -import CloseIcon from '@mui/icons-material/Close'; -import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from "@mui/icons-material/Add"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; +import CloseIcon from "@mui/icons-material/Close"; +import PersonAddIcon from "@mui/icons-material/PersonAdd"; +import SearchIcon from "@mui/icons-material/Search"; import { Alert, Box, @@ -18,7 +18,6 @@ import { InputAdornment, Paper, Snackbar, - Table, TableBody, TableCell, TableContainer, @@ -26,18 +25,26 @@ import { TableRow, TextField, Typography, -} from '@mui/material'; -import React, { useState } from 'react'; -import CustomBreadcrumbs from '../../components/CustomBreadcrumbs'; -import PageHeader from '../../components/PageHeader'; +} from "@mui/material"; +import React, { useMemo, useRef, useState } from "react"; +import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; +import PageHeader from "../../components/PageHeader"; +import { createAdmin, getAdmins } from "../../services/auth.services"; +import { pushNotification } from "../../utils/notification"; +import { NOTIFICATION } from "../../constants"; +import { useTheme } from "@emotion/react"; +import { useStyles } from "./staffStyles"; +import Table from "../../components/Table"; const StaffManagement = () => { + const theme = useTheme(); + const classes = useStyles(); + const ref = useRef(null); // State for form fields - const [firstName, setFirstName] = useState(''); - const [lastName, setLastName] = useState(''); - const [email, setEmail] = useState(''); - const [emailError, setEmailError] = useState(''); - const queryParams = new URLSearchParams(location.search); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [email, setEmail] = useState(""); + const [emailError, setEmailError] = useState(""); // State for staff list const [staffList, setStaffList] = useState([]); @@ -46,7 +53,7 @@ const StaffManagement = () => { const [openDialog, setOpenDialog] = useState(false); // State for search - const [searchQuery, setSearchQuery] = useState(''); + const [searchQuery, setSearchQuery] = useState(""); // State for pagination const [page, setPage] = useState(1); @@ -55,8 +62,8 @@ const StaffManagement = () => { // State for notification const [notification, setNotification] = useState({ open: false, - message: '', - severity: 'success', + message: "", + severity: "success", }); // Email validation function @@ -73,41 +80,37 @@ const StaffManagement = () => { const handleCloseDialog = () => { setOpenDialog(false); // Clear form - setFirstName(''); - setLastName(''); - setEmail(''); - setEmailError(''); + setFirstName(""); + setLastName(""); + setEmail(""); + setEmailError(""); }; // Handle form submission - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); // Validate email if (!validateEmail(email)) { - setEmailError('Please enter a valid email address'); + setEmailError("Please enter a valid email address"); return; } - // Add new staff member - const newStaff = { - id: staffList.length + 1, - firstName, - lastName, + const payload = { + username: `${firstName} ${lastName}`, email, }; - setStaffList([...staffList, newStaff]); + const response = await createAdmin(payload); + + if (response?.data?.data) { + pushNotification("Admin created successfully!", NOTIFICATION.SUCCESS); + } else { + pushNotification(response?.data?.message, NOTIFICATION.ERROR); + } // Close dialog handleCloseDialog(); - - // Show success notification - setNotification({ - open: true, - message: 'Staff member added successfully!', - severity: 'success', - }); }; // Handle notification close @@ -118,310 +121,392 @@ const StaffManagement = () => { }); }; + const getData = async (filters) => { + try { + // Remove the type parameter since it's not defined + let params = { + ...filters + }; + + const resp = await getAdmins(params); + console.log('API Response:', resp); + + return { + data: resp?.data?.data?.data, + rowCount: resp?.data?.total || 0, + }; + } catch (error) { + console.error('Error fetching admins:', error); + return { + data: [], + rowCount: 0, + }; + } + }; + // ...................breadcrumbs array........................ const breadcrumbs = [ { - label: 'Dashboard', - path: '/', + label: "Dashboard", + path: "/", }, { - label: 'Staff Management', - path: '/staff', + label: "Staff Management", + path: "/staff", }, ]; - return ( - - {/* - Staff Management - */} + const columns = useMemo(() => [ + { + size: 100, + header: "Sr. No.", + Cell: (props) => { + const tableState = props?.table?.getState(); + const serialNumber = ( + props?.row?.index + + 1 + + tableState?.pagination?.pageIndex * tableState?.pagination?.pageSize + ) + ?.toString() + ?.padStart(2, "0"); + return {serialNumber}; + }, + enableSorting: false, + }, + { + size: 280, + accessorKey: "username", + header: "User Name", + enableColumnFilter: false, + }, + { + size: 220, + accessorKey: "email", + header: "User Email Address", + enableColumnFilter: false, + }, + ]); + return ( + } - // addButtonDisabled={false} - // permissionName={'CREATE_USERS'} - // infiniteDropdown + // hideAddButton + addButtonIcon={} + onAddButtonClick={handleOpenDialog} /> - - - - {/* Staff List Header with Add Button */} - - - Staff List - - - - - - {/* Search Box */} - - setSearchQuery(e.target.value)} - InputProps={{ - startAdornment: ( - - - - ), - }} - sx={{ - backgroundColor: '#fff', - '& .MuiOutlinedInput-root': { - borderRadius: 2, - }, + + + - - {/* Staff List Table */} - -
- - - Sr. No. - User Name - - User Email Address - - - - - {staffList.length > 0 ? ( - staffList - .filter( - (staff) => - `${staff.firstName} ${staff.lastName}` - .toLowerCase() - .includes(searchQuery.toLowerCase()) || - staff.email - .toLowerCase() - .includes(searchQuery.toLowerCase()) - ) - .slice((page - 1) * rowsPerPage, page * rowsPerPage) - .map((staff, index) => ( - - - {(page - 1) * rowsPerPage + index + 1} - - {`${staff.firstName} ${staff.lastName}`} - {staff.email} - - )) - ) : ( - - - No staff members added yet - - - )} - -
- - - {/* Pagination */} - {staffList.length > 0 && ( - + + + - + + + + Add New Staff + + + + + + + + + + + setFirstName(e.target.value)} + placeholder="First Name" + required + InputLabelProps={{ + shrink: true, + }} + /> + + setLastName(e.target.value)} + placeholder="Last Name" + required + InputLabelProps={{ + shrink: true, + }} + /> + + { + setEmail(e.target.value); + setEmailError(""); + }} + placeholder="Email Address" + error={!!emailError} + helperText={emailError} + required + InputLabelProps={{ + shrink: true, + }} + /> + + + + + + + + + // + // {/* + // Staff Management + // */} - -
- )} -
+ // } + // // addButtonDisabled={false} + // // permissionName={'CREATE_USERS'} + // // infiniteDropdown + // /> - {/* Add Staff Dialog */} - - - - - - Add New Staff - - - - - - + // - + // + // {/* Staff List Header with Add Button */} + // + // + // Staff List + // - - - setFirstName(e.target.value)} - placeholder="First Name" - required - InputLabelProps={{ - shrink: true, - }} - /> + // + // - setLastName(e.target.value)} - placeholder="Last Name" - required - InputLabelProps={{ - shrink: true, - }} - /> + // {/* Search Box */} + // + // setSearchQuery(e.target.value)} + // InputProps={{ + // startAdornment: ( + // + // + // + // ), + // }} + // sx={{ + // backgroundColor: '#fff', + // '& .MuiOutlinedInput-root': { + // borderRadius: 2, + // }, + // }} + // /> + // - { - setEmail(e.target.value); - setEmailError(''); - }} - placeholder="Email Address" - error={!!emailError} - helperText={emailError} - required - InputLabelProps={{ - shrink: true, - }} - /> - - + // {/* Staff List Table */} + // + // + // + // + // Sr. No. + // User Name + // + // User Email Address + // + // + // + // + // {staffList.length > 0 ? ( + // staffList + // .filter( + // (staff) => + // `${staff.firstName} ${staff.lastName}` + // .toLowerCase() + // .includes(searchQuery.toLowerCase()) || + // staff.email + // .toLowerCase() + // .includes(searchQuery.toLowerCase()) + // ) + // .slice((page - 1) * rowsPerPage, page * rowsPerPage) + // .map((staff, index) => ( + // + // + // {(page - 1) * rowsPerPage + index + 1} + // + // {`${staff.firstName} ${staff.lastName}`} + // {staff.email} + // + // )) + // ) : ( + // + // + // No staff members added yet + // + // + // )} + // + //
+ //
- - - -
+ // {/* Pagination */} + // {staffList.length > 0 && ( + // + // - {/* Notification */} - - - {notification.message} - - -
+ // + + // + // + // )} + // + + // {/* Add Staff Dialog */} + + // {/* Notification */} + // + // + // {notification.message} + // + // + // ); }; diff --git a/src/views/StaffManagement/staffStyles.js b/src/views/StaffManagement/staffStyles.js new file mode 100644 index 0000000..a2a130e --- /dev/null +++ b/src/views/StaffManagement/staffStyles.js @@ -0,0 +1,119 @@ +import makeStyles from '@mui/styles/makeStyles'; +import { pxToRem } from '../../theme/typography'; + +export const useStyles = makeStyles((theme) => ({ + chipClass: { + height: 'fit-content', + minHeight: '30px', + padding: '2px', + alignItems: 'center', + }, + statusColor: { + color: theme.palette.primary.main, + fontSize: pxToRem(10), + }, + tabsBox: { + display: 'flex', + justifyContent: ' space-around', + // width: '55%', + marginTop: theme.spacing(0.5), + marginRight: theme.spacing(5.0), + alignItems: 'center', + }, + secondaryButton: { + width: '200px', + height: '46px', + borderRadius: '8px', + justifyContent: 'space-evenly', + fontSize: pxToRem(16), + }, + tableActionIcons: { + marginRight: theme.spacing(1.4), + width: '15px', + }, + companyNameTableColumn: { + display: 'flex', + alignItems: 'center', + }, + companyName: { + marginLeft: theme.spacing(1), + fontSize: pxToRem(14), + objectFit: 'contain', + width: '260px', + }, + companyNameLink: { + textDecoration: 'none', + color: theme.palette.grey[10], + '&:hover': { + color: theme.palette.info.main, + textDecoration: 'underline', + }, + }, + companyWebsiteLabel: { + fontSize: pxToRem(12), + }, + companyNameLogo: { + height: '40px', + width: '40px', + borderRadius: theme.shape.borderRadiusComponent, + objectFit: 'contain', + }, + sendEmailStatus: { + fontSize: pxToRem(14), + color: theme.palette.primary.main, + }, + sendEmailLastSentMailDate: { + fontSize: pxToRem(12), + }, + addDiscountCodeLink: { + fontSize: pxToRem(12), + color: theme.palette.primary.main, + }, + addDiscountCodeLabel: { + fontSize: pxToRem(14), + backgroundColor: theme.palette.common.white, + color: theme.palette.common.black, + }, + customModel: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + }, + customModelBox: { + paddingLeft: theme.spacing(5), + paddingRight: theme.spacing(5), + textAlign: 'center', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + // height: '100%', + border: 'none', + }, + newPlanTitleText: { + fontSize: pxToRem(28), + fontFamily: theme.fontFamily.bold, + }, + newPlanSubTitleText: { + fontSize: pxToRem(18), + padding: theme.spacing(1), + }, + addPlanSuccessIcon: { + padding: theme.spacing(2), + paddingTop: theme.spacing(1), + }, + planAddedText: { + fontSize: pxToRem(12), + color: theme.palette.grey[54], + display: 'flex', + alignItems: 'center', + alignSelf: 'center', + }, + verifyIcon: { + marginLeft: theme.spacing(0.3), + fontSize: pxToRem(12), + color: theme.palette.grey[54], + }, +}));