feat: pr changes

This commit is contained in:
deepvasoya 2025-05-08 16:55:35 +05:30
parent d9db46abca
commit 4717c67cca
13 changed files with 966 additions and 690 deletions

View File

@ -7,11 +7,11 @@ import {
Select,
TextField,
Tooltip,
} from '@mui/material';
import {MaterialReactTable} from 'material-react-table';
import PreviousIcon from '@mui/icons-material/ArrowBack';
import NextIcon from '@mui/icons-material/ArrowForward';
import PropTypes from 'prop-types';
} from "@mui/material";
import { MaterialReactTable } from "material-react-table";
import PreviousIcon from "@mui/icons-material/ArrowBack";
import NextIcon from "@mui/icons-material/ArrowForward";
import PropTypes from "prop-types";
import React, {
forwardRef,
memo,
@ -20,17 +20,17 @@ import React, {
useMemo,
useRef,
useState,
} from 'react';
import SearchIcon from '@mui/icons-material/Search';
import Typography from '@mui/material/Typography';
import Pagination from '@mui/material/Pagination';
import PaginationItem from '@mui/material/PaginationItem';
import { debounce } from 'lodash';
import { ExpandMore } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
} from "react";
import SearchIcon from "@mui/icons-material/Search";
import Typography from "@mui/material/Typography";
import Pagination from "@mui/material/Pagination";
import PaginationItem from "@mui/material/PaginationItem";
import { debounce } from "lodash";
import { ExpandMore } from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
import { useStyles } from './styles/tableStyles';
import ProtectedComponent from '../components/ProtectedComponent';
import { useStyles } from "./styles/tableStyles";
import ProtectedComponent from "../components/ProtectedComponent";
const Table = memo(
forwardRef((props, ref) => {
@ -54,31 +54,13 @@ const Table = memo(
navigateTo,
} = props;
const [data, setData] = useState([]);
const [formattedColumns, setFormattedColumns] = useState(
columns.map((column, i) => {
if (i === 0) {
return {
...column,
muiTableBodyCellProps: {
sx: (theme) => ({
opacity: 1,
fontWeight: 600,
fontSize: '16px',
fontFamily: theme.fontFamily.semiBold,
color: theme.palette.grey[10],
}),
},
};
}
return column;
})
);
const [formattedColumns, setFormattedColumns] = useState([]);
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isRefetching, setIsRefetching] = useState(false);
const [rowCount, setRowCount] = useState(0);
const [columnFilters, setColumnFilters] = useState([]);
const [globalFilter, setGlobalFilter] = useState('');
const [globalFilter, setGlobalFilter] = useState("");
const [sorting, setSorting] = useState([]);
const tableRef = useRef();
const [pagination, setPagination] = useState({
@ -111,26 +93,46 @@ const Table = memo(
},
}));
useEffect(() => {
// Process columns and add any action columns that have a render function
const processedColumns = [...columns];
// Add action columns with render functions
if (actions && actions.length > 0) {
actions.forEach((action) => {
if (action.render && action.field) {
processedColumns.push({
accessorKey: action.field,
header: action.title || "",
Cell: ({ row }) => action.render(row.original),
});
}
});
}
setFormattedColumns(
columns.map((column, i) => {
processedColumns.map((column, i) => {
if (column.isBold) {
return {
...column,
muiTableBodyCellProps: {
sx: (theme) => ({
opacity: 1,
fontWeight: 600,
fontSize: '16px',
fontFamily: theme.fontFamily.semiBold,
color: theme.palette.grey[10],
}),
},
Cell: ({ cell }) => (
<Typography
sx={(theme) => ({
opacity: 1,
fontWeight: 600,
fontSize: "16px",
fontFamily: theme.fontFamily.semiBold,
color: theme.palette.grey[10],
})}
>
{cell.getValue()}
</Typography>
),
};
}
return column;
})
);
}, [columns]);
}, [columns, actions]);
const fetchData = async () => {
if (!data.length) {
@ -138,15 +140,15 @@ const Table = memo(
} else {
setIsRefetching(true);
}
let filterString = '';
let filterString = "";
columnFilters.forEach((filter) => {
filterString += `${filter.id}=${filter.value}&`;
});
let sortingString = sorting
.map(
(sort) => `orderBy=${sort.id}&order=${sort.desc ? 'DESC' : 'ASC'}&`
(sort) => `orderBy=${sort.id}&order=${sort.desc ? "DESC" : "ASC"}&`
)
.join('');
.join("");
try {
const response = await getData({
pagination,
@ -222,11 +224,6 @@ const Table = memo(
InputProps={{
startAdornment: (
<InputAdornment position="end">
{/* <img
src={SearchFieldIcon}
alt="search icon"
className={classes.searchIconImg}
/> */}
<SearchIcon />
</InputAdornment>
),
@ -256,29 +253,26 @@ const Table = memo(
<CustomPagination table={props.table} />
)}
muiTableBodyCellProps={{
// className: classes?.muiTableBodyCell,
sx: (theme) => ({
opacity: '1',
// lineHeight: '2.5rem',
opacity: "1",
margin: theme.spacing(1),
fontWeight: 500,
fontSize: '16px',
fontStretch: 'normal',
fontStyle: 'normal',
letterSpacing: 'normal',
fontSize: "16px",
fontStretch: "normal",
fontStyle: "normal",
letterSpacing: "normal",
fontFamily: theme.fontFamily.medium,
color: theme.palette.grey[22],
// textAlign: 'center',
textAlign: 'left',
textAlign: "left",
}),
}}
muiTopToolbarProps={{
sx: (theme) => ({
borderRadius: theme.shape.borderRadius,
'& div': {
'& div': {
'& .MuiIconButton-sizeMedium': {
display: 'none',
"& div": {
"& div": {
"& .MuiIconButton-sizeMedium": {
display: "none",
},
},
},
@ -288,6 +282,16 @@ const Table = memo(
renderTopToolbar={false}
muiTableHeadCellProps={{
className: classes?.muiTableHeadCell,
sx: {
// Fix for extra space in header cells
padding: "16px 8px",
whiteSpace: "nowrap",
justifyContent: "flex-start",
'& .MuiTableSortLabel-root': {
width: '100%',
justifyContent: 'flex-start',
}
}
}}
enableRowNumbers={enableRowNumbers}
rowNumberMode={rowNumberMode}
@ -296,15 +300,15 @@ const Table = memo(
sx: (theme) => ({
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.common.white,
boxShadow: 'none',
'& .MuiTableContainer-root': {
border: '0px solid',
borderRadius: '16px',
boxShadow: "none",
"& .MuiTableContainer-root": {
border: "0px solid",
borderRadius: "16px",
},
}),
}}
muiTableHeadCellFilterTextFieldProps={{
variant: 'outlined',
variant: "outlined",
}}
muiTableBodyProps={{
sx: (theme) => ({
@ -328,12 +332,12 @@ const Table = memo(
muiToolbarAlertBannerProps={
isError
? {
color: 'error',
children: 'Error loading data',
color: "error",
children: "Error loading data",
}
: undefined
}
positionToolbarAlertBanner={'none'}
positionToolbarAlertBanner={"none"}
onColumnFiltersChange={setColumnFilters}
onGlobalFilterChange={setGlobalFilter}
onPaginationChange={setPagination}
@ -351,16 +355,16 @@ const Table = memo(
className: classes?.tableHeadRow,
}}
displayColumnDefOptions={{
'mrt-row-actions': {
header: '',
"mrt-row-actions": {
header: "",
},
}}
muiTableBodyRowProps={({ row }) => ({
onClick: (event) => {
if (
Object.values(event.target)?.[1]
?.className?.split(' ')
.includes('MuiBackdrop-root')
?.className?.split(" ")
.includes("MuiBackdrop-root")
) {
return;
}
@ -378,74 +382,67 @@ const Table = memo(
muiSelectAllCheckboxProps={{
className: classes?.tableCheckbox,
}}
renderRowActionMenuItems={({ row, closeMenu }) =>
actions?.map((action, index) =>
!(action?.renderAction?.(row) ?? true) ? null : (
<ProtectedComponent
key={index}
permission={action.permissionName}
>
{(action?.icon ||
action?.text ||
(action?.textFn && action?.textFn(row))) && (
<MenuItem
className={classes.menuItem}
onClick={(event) => {
event.stopPropagation();
action.onClick(row);
closeMenu();
}}
disabled={
action?.isDisabledValue
? action?.isDisabledValue ===
row?.original?.[action?.rowKey]
: false
}
>
{action?.icon} {action?.text}{' '}
{action.textFn && action.textFn(row)}
</MenuItem>
)}
</ProtectedComponent>
)
) ?? []
}
// renderRowActionMenuItems={({ row, closeMenu }) =>
// actions?.filter(action => !action.render)?.map((action, index) =>
// !(action?.renderAction?.(row) ?? true) ? null : (
// <MenuItem
// key={index}
// className={classes.menuItem}
// onClick={(event) => {
// event.stopPropagation();
// action.onClick && action.onClick(row);
// closeMenu();
// }}
// disabled={
// action?.isDisabledValue
// ? action?.isDisabledValue ===
// row?.original?.[action?.rowKey]
// : false
// }
// >
// {action?.icon} {action?.text}{" "}
// {action.textFn && action.textFn(row)}
// </MenuItem>
// )
// ) ?? []
// }
renderTopToolbarCustomActions={({ table }) => {
const handleActive = () => {
const data = table
.getSelectedRowModel()
.flatRows.map((row) => row?.original);
return handleBulkAction('ACTIVE', data);
return handleBulkAction("ACTIVE", data);
};
const handleDelete = () => {
const data = table
.getSelectedRowModel()
.flatRows.map((row) => row?.original);
return handleBulkAction('DELETE', data, table);
return handleBulkAction("DELETE", data, table);
};
const handleDownload = () => {
const data = table
.getSelectedRowModel()
.flatRows.map((row) => row?.original);
return handleBulkAction('DOWNLOAD', data);
return handleBulkAction("DOWNLOAD", data);
};
return table.getIsSomeRowsSelected() ||
table.getIsAllRowsSelected() ? (
<Box
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Tooltip title="Change Status">
<Box
component="img"
src={inactive}
// Replace with actual image path or icon component
// src={inactive}
style={{
marginLeft: '5px',
cursor: 'pointer',
marginTop: '8px',
marginLeft: "5px",
cursor: "pointer",
marginTop: "8px",
}}
onClick={handleActive}
/>
@ -453,11 +450,11 @@ const Table = memo(
<Tooltip title="Delete">
<Box
component="img"
src={deleteIcon}
// src={deleteIcon}
style={{
marginLeft: '24px',
cursor: 'pointer',
marginTop: '8px',
marginLeft: "24px",
cursor: "pointer",
marginTop: "8px",
}}
onClick={handleDelete}
/>
@ -466,12 +463,12 @@ const Table = memo(
<Tooltip title="Download Report">
<Box
component="img"
src={downloadActivity}
// src={downloadActivity}
style={{
marginLeft: '24px',
cursor: 'pointer',
marginTop: '9px',
height: '20px',
marginLeft: "24px",
cursor: "pointer",
marginTop: "9px",
height: "20px",
}}
onClick={handleDownload}
/>
@ -483,7 +480,7 @@ const Table = memo(
);
}}
muiTablePaginationProps={{
labelRowsPerPage: 'Records Per Page',
labelRowsPerPage: "Records Per Page",
}}
tableInstanceRef={tableRef}
state={{
@ -548,9 +545,9 @@ const CustomPagination = ({ table }) => {
return (
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: 'center',
display: "flex",
flexDirection: { xs: "column", md: "row" },
alignItems: "center",
padding: 2,
}}
>
@ -562,7 +559,7 @@ const CustomPagination = ({ table }) => {
boundaryCount={3}
onChange={(event, page) => setPageIndex(page - 1)}
renderItem={(item) => {
if (item.type === 'previous') {
if (item.type === "previous") {
return (
<PaginationItem
component={() => (
@ -572,13 +569,13 @@ const CustomPagination = ({ table }) => {
disabled={currentPage === 1}
onClick={() => previousPage()}
>
<PreviousIcon style={{ marginRight: '6px' }} />
<PreviousIcon style={{ marginRight: "6px" }} />
Previous
</Button>
)}
/>
);
} else if (item.type === 'next') {
} else if (item.type === "next") {
return (
<PaginationItem
component={() => (
@ -589,7 +586,7 @@ const CustomPagination = ({ table }) => {
onClick={() => nextPage()}
>
Next
<NextIcon style={{ marginLeft: '6px' }} />
<NextIcon style={{ marginLeft: "6px" }} />
</Button>
)}
disabled={currentPage === totalPage}
@ -600,19 +597,19 @@ const CustomPagination = ({ table }) => {
}
}}
sx={{
display: 'flex',
justifyContent: 'center',
flex: '1',
'& .MuiPagination-ul': {
width: '100%',
display: 'flex',
cursor: 'pointer',
display: "flex",
justifyContent: "center",
flex: "1",
"& .MuiPagination-ul": {
width: "100%",
display: "flex",
cursor: "pointer",
},
'& .MuiPagination-ul > :first-child': {
marginRight: 'auto',
"& .MuiPagination-ul > :first-child": {
marginRight: "auto",
},
' & .MuiPagination-ul > :last-child': {
marginLeft: 'auto',
" & .MuiPagination-ul > :last-child": {
marginLeft: "auto",
},
}}
/>
@ -620,4 +617,4 @@ const CustomPagination = ({ table }) => {
);
};
export default Table;
export default Table;

View File

@ -7,10 +7,10 @@ import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline';
import PeopleIcon from '@mui/icons-material/People';
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
import SettingsIcon from '@mui/icons-material/Settings';
import MedicationIcon from '@mui/icons-material/Medication';
import MedicationOutlinedIcon from '@mui/icons-material/MedicationOutlined';
import ArticleIcon from '@mui/icons-material/Article';
import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined';
import TopicIcon from '@mui/icons-material/Topic';
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
import { USER_ROLES } from '../../../redux/userRoleSlice';
@ -40,6 +40,14 @@ export const SIDEBAR_CONFIG = [
// Only super admin can access admin staff management
roles: [USER_ROLES.SUPER_ADMIN]
},
{
text: 'Master Data Management',
path: 'masterData',
icon: SettingsOutlinedIcon,
activeIcon: SettingsIcon,
// Only super admin can access admin staff management
roles: [USER_ROLES.SUPER_ADMIN]
},
{
text: 'Doctor/Nurse Management',
path: 'doctor',
@ -67,4 +75,13 @@ export const SIDEBAR_CONFIG = [
// Clinic admin can access call transcripts
roles: [USER_ROLES.CLINIC_ADMIN]
},
{
text: 'Contract Management',
path: 'contractManagement',
requireSaprateApp: false,
icon: TopicOutlinedIcon,
activeIcon: TopicIcon,
// Clinic admin can access contract management
roles: [USER_ROLES.CLINIC_ADMIN]
},
];

72
src/mock/users/index.js Normal file
View File

@ -0,0 +1,72 @@
export const userDataMock = {
data: {
records: [
{
id: 1,
name: "test",
email: "test@gmail.com",
userType: "Manager",
specialities: "Doctor",
createdAt: "2025-01-01",
status: "Active",
},
{
id: 2,
name: "John Doe",
email: "johndoe@example.com",
userType: "Employee",
specialities: "Engineer",
createdAt: "2024-11-15",
status: "Inactive",
},
{
id: 3,
name: "Jane Smith",
email: "janesmith@example.com",
userType: "Admin",
specialities: "Manager",
createdAt: "2024-12-01",
status: "Active",
},
{
id: 4,
name: "Alice Brown",
email: "alicebrown@example.com",
userType: "Manager",
specialities: "HR",
createdAt: "2023-06-22",
status: "Active",
},
{
id: 5,
name: "Bob Green",
email: "bobgreen@example.com",
userType: "Employee",
specialities: "Software Developer",
createdAt: "2024-09-10",
status: "Active",
},
{
id: 6,
name: "Charlie Black",
email: "charlieblack@example.com",
userType: "Admin",
specialities: "IT Support",
createdAt: "2023-07-05",
status: "Inactive",
},
{
id: 7,
name: "Eve White",
email: "evewhite@example.com",
userType: "Manager",
specialities: "Finance",
createdAt: "2025-03-30",
status: "Active",
},
],
totalCount: 7,
},
message: "Companies retrieved successfully",
error: "",
};

View File

@ -10,6 +10,8 @@ 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";
export const routesData = [
{
@ -23,6 +25,8 @@ export const routesData = [
{ path: "/doctor", component: Users },
{ path: "/clinicSetup", component: ClinicSetup },
{ path: "/transcripts", component: ClinicTranscripts },
{ path: "/contractManagement", component: ContractManagement },
{ path: "/masterData", component: MasterDataManagement },
],
isProtected: true,
},

View File

@ -1,24 +1,30 @@
import { axiosInstance } from '../config/api';
import { axiosInstance } from "../config/api";
import { userDataMock } from "../mock/users";
// export const getUsers = (params) => {
// let searchParams = new URLSearchParams();
// searchParams.append('size', params?.pagination?.pageSize ?? 10);
// searchParams.append('page', params?.pagination.pageIndex ?? 0);
// if (params?.globalFilter) searchParams.append('search', params?.globalFilter);
// if (params?.sortingString) {
// const sortingParams = new URLSearchParams(params.sortingString);
// sortingParams.forEach((value, key) => {
// searchParams.append(key, value);
// });
// }
// let url = `/users?${searchParams.toString()}`;
// return new Promise((resolve, reject) => {
// axiosInstance
// .get(url)
// .then((response) => resolve(response?.data))
// .catch((err) => reject(err));
// });
// };
export const getUsers = (params) => {
let searchParams = new URLSearchParams();
searchParams.append('size', params?.pagination?.pageSize ?? 10);
searchParams.append('page', params?.pagination.pageIndex ?? 0);
if (params?.globalFilter) searchParams.append('search', params?.globalFilter);
if (params?.sortingString) {
const sortingParams = new URLSearchParams(params.sortingString);
sortingParams.forEach((value, key) => {
searchParams.append(key, value);
});
}
let url = `/users?${searchParams.toString()}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response?.data))
.catch((err) => reject(err));
});
const data = userDataMock;
return data
};
export const getUserById = (id) => {
@ -37,43 +43,43 @@ export const getRoles = ({ page }) =>
data: [
{
id: 1,
name: 'Adult Immunisation',
name: "Adult Immunisation",
},
{
id: 2,
name: 'Allied health',
name: "Allied health",
},
{
id: 3,
name: 'Assist',
name: "Assist",
},
{
id: 4,
name: 'Care plan',
name: "Care plan",
},
{
id: 5,
name: 'Care plan reviews',
name: "Care plan reviews",
},
{
id: 6,
name: 'Cervical Screening',
name: "Cervical Screening",
},
{
id: 7,
name: 'Child Immunisation',
name: "Child Immunisation",
},
{
id: 8,
name: 'Health assessments',
name: "Health assessments",
},
{
id: 9,
name: 'Mental Health Care Plans (MHCP)',
name: "Mental Health Care Plans (MHCP)",
},
{
id: 10,
name: 'Travel Immunisation',
name: "Travel Immunisation",
},
],
// },
@ -88,7 +94,7 @@ export const getRoles = ({ page }) =>
// });
export const addUser = (data) => {
const url = '/users';
const url = "/users";
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
@ -149,7 +155,7 @@ export const logout = () => {
};
export const changePassword = (data) => {
const url = '/users/me/password';
const url = "/users/me/password";
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
@ -158,7 +164,7 @@ export const changePassword = (data) => {
});
};
export const updateMailersSettings = (data) => {
const url = '/users/me/mail-notification';
const url = "/users/me/mail-notification";
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
@ -168,7 +174,7 @@ export const updateMailersSettings = (data) => {
};
export const updateUser = (imageUrl, isProfileDelete = false) => {
let url = '/users/me';
let url = "/users/me";
const data = { profilePhoto: imageUrl, isProfileDelete };
return new Promise((resolve, reject) => {
@ -179,7 +185,7 @@ export const updateUser = (imageUrl, isProfileDelete = false) => {
});
};
export const companyMe = () => {
let url = '/companies/me';
let url = "/companies/me";
return new Promise((resolve, reject) => {
axiosInstance
@ -189,7 +195,7 @@ export const companyMe = () => {
});
};
export const updateCompanyDetails = (data) => {
let url = '/companies/me';
let url = "/companies/me";
return new Promise((resolve, reject) => {
axiosInstance
@ -200,7 +206,7 @@ export const updateCompanyDetails = (data) => {
};
export const bsAdminLogin = (token) => {
const url = '/auth/admin/login';
const url = "/auth/admin/login";
return new Promise((resolve, reject) => {
axiosInstance
.post(url, {
@ -212,7 +218,7 @@ export const bsAdminLogin = (token) => {
};
export const resendPasswordMailer = (data) => {
const url = '/users/remind-password-setup';
const url = "/users/remind-password-setup";
return new Promise((resolve, reject) => {
axiosInstance
.patch(url, {
@ -224,7 +230,7 @@ export const resendPasswordMailer = (data) => {
};
export const updateUserEmailAndContact = (data) => {
let url = '/users/me';
let url = "/users/me";
return new Promise((resolve, reject) => {
axiosInstance
@ -235,7 +241,7 @@ export const updateUserEmailAndContact = (data) => {
};
export const transferMasterAdminAccess = (id) => {
if (!id) throw new Error('User id is required');
if (!id) throw new Error("User id is required");
const url = `/users/${id}/transfer-master-admin-access`;
return new Promise((resolve, reject) => {

View File

@ -1,246 +0,0 @@
import { axiosInstance } from '../config/api';
export const getUsers = (params) => {
let searchParams = new URLSearchParams();
searchParams.append('size', params?.pagination?.pageSize ?? 10);
searchParams.append('page', params?.pagination.pageIndex ?? 0);
if (params?.globalFilter) searchParams.append('search', params?.globalFilter);
if (params?.sortingString) {
const sortingParams = new URLSearchParams(params.sortingString);
sortingParams.forEach((value, key) => {
searchParams.append(key, value);
});
}
let url = `/users?${searchParams.toString()}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response?.data))
.catch((err) => reject(err));
});
};
export const getUserById = (id) => {
const url = `/users/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const getRoles = ({ page }) =>
page == 0
? {
// data: {
data: [
{
id: 1,
name: 'Adult Immunisation',
},
{
id: 2,
name: 'Allied health',
},
{
id: 3,
name: 'Assist',
},
{
id: 4,
name: 'Care plan',
},
{
id: 5,
name: 'Care plan reviews',
},
{
id: 6,
name: 'Cervical Screening',
},
{
id: 7,
name: 'Child Immunisation',
},
{
id: 8,
name: 'Health assessments',
},
{
id: 9,
name: 'Mental Health Care Plans (MHCP)',
},
{
id: 10,
name: 'Travel Immunisation',
},
],
// },
}
: { data: [] };
// const url = '/roles';
// return new Promise((resolve, reject) => {
// axiosInstance
// .get(url)
// .then((response) => resolve(response))
// .catch((err) => reject(err));
// });
export const addUser = (data) => {
const url = '/users';
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateUserById = (id, data) => {
const url = `/users/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const revokeAdminTransferAccess = () => {
const url = `/users/admin-access-transfer-revoke`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const approveAdminTransferAccess = (id, token) => {
const url = `/users/admin-access-transfer-approve`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url, { id, token })
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const deleteUserById = (id) => {
const url = `/users/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.delete(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const logout = () => {
const url = '/auth/companies/logout';
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const changePassword = (data) => {
const url = '/users/me/password';
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateMailersSettings = (data) => {
const url = '/users/me/mail-notification';
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateUser = (imageUrl, isProfileDelete = false) => {
let url = '/users/me';
const data = { profilePhoto: imageUrl, isProfileDelete };
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const companyMe = () => {
let url = '/companies/me';
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateCompanyDetails = (data) => {
let url = '/companies/me';
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const bsAdminLogin = (token) => {
const url = '/auth/admin/login';
return new Promise((resolve, reject) => {
axiosInstance
.post(url, {
token,
})
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const resendPasswordMailer = (data) => {
const url = '/users/remind-password-setup';
return new Promise((resolve, reject) => {
axiosInstance
.patch(url, {
email: data,
})
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateUserEmailAndContact = (data) => {
let url = '/users/me';
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const transferMasterAdminAccess = (id) => {
if (!id) throw new Error('User id is required');
const url = `/users/${id}/transfer-master-admin-access`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};

View File

@ -465,8 +465,8 @@ const ClinicsList = () => {
extraComponent={
<Box className={classes.tabsBox}>
<Tabs value={type} onChange={handleTabsChange}>
<Tab value={CLINIC_TYPE.UNREGISTERED} label="Unregistered" />
<Tab value={CLINIC_TYPE.REGISTERED} label="Registered" />
<Tab value={CLINIC_TYPE.UNREGISTERED} label="Unregistered (2)" />
<Tab value={CLINIC_TYPE.REGISTERED} label="Registered (2)" />
{/* <Tab value={COMPANY_TYPE.SUBSCRIBED} label="Subscribers" /> */}
</Tabs>
</Box>

View File

@ -0,0 +1,9 @@
import React from 'react'
const ContractManagement = () => {
return (
<div>ContractManagement</div>
)
}
export default ContractManagement

View File

@ -0,0 +1,9 @@
import React from 'react'
const MasterDataManagement = () => {
return (
<div>MasterDataManagement</div>
)
}
export default MasterDataManagement

View File

@ -19,6 +19,7 @@ import {
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import creditcard from '../../assets/images/logo/credit_card.png';
import AccountBalanceIcon from '@mui/icons-material/AccountBalance';
import { NOTIFICATION } from '../../constants';
import { pushNotification } from '../../utils/notification';
import { useNavigate } from 'react-router-dom';
@ -27,6 +28,9 @@ import { setClinicId } from '../../views/ClinicDetails/store/logInAsClinicAdminA
const PaymentPage = () => {
const [paymentMethod, setPaymentMethod] = useState('card');
const [cardNumber, setCardNumber] = useState('');
const [accountNumber, setAccountNumber] = useState('');
const [ifscCode, setIfscCode] = useState('');
const [bankName, setBankName] = useState('');
const [cardExpiry, setCardExpiry] = useState('');
const [cardCVC, setCardCVC] = useState('');
const [name, setName] = useState('');
@ -203,6 +207,112 @@ const PaymentPage = () => {
</Box>
)}
</Paper>
<Paper
variant="outlined"
sx={{
p: 2,
mb: 2,
mt: 2,
borderColor:
paymentMethod === 'net_banking' ? 'primary.main' : 'divider',
}}
>
<FormControlLabel
value="net_banking"
control={<Radio />}
label={
<Box display="flex" alignItems="center">
<AccountBalanceIcon sx={{ mr: 1, height: 24, width: 24 }} />
<Typography>Net Banking</Typography>
</Box>
}
/>
{paymentMethod === 'card' && (
<Box mt={2}>
<TextField
label="Card Number"
fullWidth
margin="dense"
value={cardNumber}
onChange={(e) =>
setCardNumber(formatCardNumber(e.target.value))
}
placeholder="1234 5678 9012 3456"
inputProps={{ maxLength: 19 }}
/>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
label="Expiry Date"
fullWidth
margin="dense"
value={cardExpiry}
onChange={(e) =>
setCardExpiry(formatExpiry(e.target.value))
}
placeholder="MM/YY"
inputProps={{ maxLength: 5 }}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="CVC"
fullWidth
margin="dense"
value={cardCVC}
onChange={(e) => setCardCVC(e.target.value)}
placeholder="123"
inputProps={{ maxLength: 3 }}
/>
</Grid>
</Grid>
<TextField
label="Name on Card"
fullWidth
margin="dense"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="John Smith"
/>
</Box>
)}
{paymentMethod === 'net_banking' && (
<Box mt={2}>
<TextField
label="Bank Name"
fullWidth
margin="dense"
value={bankName}
onChange={(e) => setBankName(e.target.value)}
placeholder="Enter Bank Name"
required
/>
<TextField
label="Account Number"
fullWidth
margin="dense"
value={accountNumber}
onChange={(e) => setAccountNumber(e.target.value)}
placeholder="Enter Account Number"
required
/>
<TextField
label="IFSC Code"
fullWidth
margin="dense"
value={ifscCode}
onChange={(e) => setIfscCode(e.target.value)}
placeholder="Enter IFSC Code"
required
/>
</Box>
)}
</Paper>
</RadioGroup>
<Button
@ -213,7 +323,9 @@ const PaymentPage = () => {
disabled={
loading ||
(paymentMethod === 'card' &&
(!cardNumber || !cardExpiry || !cardCVC || !name))
(!cardNumber || !cardExpiry || !cardCVC || !name)) ||
(paymentMethod === 'net_banking' &&
(!accountNumber || !ifscCode || !bankName))
}
sx={{ mt: 3 }}
>
@ -267,6 +379,51 @@ const PaymentPage = () => {
</Paper>
</Box>
{/* Payment Information Section */}
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
Payment Information
</Typography>
<Box mb={2}>
<Paper
elevation={0}
sx={{ p: 2, backgroundColor: '#f9f9f9', borderRadius: 2 }}
>
<Typography variant="subtitle2" fontWeight="bold" gutterBottom>
Subscription Details
</Typography>
<Stack direction="row" justifyContent="space-between" mb={1}>
<Typography variant="body2">Setup Fee</Typography>
<Typography variant="body2">$500.00</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between" mb={1}>
<Typography variant="body2">Subscription Fee (Monthly)</Typography>
<Typography variant="body2">$800.00</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between" mb={1}>
<Typography variant="body2">Per Call Charges</Typography>
<Typography variant="body2">$5.00/call</Typography>
</Stack>
<Box mt={2}>
<Chip
label="Special Offer: First 3 months free"
size="small"
color="secondary"
sx={{ mt: 1, mr: 1 }}
/>
<Chip
label="Setup fee waived"
size="small"
color="success"
sx={{ mt: 1 }}
/>
</Box>
</Paper>
</Box>
<Divider sx={{ my: 2 }} />
<Stack direction="row" justifyContent="space-between" mb={1}>
@ -307,9 +464,17 @@ const PaymentPage = () => {
elevation={0}
sx={{ p: 2, mt: 2, border: '1px solid #e0e0e0', borderRadius: 2 }}
>
<Typography variant="subtitle2" fontWeight="bold" gutterBottom>
Transaction Information
</Typography>
<Typography variant="body2" color="text.secondary" paragraph>
<strong>Secure Payment:</strong> All transactions are encrypted
and secure.
<strong>Direct Debit:</strong> Payments will be automatically processed on the 1st of each month.
</Typography>
<Typography variant="body2" color="text.secondary" paragraph>
<strong>Billing Cycle:</strong> Monthly/Annual based on your selected plan.
</Typography>
<Typography variant="body2" color="text.secondary" paragraph>
<strong>Secure Payment:</strong> All transactions are encrypted and secure via Stripe Payment Gateway.
</Typography>
<Typography variant="body2" color="text.secondary">
<strong>Customer Support:</strong> If you have any questions,

View File

@ -7,6 +7,7 @@ import {
Button,
ButtonBase,
Drawer,
FormControl,
FormHelperText,
Grid,
IconButton,
@ -103,6 +104,11 @@ function YourDetailsForm() {
{ id: "director", name: "Director" },
];
const mobilePrefixOptions = [
{ id: "61", name: "+61" },
{ id: "91", name: "+91" },
];
const integrationOptions = ["BP Software", "Medical Director"];
// Default form data
@ -112,6 +118,7 @@ function YourDetailsForm() {
email: "",
password: "",
mobileNumber: "",
mobilePrefix: "",
confirmPassword: "",
currentEmail: "",
otp: "",
@ -119,7 +126,11 @@ function YourDetailsForm() {
// Clinic details
companyName: "",
designation: "",
companyWebsite: "",
businessPhone: "",
emergencyBusinessPhone: "",
emergencyBusinessPhonePrefix: "",
businessPhonePrefix: "",
businessFax: "",
companyLogo: "",
companyIndustry: "",
pincode: "",
@ -130,6 +141,9 @@ function YourDetailsForm() {
companyPANImage: "",
companyPANImage: "",
termsAccepted: "",
practiceManagementSystem: "",
practiceId: "",
practiceName: "",
});
// Field references for focus and validation
@ -138,6 +152,7 @@ function YourDetailsForm() {
name: useRef(null),
email: useRef(null),
mobileNumber: useRef(null),
mobilePrefix: useRef(null),
password: useRef(null),
confirmPassword: useRef(null),
otp: useRef(null),
@ -145,10 +160,14 @@ function YourDetailsForm() {
// Clinic details fields
companyName: useRef(null),
businessPhone: useRef(null),
businessPhonePrefix: useRef(null),
emergencyBusinessPhone: useRef(null),
emergencyBusinessPhonePrefix: useRef(null),
businessFax: useRef(null),
practiceManagementSystem: useRef(null),
practiceId: useRef(null),
practiceName: useRef(null),
designation: useRef(null),
companyWebsite: useRef(null),
companyIndustry: useRef(null),
pincode: useRef(null),
state: useRef(null),
@ -214,7 +233,7 @@ function YourDetailsForm() {
designation: Yup.string()
.required("Designation is required")
.typeError("Designation must be string"),
companyWebsite: Yup.string()
businessPhone: Yup.string()
.required("Clinic website is required")
.matches(WEBSITE_REGEX, "Please enter a valid URL"),
companyIndustry: Yup.string().required("Clinic Industry is required"),
@ -246,6 +265,8 @@ function YourDetailsForm() {
termsAccepted: Yup.boolean()
.oneOf([true], "You must accept the terms and conditions")
.required("You must accept the terms and conditions"),
practiceId: Yup.string().required("Practice ID is required"),
practiceName: Yup.string().required("Practice Name is required"),
});
// Debounced function for pincode lookup
@ -287,6 +308,12 @@ function YourDetailsForm() {
}
};
const handleTestConnection = () => {
// logic here
setTestConnDone(true);
pushNotification("Test connection successful", "success");
};
const handleOTPButton = () => {
console.log("btn");
setOtpField(true);
@ -483,7 +510,12 @@ function YourDetailsForm() {
function formatedData(inputData) {
const data = {
name: inputData.companyName || "",
website: inputData.companyWebsite || "",
businessPhone: inputData.businessPhone || "",
emergencyBusinessPhone: inputData.emergencyBusinessPhone || "",
emergencyBusinessPhonePrefix:
inputData.emergencyBusinessPhonePrefix || "",
businessPhonePrefix: inputData.businessPhonePrefix || "",
businessFax: inputData.businessFax || "",
city: inputData.locality || "",
state: inputData.state || "",
logo: inputData.companyLogo || null,
@ -499,6 +531,9 @@ function YourDetailsForm() {
password: inputData.password || "",
designation: inputData.designation || "",
},
practiceId: inputData.practiceId || "",
practiceManagementSystem: inputData.practiceManagementSystem || "",
practiceName: inputData.practiceName || "",
companyUsers: inputData.billingUsers || "",
companyDocuments: [
...(inputData.companyLogo
@ -601,6 +636,17 @@ function YourDetailsForm() {
<Box className={classes.paperContainerBox}>
<Box>
<form>
{/* Practice ID */}
<FormSectionHeading title="practice id" />
<Grid
container
spacing={theme.spacing(2.3)}
padding={`0 ${theme.spacing(3.2)}`}
className={classes.formRoot}
>
<TextField disabled />
</Grid>
{/* Personal Details Section */}
<FormSectionHeading title="PERSONAL DETAILS" />
<Grid
@ -718,12 +764,28 @@ function YourDetailsForm() {
pattern: "[0-9]*",
startAdornment: (
<InputAdornment position="start">
<Box className={classes.countryCodeBox}>
<Typography
className={classes.countryCodeLabel}
<Box sx={{ padding: "1px" }}>
<Select
name="mobilePrefix"
value={formik.values.mobilePrefix}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
inputRef={fieldRefs.mobilePrefix}
defaultValue={"61"}
>
+61
</Typography>
{mobilePrefixOptions.map((option) => (
<MenuItem
sx={{
outline: "none",
border: "none",
}}
key={option.id}
value={option.id}
>
{option.name}
</MenuItem>
))}
</Select>
</Box>
</InputAdornment>
),
@ -962,7 +1024,7 @@ function YourDetailsForm() {
{/* Your designation grid */}
<Grid item md={4} sm={6} xs={12}>
<InputLabel className={classes.inputLabel}>
Your designation*
User Role*
</InputLabel>
<Select
fullWidth
@ -983,46 +1045,330 @@ function YourDetailsForm() {
}
>
{userRoles.map((option) => (
<MenuItem
key={option.id}
value={option.id}
>
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</Grid>
{/* Clinic Website grid */}
{/* Business Phone grid */}
<Grid item md={4} sm={6} xs={12}>
<InputLabel className={classes.inputLabel}>
Clinic Website*
Business PhoneNumber*
</InputLabel>
<TextField
className={classes.mobileNumberInput}
fullWidth
placeholder="Enter Clinic Website"
placeholder="Enter Mobile Number"
color="secondary"
variant="outlined"
name="companyWebsite"
value={formik.values.companyWebsite}
onChange={formik.handleChange}
name="businessPhone"
value={formik.values.businessPhone}
onChange={(e) => {
if (e.target.value.length <= 10) {
const value =
e.target.value?.match(/\d+/g) || "";
formik.setFieldValue(
"businessPhone",
value?.toString()
);
}
}}
onBlur={formik.handleBlur}
inputRef={fieldRefs.companyWebsite}
InputProps={{
type: "text",
pattern: "[0-9]*",
startAdornment: (
<InputAdornment position="start">
<Box sx={{ padding: "1px" }}>
<Select
name="businessPhonePrefix"
value={
formik.values.businessPhonePrefix
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
inputRef={fieldRefs.businessPhonePrefix}
defaultValue={"61"}
>
{mobilePrefixOptions.map((option) => (
<MenuItem
sx={{
outline: "none",
border: "none",
}}
key={option.id}
value={option.id}
>
{option.name}
</MenuItem>
))}
</Select>
</Box>
</InputAdornment>
),
}}
inputRef={fieldRefs.businessPhone}
error={Boolean(
formik.errors.companyWebsite &&
formik.touched.companyWebsite
formik.errors.businessPhone &&
formik.touched.businessPhone
)}
helperText={
formik.errors.companyWebsite &&
formik.touched.companyWebsite
? formik.errors.companyWebsite
formik.errors.businessPhone &&
formik.touched.businessPhone
? formik.errors.businessPhone
: ""
}
/>
</Grid>
{/* business emergency phone */}
<Grid item md={4} sm={6} xs={12}>
<InputLabel className={classes.inputLabel}>
Business Emergency Phone Number*
</InputLabel>
<TextField
className={classes.mobileNumberInput}
fullWidth
placeholder="Enter Emergency Phone Number"
color="secondary"
variant="outlined"
name="emergencyBusinessPhone"
value={formik.values.emergencyBusinessPhone}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
InputProps={{
type: "text",
pattern: "[0-9]*",
startAdornment: (
<InputAdornment position="start">
<Box sx={{ padding: "1px" }}>
<Select
name="emergencyBusinessPhonePrefix"
value={
formik.values
.emergencyBusinessPhonePrefix
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
inputRef={
fieldRefs.emergencyBusinessPhonePrefix
}
defaultValue={"61"}
>
{mobilePrefixOptions.map((option) => (
<MenuItem
sx={{
outline: "none",
border: "none",
}}
key={option.id}
value={option.id}
>
{option.name}
</MenuItem>
))}
</Select>
</Box>
</InputAdornment>
),
}}
inputRef={fieldRefs.emergencyBusinessPhone}
error={Boolean(
formik.errors.emergencyBusinessPhone &&
formik.touched.emergencyBusinessPhone
)}
helperText={
formik.errors.emergencyBusinessPhone &&
formik.touched.emergencyBusinessPhone
? formik.errors.emergencyBusinessPhone
: ""
}
/>
</Grid>
{/* Business Fax grid */}
<Grid item md={4} sm={6} xs={12}>
<InputLabel className={classes.inputLabel}>
Business Fax Number*
</InputLabel>
<TextField
// className={classes.mobileNumberInput}
fullWidth
placeholder="Enter Fax Number"
color="secondary"
variant="outlined"
name="businessFax"
value={formik.values.businessFax}
onChange={(e) => {
if (e.target.value.length <= 10) {
const value =
e.target.value?.match(/\d+/g) || "";
formik.setFieldValue(
"businessFax",
value?.toString()
);
}
}}
onBlur={formik.handleBlur}
InputProps={{
type: "text",
pattern: "[0-9]*",
}}
inputRef={fieldRefs.businessFax}
error={Boolean(
formik.errors.businessFax &&
formik.touched.businessFax
)}
helperText={
formik.errors.businessFax &&
formik.touched.businessFax
? formik.errors.businessFax
: ""
}
/>
</Grid>
{/* Business Email grid */}
<Grid item md={4} sm={6} xs={12}>
<InputLabel className={classes.inputLabel}>
Business Email*
</InputLabel>
<TextField
// className={classes.mobileNumberInput}
fullWidth
placeholder="Enter Business Email"
color="secondary"
variant="outlined"
name="businessEmail"
value={formik.values.businessEmail}
onChange={(e) => {
if (e.target.value.length <= 10) {
const value =
e.target.value?.match(/\d+/g) || "";
formik.setFieldValue(
"businessEmail",
value?.toString()
);
}
}}
onBlur={formik.handleBlur}
InputProps={{
type: "email",
}}
inputRef={fieldRefs.businessEmail}
error={Boolean(
formik.errors.businessEmail &&
formik.touched.businessEmail
)}
helperText={
formik.errors.businessEmail &&
formik.touched.businessEmail
? formik.errors.businessEmail
: ""
}
/>
</Grid>
{/* practice management system */}
<Grid item xs={6} md={4}>
<InputLabel
className={classes.inputLabel}
id="integration-software-label"
>
Integrate with
</InputLabel>
<Select
fullWidth
name="practiceManagementSystem"
value={formik.values.practiceManagementSystem}
onChange={formik.handleChange}
inputRef={fieldRefs.practiceManagementSystem}
error={Boolean(
formik.errors.practiceManagementSystem &&
formik.touched.practiceManagementSystem
)}
helperText={
formik.errors.practiceManagementSystem &&
formik.touched.practiceManagementSystem
? formik.errors.practiceManagementSystem
: ""
}
>
{integrationOptions.map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
</Grid>
{/* PMS ID grid */}
<Grid item xs={12} md={4}>
<InputLabel className={classes.inputLabel}>
PMS ID
</InputLabel>
<TextField
fullWidth
label="PMS ID"
name="practiceId"
value={formik.values.practiceId}
onChange={formik.handleChange}
variant="outlined"
disabled={!formik.values.practiceManagementSystem}
error={
!!formik.values.practiceManagementSystem &&
formik.errors.practiceId
}
helperText={
!!formik.values.practiceManagementSystem &&
formik.errors.practiceId
? "Practice ID is required"
: ""
}
/>
</Grid>
{/* Practice Name grid */}
<Grid item xs={12} md={4}>
<InputLabel className={classes.inputLabel}>
Practice Name
</InputLabel>
<TextField
fullWidth
label="Practice Name"
name="practiceName"
value={formik.values.practiceName}
onChange={formik.handleChange}
variant="outlined"
disabled={!formik.values.practiceManagementSystem}
error={
!!formik.values.practiceManagementSystem &&
formik.errors.practiceName
}
helperText={
!!formik.values.practiceManagementSystem &&
formik.errors.practiceName
? "Practice Name is required"
: ""
}
/>
</Grid>
{/* Test Connection Button */}
<Grid item xs={12} md={8}>
<Button
variant="contained"
color="primary"
onClick={handleTestConnection}
>
Test Connection
</Button>
</Grid>
{/* Clinic logo grid */}
<Grid item md={4} sm={12}>
<Grid item md={5} sm={12}>
<InputLabel className={classes.inputLabel}>
Add Business Logo
{(logoImage || formik.values.companyLogo) && (

View File

@ -14,7 +14,7 @@ const initialState = {
currentEmail: '',
companyName: '',
designation: '',
companyWebsite: '',
businessPhone: '',
companyAbout: '',
companyLogo: '',
companyIndustry: '',

View File

@ -1,60 +1,61 @@
import { LoadingButton } from '@mui/lab';
import { Box, Grid, MenuItem, Select, 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 { LoadingButton } from "@mui/lab";
import { Box, Button, 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";
/* ----------------- 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';
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 SendIcon from '@mui/icons-material/Send';
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import RemoveIcon from '@mui/icons-material/Remove';
import SendIcon from "@mui/icons-material/Send";
import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import RemoveIcon from "@mui/icons-material/Remove";
import {
getUsers,
deleteUserById,
getRoles,
getUserById,
resendPasswordMailer,
revokeAdminTransferAccess,
transferMasterAdminAccess,
} from '../../services/users.services';
} 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';
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'),
userName: Yup.string().required("User Name is required"),
email: Yup.string()
.email('Invalid Email Address')
.required('Email is required'),
.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'),
.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',
userName: "",
email: "q@gmail.com",
mobile: "1234567890",
isEditUser: false,
lastName: '',
userType: '',
lastName: "",
userType: "",
appointmentType: [],
appointmentTypeObject: '',
appointmentTypeObject: "",
});
const fieldRefs = {
@ -74,9 +75,6 @@ function Users() {
const [isEditUser, setIsEditUser] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
const [masterAdminModal, setMasterAdminModal] = useState(false);
const [revokeAdminAccessRequestModal, setRevokeAdminAccessRequestModal] =
useState(false);
const [seletedUserData, setSelectedUserData] = useState();
const [buttonLoading, setButtonLoading] = useState(false);
const [userId, setUserId] = useState();
const [userTotalCount, setUserTotalCount] = useState(0);
@ -84,28 +82,41 @@ function Users() {
const [checkboxError, setCheckboxError] = useState(false);
const [roles, setRoles] = useState();
const [isAdmin, setIsAdmin] = useState();
const [hasProfileCompleted, setHasProfileCompleted] = useState(false);
const [
requestRaisedForTransferMasterAdminAccess,
setRequestRaisedForTransferMasterAdminAccess,
] = useState(false);
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);
setRequestRaisedForTransferMasterAdminAccess(
resp.data?.requestRaisedForTransferMasterAdminAccess
);
const role = await getRoles();
setRoles(role?.data?.data);
const role = await getRoles({ page: 0 });
setRoles(role?.data);
return { data: resp?.data?.records, rowCount: resp?.data?.totalCount };
};
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 {
@ -143,7 +154,7 @@ function Users() {
// if (response.status === 200) {
// pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
pushNotification('Doctor added successfully', NOTIFICATION.SUCCESS);
pushNotification("Doctor added successfully", NOTIFICATION.SUCCESS);
// ref.current.reFetchData();
toggle();
formik.resetForm();
@ -162,60 +173,6 @@ function Users() {
setShowModal(!showModal);
};
/* ----------------- Edit User ----------------- */
const editUser = async (row) => {
try {
const { data: { data: userData } = {} } = await getUserById(
row?.original?.id
);
const formData = {
userName: userData?.name,
email: userData?.email,
mobile: userData?.mobile,
password: userData?.password,
transferMasterAdminAccess: false,
isEditUser: true,
};
setIsAdmin(userData?.isAdmin);
setHasProfileCompleted(userData?.hasProfileCompleted);
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 resendPasswordSetupMailer = (row) => {
try {
const response = resendPasswordMailer(row?.original?.email);
if (response.status === 200) {
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
/* ----------------- Revoke Admin Access Request ----------------- */
const revokeAdminAccessRequest = async () => {
setRevokeAdminAccessRequestModal(true);
};
/* ----------------- Handle Checkbox Change ----------------- */
const handleCheckboxChange = (id, isChecked) => {
setSelectedCheckboxes({ ...selectedCheckboxes, [id]: isChecked });
@ -226,7 +183,7 @@ function Users() {
() => [
{
size: 30,
header: 'S.no.',
header: "S.no.",
Cell: (props) => {
const tableState = props?.table?.getState();
const serialNumber = (
@ -235,59 +192,57 @@ function Users() {
tableState?.pagination?.pageIndex * tableState?.pagination?.pageSize
)
?.toString()
?.padStart(1, '0');
?.padStart(1, "0");
return <span>{serialNumber}.</span>;
},
enableSorting: false,
},
{
accessorFn: ({ name }) => name || NOT_AVAILABLE_TEXT,
accessorKey: 'name',
header: 'Doctor/Nurse Name',
accessorKey: "name",
header: "Doctor/Nurse Name",
isBold: true,
},
// {
// accessorKey: 'roles',
// header: 'User Access',
// enableSorting: false,
// Cell: ({ row }) => (
// <div>
// {row.original.roles && row.original.roles.length > 0
// ? row.original.roles.map((role, index) => (
// <span key={index}>
// {role.name}
// {index !== row.original.roles.length - 1 ? ', ' : ''}
// </span>
// ))
// : NOT_AVAILABLE_TEXT}
// {row.original.isAdmin && ', Master Admin'}
// </div>
// ),
// },
{
accessorFn: ({ mobile }) => mobile || NOT_AVAILABLE_TEXT,
accessorKey: 'mobile',
header: 'User Type',
accessorKey: "userType",
header: "User Type",
},
{
accessorFn: ({ email }) => email || NOT_AVAILABLE_TEXT,
accessorKey: 'email',
header: 'Specialties',
accessorKey: "specialities",
header: "Specialties",
},
{
accessorKey: 'createdAt',
accessorKey: "createdAt",
Cell: ({ row }) => (
<div className={classes.dateStyle}>
{format(new Date(row?.original?.createdAt), 'dd MMM, yyyy')}
{format(new Date(row?.original?.createdAt), "dd MMM, yyyy")}
</div>
),
header: 'Added On',
header: "Added On",
},
{
accessorFn: ({ email }) => email || NOT_AVAILABLE_TEXT,
accessorKey: 'email',
header: 'Status',
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>
),
}
],
[]
);
@ -314,10 +269,6 @@ function Users() {
setSelectedUserData(row?.original);
};
const revokeAdminAccessToggle = () => {
setRevokeAdminAccessRequestModal(!revokeAdminAccessRequestModal);
};
/* ----------------- Transfer Master Admin Access to User ----------------- */
const handleTransferMasterAdminAccess = async () => {
setButtonLoading(true);
@ -355,24 +306,6 @@ function Users() {
setButtonLoading(false);
}
};
/* ----------------- Revoke Admin Access ----------------- */
const handleRevokeAdminAccessRequest = async () => {
setButtonLoading(true);
try {
const response = await revokeAdminTransferAccess();
if (response.status === 200) {
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
ref.current.resetPage(true);
setRevokeAdminAccessRequestModal(false);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error?.response?.data?.message);
} finally {
ref.current.reFetchData();
setButtonLoading(false);
}
};
const handleuserAccessCancel = () => {
setCheckboxError(false);
@ -380,7 +313,7 @@ function Users() {
};
const getRowStyle = (row) =>
row?.original?.isAdmin ? { backgroundColor: '#E7F4EE !important' } : {};
row?.original?.isAdmin ? { backgroundColor: "#E7F4EE !important" } : {};
const handleSubmitClick = async () => {
const formikErrors = await formik.validateForm();
@ -393,10 +326,10 @@ function Users() {
if (firstErrorRef) {
// Scroll to the first invalid field smoothly
if (typeof firstErrorRef?.scrollIntoView === 'function') {
if (typeof firstErrorRef?.scrollIntoView === "function") {
firstErrorRef?.scrollIntoView({
behavior: 'smooth',
block: 'center',
behavior: "smooth",
block: "center",
});
}
@ -429,49 +362,26 @@ function Users() {
columns={columns}
getData={getData}
options={{ enableRowSelection: false }}
showAction={true}
// showAction={true}
hideShowPerPage={true}
showSearchBox={true}
ref={ref}
searchText={'user'}
searchText={"user"}
getRowStyle={getRowStyle}
actions={[
{
onClick: makeMasterAdminToggle,
text: 'Make User Master Admin',
icon: <RemoveIcon className={classes.tableActionIcons} />,
renderAction: (row) => isBsAdmin && !row?.original?.isAdmin,
},
{
onClick: revokeAdminAccessRequest,
text: 'Revoke Admin Access Request',
icon: <RemoveIcon className={classes.tableActionIcons} />,
// permissionName: "CREATE_USERS",
renderAction: (row) => row?.original?.transferMasterAdminAccess,
},
{
onClick: editUser,
text: 'Edit',
icon: <EditIcon className={classes.tableActionIcons} />,
// permissionName: "CREATE_USERS",
},
{
onClick: (row) => resendPasswordSetupMailer(row),
text: 'Resend password setup mailer',
icon: <SendIcon className={classes.tableActionIcons} />,
renderAction: (row) => !row?.original?.hasProfileCompleted,
// permissionName: "CREATE_USERS",
},
{
onClick: deleteUserToggle,
text: 'Delete',
icon: <DeleteIcon className={classes.tableActionIcons} />,
renderAction: (row) => !row?.original?.isAdmin,
// permissionName: 'DELETE_USERS',
// isDisabledValue: user?.id,
rowKey: 'id',
},
].filter(Boolean)}
// actions={[
// {
// title: "Action",
// field: "isActive",
// render: (rowData) => (
// <Switch
// checked={rowData.isActive}
// onChange={() => handleToggleButton(rowData)}
// inputProps={{ "aria-label": "Status toggle" }}
// color="primary"
// />
// ),
// },
// ]}
/>
</Box>
@ -485,7 +395,7 @@ function Users() {
footerRightButtonTitle="Submit"
onFooterLeftButtonClick={() => handleCancel()}
onFooterRightButtonClick={() => handleSubmit()}
title={isEditUser ? 'Edit Doctor/Nurse' : 'Add New Doctor/Nurse'}
title={isEditUser ? "Edit Doctor/Nurse" : "Add New Doctor/Nurse"}
isTitleLeft={true}
onClose={() => {
formik.resetForm();
@ -496,8 +406,8 @@ function Users() {
modalBodyFunction={() => (
<Box
sx={{
'& .MuiDialogContent-root': {
padding: '0px',
"& .MuiDialogContent-root": {
padding: "0px",
},
}}
>
@ -511,14 +421,14 @@ function Users() {
fullWidth
inputProps={{
form: {
autocomplete: 'off',
autocomplete: "off",
},
}}
name="userName"
value={formik.values.userName}
onChange={formik.handleChange}
onBlur={(e) => {
formik.setFieldValue('userName', e.target.value.trim());
formik.setFieldValue("userName", e.target.value.trim());
formik.handleBlur(e);
}}
inputRef={fieldRefs.userName}
@ -531,7 +441,7 @@ function Users() {
<i>{formik.errors.userName}</i>
</span>
) : (
''
""
)
}
/>
@ -544,14 +454,14 @@ function Users() {
fullWidth
inputProps={{
form: {
autocomplete: 'off',
autocomplete: "off",
},
}}
name="lastName"
value={formik.values.lastName}
onChange={formik.handleChange}
onBlur={(e) => {
formik.setFieldValue('lastName', e.target.value.trim());
formik.setFieldValue("lastName", e.target.value.trim());
formik.handleBlur(e);
}}
inputRef={fieldRefs.lastName}
@ -564,7 +474,7 @@ function Users() {
<i>{formik.errors.lastName}</i>
</span>
) : (
''
""
)
}
/>
@ -572,7 +482,7 @@ function Users() {
select
className={classes.perPageDropdown}
fullWidth
style={{ marginTop: '15px' }}
style={{ marginTop: "15px" }}
defaultValue={true}
>
<MenuItem value={true}>Doctor</MenuItem>
@ -581,7 +491,7 @@ function Users() {
<MultiSelect
apiCall={getRoles}
searchParams={{ additionalParam: 'value' }}
searchParams={{ additionalParam: "value" }}
formik={formik}
fieldValue="appointmentType"
fieldObjectValue="appointmentTypeObject"
@ -741,19 +651,6 @@ function Users() {
/>
)}
{/* ----------- Revoke Admin Access Request ----------- */}
{revokeAdminAccessRequestModal && (
<ConfirmationModal
heading="Revoke Admin Access Request"
bodyContent="The request for access privileges to Master Admin will be revoked for this user."
confirmationLine="Are you sure you want to revoke access?"
leftButtonText="Cancel"
rightButtonText="Revoke Access"
onCancel={revokeAdminAccessToggle}
onConfirm={handleRevokeAdminAccessRequest}
buttonLoading={buttonLoading}
/>
)}
</Box>
);
}