health-apps-cms/src/views/User/index.jsx

734 lines
24 KiB
JavaScript

import { LoadingButton } from "@mui/lab";
import { Box, Button, Chip, Grid, MenuItem, Select, Switch, TextField } from "@mui/material";
import { useFormik } from "formik";
import React, { useMemo, useRef, useState } from "react";
import { useSelector } from "react-redux";
import * as Yup from "yup";
import EditIcon from '@mui/icons-material/Edit';
/* ----------------- Custom Imports ----------------- */
import PageHeader from "../../components/PageHeader";
import Table from "../../components/Table";
import ConfirmationModal from "../Modal/ConfirmationModal";
import CustomModal from "../Modal/Modal";
import { useStyles } from "./userStyles";
/* ----------------- Assets ----------------- */
import AddIcon from "@mui/icons-material/Add";
import {
getUsers,
deleteUserById,
getRoles,
getUserById,
resendPasswordMailer,
revokeAdminTransferAccess,
transferMasterAdminAccess,
} from "../../services/users.service";
/* ----------------- Utils ----------------- */
import { format } from "date-fns";
import MultiSelect from "../../components/MultiSelect";
import { NOTIFICATION, NOT_AVAILABLE_TEXT } from "../../constants";
import { pushNotification } from "../../utils/notification";
/* ----------------- Validation Schema ----------------- */
const validationSchema = Yup.object().shape({
userName: Yup.string().required("User Name is required"),
email: Yup.string()
.email("Invalid Email Address")
.required("Email is required"),
mobile: Yup.string()
.matches(/^\d{10}$/, "Mobile Number must be exactly 10 digits")
.required("Mobile Number is required"),
});
function Users() {
const ref = useRef(null);
const defaultFormData = useRef({
userName: "",
email: "q@gmail.com",
mobile: "1234567890",
isEditUser: false,
lastName: "",
userType: "",
appointmentType: [],
appointmentTypeObject: "",
});
const fieldRefs = {
userName: useRef(null),
email: useRef(null),
mobile: useRef(null),
lastName: useRef(null),
userType: useRef(null),
appointmentType: useRef(null),
};
const classes = useStyles();
/* ----------------- State Variables ----------------- */
const [showModal, setShowModal] = useState(false);
const [showUserAccessModal, setShowUserAccessModal] = useState(false);
const [isEditUser, setIsEditUser] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
const [masterAdminModal, setMasterAdminModal] = useState(false);
const [buttonLoading, setButtonLoading] = useState(false);
const [userId, setUserId] = useState();
const [userTotalCount, setUserTotalCount] = useState(0);
const [selectedCheckboxes, setSelectedCheckboxes] = useState({});
const [checkboxError, setCheckboxError] = useState(false);
const [roles, setRoles] = useState();
const [isAdmin, setIsAdmin] = useState();
const isBsAdmin = useSelector((state) => state?.login?.user?.isBsAdmin);
/* ----------------- Get Users ----------------- */
const getData = async (filters) => {
const resp = await getUsers(filters);
console.log(resp);
console.log(resp?.data?.totalCount);
setUserTotalCount(resp?.data?.totalCount);
const role = await getRoles({ page: 0 });
setRoles(role?.data);
return { data: resp?.data?.records, rowCount: resp?.data?.totalCount };
};
const editUser = async (row) => {
try {
const userData = await getUserById(row?.original?.id);
const formData = {
userName: userData?.name,
email: userData?.email,
mobile: userData?.mobile,
isEditUser: true,
};
// const updatedCheckboxes = roles.reduce(
// (acc, role) => ({
// ...acc,
// [role?.id]: userData?.roles.some(
// (roleData) => roleData?.id === role?.id
// ),
// }),
// {}
// );
// setSelectedCheckboxes(updatedCheckboxes);
// formik.setValues(formData);
// toggle();
// setIsEditUser(true);
// setUserId(row?.original?.id);
} catch ({ resp }) {
// eslint-disable-next-line no-console
console.error(resp?.data?.message);
}
};
const handleToggleButton = async (row) => {
try {
// Replace this with your actual API call to update user status
// Example: await updateUserStatus(row.id, !row.isActive);
// For now, just show a notification
pushNotification(
`${row.name}'s status has been ${row.isActive ? 'deactivated' : 'activated'}`,
NOTIFICATION.SUCCESS
);
// Refresh the table data
ref.current.reFetchData();
} catch (error) {
console.error(error);
pushNotification('Failed to update status', NOTIFICATION.ERROR);
}
};
/* ----------------- Handle Submit ----------------- */
const handleSubmit = async (values, formik) => {
try {
// if (!showUserAccessModal) {
// setShowUserAccessModal(true);
// return;
// }
// const hasSelectedCheckbox =
// Object.values(selectedCheckboxes).some(Boolean);
// if (!hasSelectedCheckbox) {
// setCheckboxError(true);
// return;
// }
// setCheckboxError(false);
// const combinedValues = {
// ...values,
// name: values.userName,
// roles: Object.entries(selectedCheckboxes)
// .filter(
// ([id, isChecked]) =>
// isChecked && id !== 'transferMasterAdminAccess' && id !== null
// )
// .map(([id]) => parseInt(id)),
// transferMasterAdminAccess:
// selectedCheckboxes.transferMasterAdminAccess || false,
// };
// delete combinedValues.userName;
// delete combinedValues.confirmPassword;
// let response = isEditUser
// ? await updateUserById(userId, combinedValues)
// : await addUser(combinedValues);
// if (response.status === 200) {
// pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
pushNotification("Doctor added successfully", NOTIFICATION.SUCCESS);
// ref.current.reFetchData();
toggle();
formik.resetForm();
setSelectedCheckboxes({});
// }
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
};
/* ----------------- Toggle Modal ----------------- */
const toggle = () => {
setIsEditUser(false);
setShowUserAccessModal(false);
setShowModal(!showModal);
};
/* ----------------- Handle Checkbox Change ----------------- */
const handleCheckboxChange = (id, isChecked) => {
setSelectedCheckboxes({ ...selectedCheckboxes, [id]: isChecked });
};
/* ----------------- Table Columns ----------------- */
const columns = useMemo(
() => [
{
size: 30,
header: "S.no.",
Cell: (props) => {
const tableState = props?.table?.getState();
const serialNumber = (
props?.row?.index +
1 +
tableState?.pagination?.pageIndex * tableState?.pagination?.pageSize
)
?.toString()
?.padStart(1, "0");
return <span>{serialNumber}.</span>;
},
enableSorting: false,
},
{
accessorFn: ({ name }) => name || NOT_AVAILABLE_TEXT,
accessorKey: "name",
header: "Doctor/Nurse Name",
isBold: true,
},
{
accessorFn: ({ userType }) => userType || NOT_AVAILABLE_TEXT,
accessorKey: "userType",
header: "User Type",
},
{
accessorKey: "appointmentTypes",
header: "Appointment Types",
Cell: ({ row }) => {
const appointmentTypes = row?.original?.appointmentTypes;
// Convert to array if it's a string (comma-separated values)
const typesArray = Array.isArray(appointmentTypes) && appointmentTypes.length > 0
? appointmentTypes
: typeof appointmentTypes === 'string' && appointmentTypes.trim() !== ''
? appointmentTypes.split(',').map(type => type.trim())
: [];
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{typesArray.length > 0 ? (
typesArray.map((type, index) => {
const label = typeof type === 'string'
? type
: type?.name || type?.type || String(type);
return (
<Chip
key={index}
label={label}
size="small"
variant="outlined"
sx={{
margin: '2px',
borderRadius: '16px',
backgroundColor: '#f5f5f5',
'& .MuiChip-label': { fontSize: '0.75rem' }
}}
/>
);
})
) : (
<Chip
label={NOT_AVAILABLE_TEXT}
size="small"
variant="outlined"
sx={{
margin: '2px',
borderRadius: '16px',
backgroundColor: '#f0f0f0',
color: '#757575',
'& .MuiChip-label': { fontSize: '0.75rem' }
}}
/>
)}
</Box>
);
},
},
{
accessorKey: "createdAt",
Cell: ({ row }) => (
<div className={classes.dateStyle}>
{format(new Date(row?.original?.createdAt), "dd MMM, yyyy")}
</div>
),
header: "Added On",
},
{
accessorFn: ({ email }) => email || NOT_AVAILABLE_TEXT,
accessorKey: "status",
header: "Status",
},
{
size: 30,
accessorKey: "actions",
header: "Actions",
enableSorting: false,
Cell: ({ row }) => (
<div>
<Switch
checked={row?.original?.isActive}
onChange={() => handleToggleButton(row?.original)}
inputProps={{ "aria-label": "Status toggle" }}
color="primary"
/>
</div>
),
}
],
[]
);
const formik = useFormik({
initialValues: defaultFormData.current,
validationSchema,
onSubmit: (values) => handleSubmit(values, formik),
});
/* ----------------- Handle Cancel ----------------- */
const handleCancel = () => {
toggle();
formik.resetForm();
};
const deleteUserToggle = (row) => {
setDeleteModal(!deleteModal);
setSelectedUserData(row?.original);
};
const makeMasterAdminToggle = (row) => {
setMasterAdminModal((prev) => !prev);
setSelectedUserData(row?.original);
};
/* ----------------- Transfer Master Admin Access to User ----------------- */
const handleTransferMasterAdminAccess = async () => {
setButtonLoading(true);
try {
const response = await transferMasterAdminAccess(seletedUserData?.id);
if (response.status === 200) {
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
ref.current.resetPage(true);
makeMasterAdminToggle();
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error?.response?.data?.message);
} finally {
ref.current.reFetchData();
setButtonLoading(false);
}
};
/* ----------------- Delete User ----------------- */
const deleteUser = async () => {
setButtonLoading(true);
try {
const response = await deleteUserById(seletedUserData?.id);
if (response.status === 200) {
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
ref.current.resetPage(true);
deleteUserToggle();
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error?.response?.data?.message);
} finally {
ref.current.reFetchData();
setButtonLoading(false);
}
};
const handleuserAccessCancel = () => {
setCheckboxError(false);
setShowUserAccessModal(false);
};
const getRowStyle = (row) =>
row?.original?.isAdmin ? { backgroundColor: "#E7F4EE !important" } : {};
const handleSubmitClick = async () => {
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 (
<Box>
<PageHeader
pageTitle="Doctors/Nurses List"
addButtonTitle="Add Doctor/Nurse"
onAddButtonClick={toggle}
addButtonIcon={<AddIcon />}
addButtonDisabled={userTotalCount === 10}
// permissionName={'CREATE_USERS'}
infiniteDropdown
/>
<Box className={classes.tableMainDiv}>
<Table
columns={columns}
getData={getData}
options={{ enableRowSelection: false }}
showAction={true}
hideShowPerPage={true}
showSearchBox={true}
ref={ref}
searchText={"user"}
getRowStyle={getRowStyle}
actions={[
{
onClick: editUser,
text: 'Edit',
icon: (
<EditIcon
// className={classes.tableActionIcons}
alt="Edit"
/>
),
// permissionName: "CREATE_USERS",
},
]}
/>
</Box>
{/* --------------- ADD User Modal --------------- */}
{showModal && !showUserAccessModal && (
<CustomModal
isScrolling={true}
noPadding
showFooter
footerLeftButtonTitle="Cancel"
footerRightButtonTitle="Submit"
onFooterLeftButtonClick={() => handleCancel()}
onFooterRightButtonClick={() => handleSubmit()}
title={isEditUser ? "Edit Doctor/Nurse" : "Add New Doctor/Nurse"}
isTitleLeft={true}
onClose={() => {
formik.resetForm();
setShowModal(false);
setSelectedCheckboxes([]);
setIsAdmin(false);
}}
modalBodyFunction={() => (
<Box
sx={{
"& .MuiDialogContent-root": {
padding: "0px",
},
}}
>
<form className={classes.form}>
<TextField
className={classes.userName}
color="secondary"
placeholder="First Name"
variant="outlined"
margin="normal"
fullWidth
inputProps={{
form: {
autocomplete: "off",
},
}}
name="userName"
value={formik.values.userName}
onChange={formik.handleChange}
onBlur={(e) => {
formik.setFieldValue("userName", e.target.value.trim());
formik.handleBlur(e);
}}
inputRef={fieldRefs.userName}
error={Boolean(
formik.errors.userName && formik.touched.userName
)}
helperText={
formik.errors.userName && formik.touched.userName ? (
<span className={classes.errorMessage}>
<i>{formik.errors.userName}</i>
</span>
) : (
""
)
}
/>
<TextField
className={classes.lastName}
color="secondary"
placeholder="Last Name"
variant="outlined"
margin="normal"
fullWidth
inputProps={{
form: {
autocomplete: "off",
},
}}
name="lastName"
value={formik.values.lastName}
onChange={formik.handleChange}
onBlur={(e) => {
formik.setFieldValue("lastName", e.target.value.trim());
formik.handleBlur(e);
}}
inputRef={fieldRefs.lastName}
error={Boolean(
formik.errors.lastName && formik.touched.lastName
)}
helperText={
formik.errors.lastName && formik.touched.lastName ? (
<span className={classes.errorMessage}>
<i>{formik.errors.lastName}</i>
</span>
) : (
""
)
}
/>
<Select
select
className={classes.perPageDropdown}
fullWidth
style={{ marginTop: "15px" }}
defaultValue={true}
>
<MenuItem value={true}>Doctor</MenuItem>
<MenuItem value={false}>Nurse</MenuItem>
</Select>
<MultiSelect
apiCall={getRoles}
searchParams={{ additionalParam: "value" }}
formik={formik}
fieldValue="appointmentType"
fieldObjectValue="appointmentTypeObject"
getOptionLabel={(option) => option.name}
searchDebounceTime={500}
fieldName="appointmentType"
placeholderText="Select Appointment Types"
styleForSelectorParent={classes.multiSelectParent}
styleForSelector={classes.multiSelectChild}
inputRef={fieldRefs.appointmentType}
/>
{/* <TextField
className={classes.emailField}
color="secondary"
placeholder="Enter Email ID"
variant="outlined"
margin="normal"
fullWidth
inputProps={{
form: {
autocomplete: 'off',
},
}}
name="email"
disabled={isEditUser}
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
inputRef={fieldRefs.email}
error={Boolean(formik.errors.email && formik.touched.email)}
helperText={
formik.errors.email && formik.touched.email ? (
<span className={classes.errorMessage}>
<i>{formik.errors.email}</i>
</span>
) : (
''
)
}
/>
<TextField
className={classes.phoneNumberField}
color="secondary"
placeholder="Enter 10-Digits mobile number"
variant="outlined"
margin="normal"
fullWidth
name="mobile"
value={formik.values.mobile}
onChange={(e) => {
if (e.target.value.length <= 10) {
formik.handleChange(e);
}
}}
onBlur={formik.handleBlur}
inputRef={fieldRefs.mobile}
error={Boolean(formik.errors.mobile && formik.touched.mobile)}
helperText={
formik.errors.mobile && formik.touched.mobile ? (
<span className={classes.errorMessage}>
<i>{formik.errors.mobile}</i>
</span>
) : (
''
)
}
inputProps={{
type: 'string',
pattern: '[0-9]*',
autocomplete: 'password',
form: {
autocomplete: 'off',
},
}}
/> */}
</form>
</Box>
)}
/>
)}
{/* ----------- Choose User Access Modal----------- */}
{showModal && showUserAccessModal && formik.isValid && (
<CustomModal
showBack={true}
title="Choose User Access"
isTitleLeftWithBackAndCloseButton={true}
noPadding
onFooterLeftButtonClick={() => handleuserAccessCancel()}
onFooterRightButtonClick={() => formik.handleSubmit()}
footerLeftButtonTitle="Cancel"
footerRightButtonTitle="Add"
showFooter
onClose={() => {
formik.resetForm();
setSelectedCheckboxes([]);
setShowModal(false);
setIsAdmin(false);
}}
backFunctionCall={() => {
setShowUserAccessModal(false);
}}
modalBodyFunction={() => <></>}
footerContent={
<Box>
<Grid container>
<Grid item lg={3}>
<LoadingButton
size="small"
variant="contained"
color="secondary"
onClick={() => setCheckboxError(false)} // Clear error on cancel
>
Cancel
</LoadingButton>
</Grid>
<Grid item lg={3}>
<LoadingButton
size="small"
variant="contained"
onClick={handleSubmit}
>
Add
</LoadingButton>
</Grid>
</Grid>
</Box>
}
/>
)}
{/* ----------- Delete User Modal ----------- */}
{deleteModal && (
<ConfirmationModal
heading="Delete User"
bodyContent="Deleting this user will delete all the data added to this user and it will not be recoverable once deleted."
confirmationLine="Are you sure you want to permanently delete?"
leftButtonText="Cancel"
rightButtonText="Delete Anyway"
onCancel={deleteUserToggle}
onConfirm={deleteUser}
buttonLoading={buttonLoading}
/>
)}
{/* ----------- Make User Master Admin Modal ----------- */}
{masterAdminModal && (
<ConfirmationModal
heading="Make User Master Admin"
bodyContent="The request for access privileges to Master Admin will be given to this user."
confirmationLine="Are you sure you want to make this user Master Admin?"
leftButtonText="Cancel"
rightButtonText="Save"
onCancel={makeMasterAdminToggle}
onConfirm={handleTransferMasterAdminAccess}
buttonLoading={buttonLoading}
/>
)}
</Box>
);
}
export default Users;