501 lines
14 KiB
JavaScript
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;
|