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

501 lines
14 KiB
JavaScript

import {
Box,
Chip,
Dialog,
DialogContent,
DialogTitle,
IconButton,
MenuItem,
Select,
TextField,
Typography,
Button,
DialogActions,
} from "@mui/material";
import { useFormik } from "formik";
import React, { useMemo, useRef, useState } from "react";
import * as Yup from "yup";
import EditIcon from "@mui/icons-material/Edit";
import CloseIcon from "@mui/icons-material/Close";
import MultiSelect from "../../components/MultiSelect";
/* ----------------- Custom Imports ----------------- */
import PageHeader from "../../components/PageHeader";
import Table from "../../components/Table";
import { useStyles } from "./userStyles";
/* ----------------- Assets ----------------- */
import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/Delete";
/* ----------------- Utils ----------------- */
import { format } from "date-fns";
import { NOTIFICATION, NOT_AVAILABLE_TEXT } from "../../constants";
import { pushNotification } from "../../utils/notification";
import {
createDoctor,
deleteDoctor,
getClinicDoctors,
updateDoctor,
} from "../../services/clinics.service";
import { useSelector } from "react-redux";
/* ----------------- Validation Schema ----------------- */
const validationSchema = Yup.object().shape({
userName: Yup.string().required("User Name is required"),
userType: Yup.string().required("User Type is required"),
appointmentType: Yup.array().min(
1,
"At least one appointment type is required"
),
});
function Users() {
const ref = useRef(null);
const classes = useStyles();
const userTypes = [
{ id: "doctor", name: "Doctor" },
{ id: "nurse", name: "Nurse" },
];
/* ----------------- State Variables ----------------- */
const [deleteModal, setDeleteModal] = useState(false);
const [userTotalCount, setUserTotalCount] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
const [isEditUser, setIsEditUser] = useState(false);
const [deleteId, setDeleteId] = useState(null);
// Fix: Use proper initial values for formik
const initialValues = {
userName: "",
userType: userTypes[0].id,
appointmentType: [],
appointmentTypeObject: {},
};
/* ----------------- Get Users ----------------- */
const getData = async (filters) => {
const resp = await getClinicDoctors(filters);
setUserTotalCount(resp?.data?.data?.total);
return { data: resp?.data?.data?.data, rowCount: resp?.data?.data?.total };
};
const handleDialog = () => {
if (dialogOpen) {
// Reset form when closing dialog
formik.resetForm();
setIsEditUser(false);
}
setDialogOpen(!dialogOpen);
};
const editUser = async (row) => {
const { id, name, role, appointmentTypes } = row.original;
// Set form values for editing
formik.setValues({
id,
userName: name || '',
userType: role || userTypes[0]?.id || '',
appointmentType: Array.isArray(appointmentTypes) ? appointmentTypes : [],
appointmentTypeObject: {}
});
setIsEditUser(true);
setDialogOpen(true);
};
const deleteUserToggle = (id) => {
setDeleteId(id);
setDeleteModal(!deleteModal);
};
/* ----------------- Handle Submit ----------------- */
const handleSubmit = async (values, { resetForm, setSubmitting }) => {
try {
const payload = {
name: values.userName,
role: values.userType,
appointmentTypes: values.appointmentType.map((type) => type.id),
};
let resp;
if (isEditUser) {
resp = await updateDoctor(payload, values.id);
} else {
resp = await createDoctor(payload);
}
if (resp?.data?.data?.error) {
pushNotification(resp?.data?.data?.error, NOTIFICATION.ERROR);
return;
}
pushNotification(
`${isEditUser ? "User updated" : "User added"} successfully`,
NOTIFICATION.SUCCESS
);
await ref.current?.reFetchData();
handleDialog();
resetForm();
setSubmitting(false);
} catch (error) {
console.log(error);
pushNotification("Failed to save user", NOTIFICATION.ERROR);
}
};
const handleDeleteUser = async () => {
try {
const resp = await deleteDoctor(deleteId);
if (resp?.data?.data?.error) {
pushNotification(resp?.data?.data?.error, NOTIFICATION.ERROR);
return;
}
pushNotification("User deleted successfully", NOTIFICATION.SUCCESS);
await ref.current?.reFetchData();
setDeleteModal(false);
} catch (error) {
console.log(error);
pushNotification("Failed to delete user", NOTIFICATION.ERROR);
}
};
/* ----------------- Formik Setup ----------------- */
const formik = useFormik({
initialValues,
validationSchema,
onSubmit: handleSubmit,
});
/* ----------------- 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,
},
{
size: 100,
accessorFn: ({ name }) => name || NOT_AVAILABLE_TEXT,
accessorKey: "name",
header: "Doctor/Nurse Name",
isBold: true,
},
{
size: 100,
accessorFn: ({ role }) => (role ? role.charAt(0).toUpperCase() + role.slice(1) : NOT_AVAILABLE_TEXT),
accessorKey: "role",
header: "User Type",
},
{
size: 200,
accessorKey: "appointmentTypes",
header: "Appointment Types",
Cell: ({ row }) => {
const appointmentTypes = row?.original?.appointmentTypes;
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.charAt(0).toUpperCase() + label.slice(1)}
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>
);
},
},
{
size: 150,
accessorKey: "create_time",
Cell: ({ row }) => (
<div className={classes.dateStyle}>
{format(new Date(row?.original?.create_time), "dd MMM, yyyy")}
</div>
),
header: "Added On",
},
{
size: 100,
accessorFn: ({ status }) => (status ? status[0].toUpperCase() + status.slice(1) : NOT_AVAILABLE_TEXT),
accessorKey: "status",
header: "Status",
},
],
[classes.dateStyle]
);
const getRowStyle = (row) =>
row?.original?.isAdmin ? { backgroundColor: "#E7F4EE !important" } : {};
// Function to get option label for MultiSelect
const getAppointmentTypeLabel = (option) => {
return option?.name || option?.type || option?.label || String(option);
};
return (
<Box>
<PageHeader
pageTitle="Doctors/Nurses Management"
addButtonTitle="Add Doctor/Nurse"
onAddButtonClick={handleDialog}
addButtonIcon={<AddIcon />}
infiniteDropdown
/>
<Box className={classes.tableMainDiv}>
<Table
columns={columns}
getData={getData}
options={{ enableRowSelection: false }}
showAction={true}
hideShowPerPage={false}
showSearchBox={true}
ref={ref}
searchText={"Doctor/Nurse(s)"}
getRowStyle={getRowStyle}
actions={[
{
onClick: editUser,
text: "Edit",
icon: <EditIcon alt="Edit" />,
},
{
onClick: (row) => deleteUserToggle(row.original.id),
text: "Delete",
icon: <DeleteIcon alt="Delete" />,
},
]}
/>
</Box>
<Dialog
open={dialogOpen}
onClose={handleDialog}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
borderRadius: "12px",
overflow: "hidden",
},
}}
>
<DialogTitle
sx={{
padding: "16px 24px",
backgroundColor: (theme) => theme.palette.primary.main,
color: "white",
fontWeight: "bold",
position: "relative",
}}
>
{isEditUser ? "Edit User" : "Add New User"}
<IconButton
aria-label="close"
onClick={handleDialog}
sx={{
position: "absolute",
right: 8,
top: 8,
color: "white",
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box component="form" sx={{ mt: 1 }}>
<Typography variant="subtitle2" gutterBottom sx={{ mb: 1 }}>
Doctor/Nurse Information
</Typography>
<TextField
autoFocus
margin="dense"
id="userName"
name="userName"
label="User Name"
type="text"
fullWidth
variant="outlined"
value={formik.values.userName}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.userName && Boolean(formik.errors.userName)}
helperText={formik.touched.userName && formik.errors.userName}
sx={{ mb: 3 }}
/>
<Typography variant="subtitle2" gutterBottom sx={{ mb: 1 }}>
User Type
</Typography>
<Select
name="userType"
label="User Type"
value={formik.values.userType}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
fullWidth
error={formik.touched.userType && Boolean(formik.errors.userType)}
sx={{ mb: 3 }}
>
{userTypes.map((type) => (
<MenuItem key={type.id} value={type.id}>
{type.name}
</MenuItem>
))}
</Select>
<Typography variant="subtitle2" gutterBottom sx={{ mb: 1 }}>
Appointment Types
</Typography>
<MultiSelect
formik={formik}
fieldValue="appointmentType"
fieldObjectValue="appointmentTypeObject"
getOptionLabel={getAppointmentTypeLabel}
placeholderText="Select appointment types"
/>
</Box>
</DialogContent>
<DialogActions sx={{ padding: "16px 24px" }}>
<Button onClick={handleDialog} color="secondary">
Cancel
</Button>
<Button
onClick={formik.handleSubmit}
variant="contained"
color="primary"
disabled={formik.isSubmitting}
>
{isEditUser ? "Update" : "Add"} User
</Button>
</DialogActions>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog
open={deleteModal}
onClose={() => setDeleteModal(false)}
maxWidth="xs"
fullWidth
PaperProps={{
sx: {
borderRadius: "12px",
overflow: "hidden",
},
}}
>
<DialogTitle
sx={{
padding: "16px 24px",
backgroundColor: (theme) => theme.palette.error.main,
color: "white",
fontWeight: "bold",
position: "relative",
}}
>
Confirm Deletion
<IconButton
aria-label="close"
onClick={() =>setDeleteModal(false) }
sx={{
position: "absolute",
right: 8,
top: 8,
color: "white",
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Typography variant="subtitle2" gutterBottom sx={{ mb: 1 }}>
Are you sure you want to delete this user?
</Typography>
</DialogContent>
<DialogActions sx={{ padding: "16px 24px" }}>
<Button onClick={() => setDeleteModal(false)} color="secondary">
Cancel
</Button>
<Button
onClick={handleDeleteUser}
variant="contained"
color="primary"
>
Delete
</Button>
</DialogActions>
</Dialog>
</Box>
);
}
export default Users;