feat: clinic approval flow

This commit is contained in:
deepvasoya 2025-05-20 10:43:33 +05:30
parent 970f8ebe1c
commit 8d2d652630
50 changed files with 1929 additions and 1070 deletions

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env

9
package-lock.json generated
View File

@ -21,6 +21,7 @@
"firebase": "^11.6.0",
"formik": "^2.4.6",
"i": "^0.3.7",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"material-react-table": "^3.2.1",
"npm": "^11.3.0",
@ -4184,6 +4185,14 @@
"jss": "10.10.0"
}
},
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"engines": {
"node": ">=18"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View File

@ -23,6 +23,7 @@
"firebase": "^11.6.0",
"formik": "^2.4.6",
"i": "^0.3.7",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"material-react-table": "^3.2.1",
"npm": "^11.3.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#000000" d="M164.9 24.6c-7.7-18.6-28-28.5-47.4-23.2l-88 24C12.1 30.2 0 46 0 64C0 311.4 200.6 512 448 512c18 0 33.8-12.1 38.6-29.5l24-88c5.3-19.4-4.6-39.7-23.2-47.4l-96-40c-16.3-6.8-35.2-2.1-46.3 11.6L304.7 368C234.3 334.7 177.3 277.7 144 207.3L193.3 167c13.7-11.2 18.4-30 11.6-46.3l-40-96z"/></svg>

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,5 +1,5 @@
export const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ?? '/api/v1/recruitments';
import.meta.env.VITE_API_BASE_URL;
export const IMAGE_LOCATION_BASE_URL = import.meta.env
.VITE_IMAGE_LOCATION_BASE_URL;
export const BILLDESK_URL = import.meta.env.VITE_BILLDESK_URL;

View File

@ -29,7 +29,7 @@ import { FILE_TYPE, NOTIFICATION } from '../constants';
import uploadIcon from '../assets/images/icon/upload.svg';
import PdfIcon from '../assets/images/icon/pdf.png';
import DocIcon from '../assets/images/icon/doc.png';
import { fileUpload } from '../services/file.upload.services';
import { fileUpload, getPresignedUrl, uploadToS3 } from '../services/file.upload.services';
import { IMAGE_LOCATION_BASE_URL } from '../common/envVariables';
const CustomFileUpload = forwardRef(function CustomFileUpload(
@ -77,14 +77,9 @@ const CustomFileUpload = forwardRef(function CustomFileUpload(
useEffect(() => {
const makeFullUrlIfNeeded = (url) => {
// Return early if url is undefined or empty
if (!url) {
setOldUploadedFileUrl('');
setFileExtension('');
setImageName('');
return;
if(!url){
return
}
const isHttp = url.startsWith('http://') || url.startsWith('https://');
if (!isHttp) {
setOldUploadedFileUrl(`${IMAGE_LOCATION_BASE_URL}${url}`);
@ -114,16 +109,50 @@ const CustomFileUpload = forwardRef(function CustomFileUpload(
if (!value) {
return;
}
let formData = new FormData();
formData.append('file', value);
formData.append('fileName', value?.name);
const filePayload = {
folder: "assests",
file_name: value.name,
};
try {
setIsLoading(true);
const data = await fileUpload(formData);
onUploadDone(documentName, data?.data?.data?.Key);
const response = await getPresignedUrl(filePayload);
// Debug the response structure
console.log('API Response:', response);
// Check if we have a valid response with the expected structure
if (response?.data?.data?.Key) {
// Use the Key from the response
onUploadDone(documentName, response.data.data.Key);
await uploadToS3(value, response.data.data.api_url);
} else {
// If the expected structure is not found, try to find the key in a different location
// or use a fallback value
console.log('Response structure is different than expected');
// Try different possible paths to find the key
const key = response?.data?.Key ||
response?.data?.data?.key ||
response?.Key ||
value.name; // Fallback to the file name if key not found
console.log('Using key:', key);
onUploadDone(documentName, key);
// Try to find the API URL similarly
const apiUrl = response?.data?.data?.api_url ||
response?.data?.api_url ||
response?.api_url;
if (apiUrl) {
await uploadToS3(value, apiUrl);
} else {
console.error('Could not find API URL in response');
}
}
return;
} catch (error) {
// console.error(error);
console.error('Error in handleFileUpload:', error);
pushNotification('Error while uploading file', NOTIFICATION.ERROR);
} finally {
setIsLoading(false);

View File

@ -0,0 +1,114 @@
import { Box, Button, Grid } from '@mui/material';
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';
import React from 'react';
import { useStyles } from './styles/ImagePreviewComponentStyles';
import AddOutlinedIcon from '@mui/icons-material/AddOutlined';
import RemoveOutlinedIcon from '@mui/icons-material/RemoveOutlined';
import ZoomOutOutlinedIcon from '@mui/icons-material/ZoomOutOutlined';
import { FILE_EXTENTIONS_ICONS } from '../constants';
const ImagePreviewControls = ({ zoomIn, zoomOut, resetTransform }) => {
const classes = useStyles();
return (
<Grid className={classes.controlButtons}>
<Button
className={`${classes.zoomOutButton} ${classes.controlButton}`}
onClick={() => zoomOut()} // No need to wrap this in a function
>
<RemoveOutlinedIcon className={classes.iconSize} />
</Button>
<Button
className={`${classes.zoomResetButton} ${classes.controlButton}`}
onClick={() => resetTransform()}
>
<ZoomOutOutlinedIcon className={classes.iconSize} />
</Button>
<Button
className={`${classes.zoomInButton} ${classes.controlButton}`}
onClick={() => zoomIn()}
>
<AddOutlinedIcon className={classes.iconSize} />
</Button>
</Grid>
);
};
const ImagePreviewComponent = ({
fileUrl,
fileExtension,
handleDownload,
ref,
}) => {
const classes = useStyles();
return (
<>
{fileExtension === 'pdf' ? (
<>
<Box className={classes.pdfAndDocPreviewBox}>
<img
alt="Uploaded file"
src={FILE_EXTENTIONS_ICONS[fileExtension]}
ref={ref}
height="145px"
width="auto"
/>
<Button
className={classes.pdfAndDocPreviewDownloadButton}
variant="contained"
onClick={handleDownload}
>
Download
</Button>
</Box>
</>
) : fileExtension === 'doc' ||
fileExtension === 'docx' ||
fileExtension ===
'vnd.openxmlformats-officedocument.wordprocessingml.document' ||
fileExtension === 'msword' ? (
<>
<Box className={classes.pdfAndDocPreviewBox}>
<img
alt="Uploaded file"
src={FILE_EXTENTIONS_ICONS[fileExtension]}
ref={ref}
height="145px"
width="auto"
/>
<Button
className={classes.pdfAndDocPreviewDownloadButton}
variant="contained"
onClick={handleDownload}
>
Download
</Button>
</Box>
</>
) : (
<TransformWrapper>
{(utils) => (
<React.Fragment>
<Box className={classes.transFormImageBox}>
<TransformComponent
wrapperClass={classes.customTransFormWrapper}
>
<img
alt="Uploaded file"
src={fileUrl}
ref={ref}
height="250px"
width="auto"
/>
</TransformComponent>
</Box>
<ImagePreviewControls {...utils} />
</React.Fragment>
)}
</TransformWrapper>
)}
</>
);
};
export default ImagePreviewComponent;

View File

@ -0,0 +1,55 @@
import makeStyles from '@mui/styles/makeStyles';
import { pxToRem } from '../../theme/typography';
export const useStyles = makeStyles((theme) => ({
pdfAndDocPreviewBox: {
minHeight: '450px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
},
pdfAndDocPreviewDownloadButton: {
marginTop: theme.spacing(2),
fontFamily: theme?.fontFamily?.regular,
},
transFormImageBox: {
minHeight: '65vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
customTransFormWrapper: {
overflow: 'visible',
},
iconSize: {
fontSize: pxToRem(28),
},
controlButtons: {
marginTop: theme.spacing(1),
},
controlButton: {
color: theme.palette.common.white,
cursor: 'pointer',
border: 'none',
borderRadius: theme.spacing(0),
padding: theme.spacing(1.2),
'&:hover': {
backgroundColor: 'rgba(0,0,0,0.9)',
color: theme.palette.common.white,
},
},
zoomOutButton: {
backgroundColor: 'rgba(0,0,0,0.7)',
borderTopLeftRadius: theme.spacing(0.8),
borderBottomLeftRadius: theme.spacing(0.8),
},
zoomResetButton: {
backgroundColor: 'rgba(0,0,0,0.4)',
},
zoomInButton: {
backgroundColor: 'rgba(0,0,0,0.7)',
borderTopRightRadius: theme.spacing(0.8),
borderBottomRightRadius: theme.spacing(0.8),
},
}));

View File

@ -7,22 +7,22 @@ import store from '../redux/store';
export const axiosInstance = axios.create({
baseURL: API_BASE_URL,
crossDomain: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
},
withCredentials: true,
timeout: 300000,
// crossDomain: true,
// headers: {
// 'Content-Type': 'application/json',
// // 'Access-Control-Allow-Origin': '*',
// // 'Access-Control-Allow-Headers': '*',
// },
// withCredentials: false,
// timeout: 300000,
});
axiosInstance.interceptors.request.use(
async function (config) {
try {
const token = JSON.parse(localStorage.getItem('redux'));
if (token?.data?.data) {
config.headers.Authorization = `${token?.data?.data}`;
if (token?.login?.token) {
config.headers.Authorization = `Bearer ${token?.login?.token}`;
}
const state = store.getState();
const companyId = state?.loginAsCompanyAdmin?.companyId; // Extract companyId
@ -47,7 +47,7 @@ axiosInstance.interceptors.request.use(
axiosInstance.interceptors.response.use(
function (response) {
if (response?.data && response?.data?.error !== '') {
if (response?.data && response?.data?.error !== null) {
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
return Promise.reject(response);
}

View File

@ -1,5 +1,5 @@
import { initializeApp } from 'firebase/app';
import { getMessaging, onMessage } from 'firebase/messaging';
import { getMessaging, onMessage, isSupported } from 'firebase/messaging';
export const firebaseConfig = {
apiKey: "AIzaSyDBDwlnQsbIxKni_UzZxDjeIk0akK-vDPM",
@ -12,7 +12,35 @@ export const firebaseConfig = {
const firebaseApp = initializeApp(firebaseConfig);
export default firebaseApp;
export const messaging = getMessaging(firebaseApp);
export const onForegroundMessage = () =>
new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload)));
// Initialize messaging only if supported
let messagingInstance = null;
// Check if messaging is supported before initializing
export const initializeMessaging = async () => {
try {
const isSupportedBrowser = await isSupported();
if (isSupportedBrowser) {
messagingInstance = getMessaging(firebaseApp);
return messagingInstance;
} else {
console.log('Firebase messaging is not supported in this browser');
return null;
}
} catch (error) {
console.error('Error checking messaging support:', error);
return null;
}
};
// Safe getter for messaging
export const getMessagingInstance = () => messagingInstance;
// Safe onForegroundMessage function
export const onForegroundMessage = async () => {
const messaging = await initializeMessaging();
if (!messaging) {
return Promise.resolve(null); // Return resolved promise with null if messaging not supported
}
return new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload)));
};

View File

@ -5,6 +5,12 @@ export const NOTIFICATION = {
ERROR: 'error',
};
export const USER_ROLES = {
SUPER_ADMIN: 'SUPER_ADMIN',
CLINIC_ADMIN: 'CLINIC_ADMIN',
}
export const CLINIC_TYPE = {
UNREGISTERED: 'UNREGISTERED',
REGISTERED: 'REGISTERED',
@ -28,6 +34,8 @@ export const PERMISSIONS = {
};
export const CLINIC_STATUS = {
UNDER_REVIEW:"UNDER_REVIEW",
ACTIVE:"ACTIVE",
ON_HOLD: 'ON_HOLD',
REJECTED: 'REJECTED',
NOT_REVIEWED: 'NOT_REVIEWED',
@ -145,6 +153,10 @@ export const numRegex = /^[0-9\b]+$/;
import PdfIcon from '../assets/images/icon/pdf.png';
import DocIcon from '../assets/images/icon/doc.png';
import JpegIcon from '../assets/images/icon/jpeg.png';
import JpgIcon from '../assets/images/icon/jpg.png';
import PngIcon from '../assets/images/icon/png.png';
import SvgIcon from '../assets/images/icon/svg.png';
export const FILE_EXTENTIONS_ICONS = {
pdf: PdfIcon,
@ -152,6 +164,10 @@ export const FILE_EXTENTIONS_ICONS = {
docx: DocIcon,
'vnd.openxmlformats-officedocument.wordprocessingml.document': DocIcon,
msword: DocIcon,
jpeg: JpegIcon,
jpg: JpgIcon,
png: PngIcon,
svg: SvgIcon,
};
export const DEBOUNCE_DELAY = 500;

View File

@ -1,7 +1,7 @@
import { onMessage } from 'firebase/messaging';
import { createContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { messaging } from '../config/firebase';
import { initializeMessaging, getMessagingInstance } from '../config/firebase';
import { redirectByNotificationType } from '../views/Notifications/notificationUtils';
// Create the context
@ -12,8 +12,15 @@ const FirebaseProvider = (props) => {
const navigate = useNavigate(); // Initialize navigation without page reload
useEffect(() => {
checkNewMessage(messaging);
}, [messaging]);
const setupMessaging = async () => {
const messagingInstance = await initializeMessaging();
if (messagingInstance) {
checkNewMessage(messagingInstance);
}
};
setupMessaging();
}, []);
const showBrowserNotifications = (payload) => {
// Handle notification click

View File

@ -4,7 +4,7 @@ import ProfileSection from './ProfileSection';
import { useStyles } from '../mainLayoutStyles';
import NotificationSection from './NotificationSection ';
import { useSelector } from 'react-redux';
import { CLINIC_STATUS } from '../../../constants';
import { CLINIC_STATUS, USER_ROLES } from '../../../constants';
function Header() {
const theme = useTheme();
@ -21,7 +21,7 @@ function Header() {
}
>
<Typography>
{user?.isBsAdmin ? null : `Welcome to ${user?.company?.name}`}
{user?.userType == USER_ROLES.SUPER_ADMIN.toLowerCase() ? null : `Welcome to ${user?.created_clinics?.[0]?.name}`}
</Typography>
<Box className={classes.profilDiv}>
<NotificationSection />

View File

@ -16,6 +16,7 @@ import { useTheme } from '@mui/material/styles';
// assets
import signoutImg from '../../../assets/images/icon/signout.svg';
import phoneImg from '../../../assets/images/icon/phone.svg';
import { useStyles } from '../mainLayoutStyles';
import MainCard from './MainCard';
@ -38,7 +39,7 @@ const ProfileSection = () => {
const anchorRef = useRef(null);
const user = useSelector((state) => state?.login?.user);
const isBsAdmin = user?.isBsAdmin;
const isSuperAdmin = user?.isSuperAdmin;
const companyStatus = useSelector(
(state) => state?.login?.user?.company?.status
@ -66,7 +67,8 @@ const ProfileSection = () => {
const menuItems = [
{ id: 5, img: signoutImg, text: 'Sign Out', alt: 'signoutImg' },
{ id: 1, img: phoneImg, text: 'Contact Us', alt: 'contactUsImg' },
{ id: 2, img: signoutImg, text: 'Sign Out', alt: 'signoutImg' },
].filter(Boolean);
const renderProfile = (item, index) => (
@ -76,6 +78,7 @@ const ProfileSection = () => {
<ListItem button key={`menu-item-${item.id}`} onClick={() => handleMenuItemClick(item)}>
<Box className={classes.listIcon}>
<img
style={{ width: "20px", height: "20px" }}
src={item.img}
className={classes.iconImage}
alt={item.alt}
@ -94,10 +97,10 @@ const ProfileSection = () => {
const handleMenuItemClick = (item) => {
switch (item.id) {
case 1:
navigate('/profile-settings');
// navigate('/contact-us');
break;
case 2:
navigate(`/profile`);
commonLogoutFunc();
break;
case 3:
// setShowTransactionHistoryPopup(true);

View File

@ -34,7 +34,7 @@ import { SIDEBAR_CONFIG } from "./sideBarConfig"; // Adjust path if necessary
const Sidebar = ({ onClose, showCloseIcon }) => {
const classes = useStyles();
const location = useLocation();
const { isBSAdmin } = isBSPortal();
const { isSuperAdmin } = isBSPortal();
const [activeLink, setActiveLink] = useState("Dashboard");
const [accordianActiveLink, setAccordianActiveLink] = useState("All Jobs");
const [parentRoute, setParentRoute] = useState("");
@ -92,13 +92,13 @@ const Sidebar = ({ onClose, showCloseIcon }) => {
(companyStatus === CLINIC_STATUS.APPROVED &&
HIDE_FUNCTIONALITY &&
HIDE_MODULES.includes(item?.path)) ||
(isBSAdmin && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path));
(isSuperAdmin && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path));
// Only render if user has the required role
if (hasRole) {
// Determine if the link should be disabled
const isDisabled =
(!isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature;
(!isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature;
const targetPath = isDisabled ? "#" : `/${item.path}`;
const isActive = activeLink === item.path; // Check if this link is the active one
@ -239,7 +239,7 @@ const Sidebar = ({ onClose, showCloseIcon }) => {
{visibleChildren.map((subItem, subIndex) => {
// Determine if the sub-item link should be disabled
const isSubDisabled =
!isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED; // Add hideFeature logic if needed for sub-items
!isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED; // Add hideFeature logic if needed for sub-items
const subTargetPath = isSubDisabled ? "#" : `/${subItem.path}`;
const isSubActive = combinedRoute === subItem.path; // Check if this child link is active

View File

@ -9,10 +9,9 @@ import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
import SettingsIcon from '@mui/icons-material/Settings';
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';
import PaymentIcon from '@mui/icons-material/Payment';
import PaymentOutlinedIcon from '@mui/icons-material/PaymentOutlined';
import { USER_ROLES } from '../../../constants';
// Define the sidebar configuration with proper permission fields
export const SIDEBAR_CONFIG = [
@ -48,6 +47,14 @@ export const SIDEBAR_CONFIG = [
// Only super admin can access admin staff management
roles: [USER_ROLES.SUPER_ADMIN]
},
{
text: 'Payment Management',
path: 'payment-management',
icon: PaymentOutlinedIcon,
activeIcon: PaymentIcon,
// Only super admin can access payment management
roles: [USER_ROLES.SUPER_ADMIN]
},
{
text: 'Doctor/Nurse Management',
path: 'doctor',

View File

@ -3,7 +3,7 @@ export const mockSuperAdmin = {
id: 1,
name: "Super Admin User",
email: "superadmin@example.com",
isBsAdmin: true,
isSuperAdmin: true,
isAdmin: true,
permissions: ["SUPER_ADMIN_PERMISSION"],
roles: [
@ -25,7 +25,7 @@ export const mockClinicAdmin = {
id: 2,
name: "Clinic Admin User",
email: "clinicadmin@example.com",
isBsAdmin: false,
isSuperAdmin: false,
isAdmin: true,
permissions: ["CLINIC_ADMIN_PERMISSION"],
roles: [

View File

@ -25,7 +25,7 @@ export const setMockUser = (userType) => {
};
// Export mock user types for convenience
export const MOCK_USER_TYPES = {
export const MOCK_USER_ROLES = {
SUPER_ADMIN: 'superAdmin',
CLINIC_ADMIN: 'clinicAdmin'
};

View File

@ -1,9 +1,3 @@
// User role constants
export const USER_ROLES = {
SUPER_ADMIN: 'SUPER_ADMIN',
CLINIC_ADMIN: 'CLINIC_ADMIN',
};
// Initial state
const initialState = {
role: null,

View File

@ -12,6 +12,7 @@ import ClinicSetup from "../views/ClinicSetup";
import ClinicTranscripts from "../views/ClinicTranscripts";
import ContractManagement from "../views/ContractManagement";
import MasterDataManagement from "../views/MasterData";
import PaymentManagement from "../views/PaymentManagement";
export const routesData = [
{
@ -26,6 +27,7 @@ export const routesData = [
{ path: "/clinicSetup", component: ClinicSetup },
{ path: "/transcripts", component: ClinicTranscripts },
{ path: "/masterData", component: MasterDataManagement },
{ path: "/payment-management", component: PaymentManagement },
],
isProtected: true,
},

View File

@ -9,11 +9,11 @@ const withPermission = (Component) => (props) => {
const companyStatus = useSelector(
(state) => state?.login?.user?.company?.status
);
const { isBSAdmin } = false;
// const { isBSAdmin } = isBSPortal();
const { isSuperAdmin } = false;
// const { isSuperAdmin } = isBSPortal();
// If the user is a BS Admin, render the component without any checks
if (isBSAdmin === true) {
if (isSuperAdmin === true) {
return <Component {...props} />;
}

View File

@ -0,0 +1,11 @@
import { axiosInstance } from "../config/api";
export const signup = (data) => {
const url = '/auth/register';
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};

View File

@ -1,45 +1,86 @@
import { axiosInstance } from "../config/api";
import { CLINIC_TYPE } from "../constants";
import { clinicsData, registeredClinicsData } from "../mock/clinics";
// export const getClinics = (params) => {
// switch (params.type) {
// case CLINIC_TYPE.UNREGISTERED:
// return { data: clinicsData };
// case CLINIC_TYPE.REGISTERED:
// return { data: registeredClinicsData };
// case CLINIC_TYPE.SUBSCRIBED:
// return { data: registeredClinicsData };
// default:
// return { data: clinicsData };
// }
// };
export const getClinics = (params) => {
switch (params.type) {
case CLINIC_TYPE.UNREGISTERED:
return { data: clinicsData };
case CLINIC_TYPE.REGISTERED:
return { data: registeredClinicsData };
case CLINIC_TYPE.SUBSCRIBED:
return { data: registeredClinicsData };
default:
return { data: clinicsData };
}
};
console.log(params);
export const getClinicsById = (id) => {
const url = `/companies/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
let searchParams = new URLSearchParams();
searchParams.append("size", params?.pagination?.pageSize ?? 10);
searchParams.append("page", params?.pagination.pageIndex ?? 0);
searchParams.append("filter_type", params?.type ?? CLINIC_TYPE.REGISTERED);
searchParams.append("search", params?.globalFilter ?? "");
export const updateClinicStatus = (id, data) => {
const url = `/companies/${id}/company-review`;
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
let url = `/clinics/?${searchParams.toString()}`;
export const getClinicsDashboardStatsById = () => {
const url = `/companies/dashboard-stats`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const getLatestClinicId = () => {
const url = `/clinics/latest-id`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const getClinicsById = (id) => {
const url = `/clinics/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateClinicStatus = (data) => {
const url = `/admin/clinic/status/`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateClinicDocs = (id, data) => {
const url = `/clinics/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const getClinicsDashboardStatsById = () => {
const url = `/companies/dashboard-stats`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};

View File

@ -0,0 +1,33 @@
import { axiosInstance } from "../config/api";
export const getDashboardStats = () => {
const url = '/dashboard/';
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const getSignupMasterPricing = () => {
const url = '/dashboard/signup-pricing-master/';
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const setSignupMasterPricing = (data) => {
const url = '/dashboard/signup-pricing-master/';
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};

View File

@ -23,3 +23,27 @@ export const fileUpload = (data, type = null, companyId = null) => {
.catch((err) => reject(err));
});
};
export const getPresignedUrl = (data) => {
const url = `/s3`;
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const uploadToS3 = (file, putUrl) =>{
const url = putUrl;
return new Promise((resolve, reject) => {
axiosInstance
.put(url, file, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => resolve(response))
.catch((err) => reject(err));
});
}

View File

@ -0,0 +1,5 @@
import { jwtDecode } from "jwt-decode";
export const decodeJWT = (token) => {
return jwtDecode(token);
}

View File

@ -1,6 +1,6 @@
import { logout } from "../services/users.service";
import { deleteToken } from "firebase/messaging";
import { messaging } from "../config/firebase";
import { getMessagingInstance } from "../config/firebase";
export const isLoggedIn = () => {
let redux = JSON.parse(localStorage.getItem("redux"));
@ -27,8 +27,8 @@ export const resetLocalStorage = () => {
export const isBSPortal = () => {
let redux = JSON.parse(localStorage.getItem("redux"));
const isBSAdmin = redux?.login?.user?.isBsAdmin;
return { isBSAdmin };
const isSuperAdmin = redux?.login?.user?.isSuperAdmin;
return { isSuperAdmin };
};
export const commonLogoutFunc = async (redirectPath = "/auth/login") => {

View File

@ -29,90 +29,78 @@ const FileEvaluate = ({
const [reviewedLogoFiles, setReviewedLogoFiles] = useState([]);
const [notReviewedLogoFiles, setNotReviewedLogoFiles] = useState([]);
const [reviewedOtherFiles, setReviewedOtherFiles] = useState([]);
const [notReviewedOtherFiles, setNotReviewedOtherFile] = useState([]);
const [notReviewedOtherFiles, setNotReviewedOtherFiles] = useState([]);
const getAscendingArray = (array) => {
const gstFiles = array.filter((file) => file.documentType === 'GST');
const panFiles = array.filter((file) => file.documentType === 'PAN');
const tanFiles = array.filter((file) => file.documentType === 'TAN');
const filteredArray = [];
const maxLength = Math.max(
gstFiles.length,
panFiles.length,
tanFiles.length
);
// const getAscendingArray = (array) => {
for (let i = 0; i < maxLength; i++) {
if (gstFiles[i]) {
filteredArray.push(gstFiles[i]);
}
if (panFiles[i]) {
filteredArray.push(panFiles[i]);
}
if (tanFiles[i]) {
filteredArray.push(tanFiles[i]);
}
}
return filteredArray;
};
// return filteredArray;
// };
useEffect(() => {
// ...........reviewed logo file set...............
if (Array.isArray(files)) {
const filteredFiles = files.filter(
(file) =>
file.documentType === 'LOGO' &&
(file.status === CLINIC_DOCUMENT_STATUS.APPROVED ||
file.status === CLINIC_DOCUMENT_STATUS.REJECTED)
);
setReviewedLogoFiles(filteredFiles);
}
// .............no review logo files set............
if (Array.isArray(files)) {
const filteredFiles = files.filter(
(file) =>
file.documentType === 'LOGO' &&
file.status === CLINIC_DOCUMENT_STATUS.NOT_REVIEWED
);
setNotReviewedLogoFiles(filteredFiles);
}
// ..............reviewed other file set............
if (Array.isArray(files)) {
const filteredFiles = files.filter(
(file) =>
(file.documentType === 'PAN' ||
file.documentType === 'TAN' ||
file.documentType === 'GST') &&
(file.status === CLINIC_DOCUMENT_STATUS.APPROVED ||
file.status === CLINIC_DOCUMENT_STATUS.REJECTED)
);
setReviewedOtherFiles(getAscendingArray(filteredFiles));
}
// .............. no reviewed other file set...........
if (Array.isArray(files)) {
const filteredFiles = files.filter(
(file) =>
(file.documentType === 'PAN' ||
file.documentType === 'TAN' ||
file.documentType === 'GST') &&
file.status === CLINIC_DOCUMENT_STATUS.NOT_REVIEWED
);
setNotReviewedOtherFile(getAscendingArray(filteredFiles));
}
if (!Array.isArray(files)) return;
// Process all files at once to avoid multiple iterations
const reviewedLogo = [];
const notReviewedLogo = [];
const reviewedOther = [];
const notReviewedOther = [];
files.forEach(file => {
// Handle logo documents
if (file.logo_doc) {
if (file.logo_doc_is_verified) {
reviewedLogo.push({file: file.logo_doc, documentType: 'LOGO', isVerified: file.logo_doc_is_verified});
} else {
notReviewedLogo.push({file: file.logo_doc, documentType: 'LOGO', isVerified: file.logo_doc_is_verified});
}
}
// Handle ABN and contract documents (excluding logo docs which are handled separately)
if (file.abn_doc ) {
if (file.abn_doc_is_verified) {
reviewedOther.push({file: file.abn_doc, documentType: 'ABN', isVerified: file.abn_doc_is_verified});
}
else{
notReviewedOther.push({file: file.abn_doc, documentType: 'ABN', isVerified: file.abn_doc_is_verified});
}
}
if (file.contract_doc) {
if (file.contract_doc_is_verified) {
reviewedOther.push({file: file.contract_doc, documentType: 'CONTRACT', isVerified: file.contract_doc_is_verified});
} else{
notReviewedOther.push({file: file.contract_doc, documentType: 'CONTRACT', isVerified: file.contract_doc_is_verified});
}
}
});
// Update state with filtered files
setReviewedLogoFiles(reviewedLogo);
setNotReviewedLogoFiles(notReviewedLogo);
setReviewedOtherFiles(reviewedOther);
setNotReviewedOtherFiles(notReviewedOther);
// Debug logs
console.log('Files processed:', files.length);
console.log('reviewedLogoFiles:', reviewedLogo);
console.log('notReviewedLogoFiles:', notReviewedLogo);
console.log('reviewedOtherFiles:', reviewedOther);
console.log('notReviewedOtherFiles:', notReviewedOther);
}, [files]);
// .........................get file name and extention function.......................
const getFileNameUsingFile = (file) => {
if (file) {
const url = new URL(file.fileURL);
return url.pathname.split('/').pop();
// const url = new URL(file);
// return url.pathname.split('/').pop();
return file
}
return;
};
const getFileExtentionUsingFile = (file) => {
if (file) {
const url = new URL(file.fileURL);
const url = new URL(file.file);
const fileName = url.pathname.split('/').pop();
return fileName.split('.').pop();
}
@ -129,7 +117,7 @@ const FileEvaluate = ({
: CLINIC_DOCUMENT_STATUS.REJECTED,
};
if (file.documentType === 'LOGO') {
if (file.logo_doc) {
const updatedFiles = [...notReviewedLogoFiles];
updatedFiles.splice(index, 1, newFile);
setNotReviewedLogoFiles(updatedFiles);
@ -181,8 +169,12 @@ const FileEvaluate = ({
const handleDownload = (file) => {
if (file) {
const anchor = document.createElement('a');
anchor.href = file.fileURL;
anchor.download = getFileNameUsingFile(file);
anchor.href = file.file; // file is now a direct URL
// Extract filename from URL for download attribute
const fileName = file.file.split('/').pop();
anchor.download = fileName;
anchor.click();
}
};
@ -200,7 +192,7 @@ const FileEvaluate = ({
return (
<>
{/* ............CLINIC is Approved that time show only card............. */}
{companyStatus === CLINIC_STATUS.APPROVED ? (
{companyStatus === CLINIC_STATUS.ACTIVE ? (
<>
<Grid container className={classes.mainGrid} gap={3.5}>
{reviewedLogoFiles.map((file, index) => (
@ -217,10 +209,10 @@ const FileEvaluate = ({
<Typography className={classes.titleOfBox}>
CLINIC LOGO INFO
</Typography>
<Typography className={classes.updatedDate}>
{/* <Typography className={classes.updatedDate}>
Uploaded:{' '}
{format(new Date(file?.updatedAt), 'dd MMM yyyy')}
</Typography>
</Typography> */}
<Typography className={classes.documentNumberAndNameLabel}>
{companyName} LOGO
</Typography>
@ -228,7 +220,7 @@ const FileEvaluate = ({
<Box className={classes.imageBox}>
<img
className={classes.image}
src={file.fileURL}
src={file.file}
alt="Uploaded File"
/>
</Box>
@ -251,10 +243,10 @@ const FileEvaluate = ({
<Typography className={classes.titleOfBox}>
CLINIC {file.documentType} INFO
</Typography>
<Typography className={classes.updatedDate}>
{/* <Typography className={classes.updatedDate}>
Uploaded:{' '}
{format(new Date(file?.updatedAt), 'dd MMM yyyy')}
</Typography>
</Typography> */}
<Typography className={classes.documentNumberAndNameLabel}>
{file.documentNumber}
</Typography>
@ -287,10 +279,10 @@ const FileEvaluate = ({
<Typography className={classes.titleOfBox}>
CLINIC LOGO INFO
</Typography>
<Typography className={classes.updatedDate}>
{/* <Typography className={classes.updatedDate}>
Uploaded:{' '}
{format(new Date(file?.updatedAt), 'dd MMM yyyy')}
</Typography>
</Typography> */}
<Typography className={classes.documentNumberAndNameLabel}>
{companyName} LOGO
</Typography>
@ -341,10 +333,10 @@ const FileEvaluate = ({
<Typography className={classes.titleOfBox}>
CLINIC {file.documentType} INFO
</Typography>
<Typography className={classes.updatedDate}>
{/* <Typography className={classes.updatedDate}>
Uploaded:{' '}
{format(new Date(file?.updatedAt), 'dd MMM yyyy')}
</Typography>
</Typography> */}
<Typography className={classes.documentNumberAndNameLabel}>
{companyName} LOGO
</Typography>
@ -494,17 +486,17 @@ const FileEvaluate = ({
<Typography className={classes.titleOfBox}>
CLINIC {file.documentType} INFO
</Typography>
<Typography className={classes.updatetdDate}>
{/* <Typography className={classes.updatetdDate}>
Uploaded:{' '}
{format(new Date(file?.updatedAt), 'dd MMM yyyy')}
</Typography>
</Typography> */}
<Box className={classes.documentNumberAndNameLabelBox}>
<Typography
{/* <Typography
className={classes.documentNumberAndNameLabel}
>
{file.documentNumber}
</Typography>
{file.documentNumber.length === GST_NUMBER_LENGTH &&
</Typography> */}
{/* {file.documentNumber.length === GST_NUMBER_LENGTH &&
file.documentType !== 'TAN' &&
file.documentType !== 'PAN' && (
<Typography
@ -521,7 +513,7 @@ const FileEvaluate = ({
>
<VerifiedIcon className={classes.verifyIcon} />
</Typography>
)}
)} */}
</Box>
</Box>
@ -632,17 +624,15 @@ const FileEvaluate = ({
<Grid container>
<Grid item md={12} className={classes.previewFileTitle}>
<Typography color="white">
{getFileNameUsingFile(previewFile)}
{previewFile.split('/').pop()}
</Typography>
</Grid>
</Grid>
<Grid xs={12} className={classes.previewImageGrid}>
<ImagePreviewComponent
file={previewFile}
fileUrl={previewFile.fileURL}
fileUrl={previewFile}
fileExtension={getFileExtentionUsingFile(previewFile)}
handlePreview={handlePreview}
handleDownload={() => handleDownload(previewFile)}
classes={classes}
/>

View File

@ -9,13 +9,13 @@ import { profile } from '../../Login/loginAction';
import { setClinicId } from '../store/logInAsClinicAdminAction';
import { useStyles } from './styles/generalInformationStyles';
const GeneralInformation = ({ companyData, companyAdminData }) => {
const GeneralInformation = ({ clinicData, clinicAdminData }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const classes = useStyles();
const handleViewCompanyDashboard = async () => {
dispatch(setClinicId({ companyId: companyData?.id }));
dispatch(setClinicId({ companyId: clinicData?.id }));
await dispatch(profile());
navigate('/');
};
@ -32,13 +32,13 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
<Grid item xs={3}>
<Typography className={classes.generalInfoUploaded}>
Uploaded:{' '}
{companyData?.updatedAt
? format(new Date(companyData?.createdAt), 'dd MMM yyyy')
{clinicData?.updatedAt
? format(new Date(clinicData?.createdAt), 'dd MMM yyyy')
: ''}
</Typography>
</Grid>
<Grid item xs={3}>
{companyData?.status === CLINIC_STATUS.APPROVED && (
{clinicData?.status === CLINIC_STATUS.APPROVED && (
<LoadingButton
variant="contained"
display={false}
@ -55,15 +55,15 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
<Grid className={classes.companyNameGrid}>
<Grid item xs={12}>
<Typography className={classes.companyNameLabel}>
Para Hills
{clinicData?.name}
</Typography>
</Grid>
<Grid xs={12} className={classes.companyNameSubTitleGrid}>
<div>
<Typography className={classes.companyNameSubTitleLabel}>
Raised On:{' '}
{companyData?.requestRaisedOn
? format(new Date(companyData?.requestRaisedOn), 'dd MMM yyyy')
{clinicData?.update_time
? format(new Date(clinicData?.update_time), 'dd MMM yyyy')
: NOT_AVAILABLE_TEXT}
</Typography>
</div>
@ -74,17 +74,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
/>
<div>
<Typography className={classes.companyNameSubTitleLabel}>
Website: {companyData?.website}
</Typography>
</div>
<Divider
className={classes.dividerClass}
orientation="vertical"
flexItem
/>
<div>
<Typography className={classes.companyNameSubTitleLabel}>
User ID: admin@gmail.com
{clinicData?.email}
</Typography>
</div>
</Grid>
@ -93,7 +83,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
<Box className={classes.companyUserDetails}>
<Box>
<Typography className={classes.companyUserDetailsTitle}>
{companyAdminData?.name}
{clinicAdminData?.name}
</Typography>
<Typography className={classes.companyUserDetailsSubTitle}>
User Name
@ -101,8 +91,8 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
</Box>
<Box>
<Typography className={classes.companyUserDetailsTitle}>
{companyAdminData?.designation
? companyAdminData?.designation
{clinicAdminData?.designation
? clinicAdminData?.designation
: NOT_AVAILABLE_TEXT}
</Typography>
<Typography className={classes.companyUserDetailsSubTitle}>
@ -111,8 +101,8 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
</Box>
<Box>
<Typography className={classes.companyUserDetailsTitle}>
{companyAdminData?.mobile
? `+91-${companyAdminData.mobile}`
{clinicAdminData?.phone
? `${clinicAdminData.phone}`
: NOT_AVAILABLE_TEXT}
</Typography>
<Typography className={classes.companyUserDetailsSubTitle}>
@ -121,7 +111,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
</Box>
<Box>
<Typography className={classes.companyUserDetailsTitle}>
{companyData?.pinCode ? companyData?.pinCode : NOT_AVAILABLE_TEXT}
{clinicData?.postal_code ? clinicData?.postal_code : NOT_AVAILABLE_TEXT}
</Typography>
<Typography className={classes.companyUserDetailsSubTitle}>
Pincode
@ -133,7 +123,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => {
<Grid container className={classes.companyDetailsAddressGrid}>
<Grid item xs={12}>
<Typography className={classes.companyUserDetailsTitle}>
{companyData?.street ? companyData?.street : NOT_AVAILABLE_TEXT}
{clinicData?.address ? clinicData?.address : NOT_AVAILABLE_TEXT}
</Typography>
<Typography className={classes.companyUserDetailsSubTitle}>
Full Address

View File

@ -1,6 +1,6 @@
import CloseIcon from '@mui/icons-material/Close';
import DoneIcon from '@mui/icons-material/Done';
import { LoadingButton } from '@mui/lab';
import CloseIcon from "@mui/icons-material/Close";
import DoneIcon from "@mui/icons-material/Done";
import { LoadingButton } from "@mui/lab";
import {
Box,
Button,
@ -10,30 +10,31 @@ import {
Tab,
TextField,
Typography,
} from '@mui/material';
import { useFormik } from 'formik';
import React, { useEffect, useRef, useState } from 'react';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import * as Yup from 'yup';
import onHoldDisableIcon from '../../assets/images/icon/onHoldDisable.svg';
import onHoldEnableIcon from '../../assets/images/icon/onHoldEnable.svg';
import CustomBreadcrumbs from '../../components/CustomBreadcrumbs';
import Loader from '../../components/Loader';
import PageHeader from '../../components/PageHeader';
} from "@mui/material";
import { useFormik } from "formik";
import React, { useEffect, useRef, useState } from "react";
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
import * as Yup from "yup";
import onHoldDisableIcon from "../../assets/images/icon/onHoldDisable.svg";
import onHoldEnableIcon from "../../assets/images/icon/onHoldEnable.svg";
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import Loader from "../../components/Loader";
import PageHeader from "../../components/PageHeader";
import {
CLINIC_DOCUMENT_STATUS,
CLINIC_STATUS,
NOTIFICATION,
} from '../../constants';
} from "../../constants";
import {
getClinicsById,
updateClinicStatus,
} from '../../services/clinics.service';
import { pushNotification } from '../../utils/notification';
import CustomModal from '../Modal/Modal';
import { useStyles } from './clinicDetailsStyles';
} from "../../services/clinics.service";
import { pushNotification } from "../../utils/notification";
import CustomModal from "../Modal/Modal";
import { useStyles } from "./clinicDetailsStyles";
import FileEvaluate from "./component/FileEvaluate";
import GeneralInformation from './component/GeneralInformation';
import GeneralInformation from "./component/GeneralInformation";
function ClinicDetails() {
const classes = useStyles();
@ -42,11 +43,12 @@ function ClinicDetails() {
const navigate = useNavigate();
const queryParams = new URLSearchParams(location.search);
const [isLoading, setIsLoading] = useState(false);
const [companyData, setCompanyData] = useState('');
const [companyAdminData, setCompanyAdminData] = useState('');
const [clinicData, setClinicData] = useState("");
const [clinicFiles, setClinicFiles] = useState("");
const [clinicAdminData, setClinicAdminData] = useState("");
const [isShowReasonModel, setIsShowReasonModel] = useState(false);
const [updateFiles, setUpdateFiles] = useState([]);
const [buttonClickStatus, setButtonClickStatus] = useState('');
const [buttonClickStatus, setButtonClickStatus] = useState("");
const [isRejectButtonShow, setIsRejectedButtonShow] = useState(false);
const [isOnHoldButtonShow, setIsOnHoldButtonShow] = useState(false);
const [isAcceptButtonShow, setIsAcceptedButtonShow] = useState(false);
@ -57,13 +59,12 @@ function ClinicDetails() {
try {
setIsLoading(true);
const response = await getClinicsById(id);
setCompanyData(response?.data?.data);
setCompanyAdminData(
response?.data?.data?.companyUsers?.find((user) => user.isAdmin) || null
);
setClinicData(response?.data?.data?.clinic);
setClinicFiles(response?.data?.data?.clinic_files);
setClinicAdminData(response?.data?.data?.creator);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error fetching data:', error);
console.error("Error fetching data:", error);
} finally {
setIsLoading(false);
}
@ -75,17 +76,17 @@ function ClinicDetails() {
// ...................breadcrumbs array........................
const breadcrumbs = [
{
label: 'Dashboard',
path: '/',
label: "Dashboard",
path: "/",
},
{
label: 'Clinic List',
path: '/clinics',
query: { tab: queryParams.get('tab') || 'UNREGISTERED' },
label: "Clinic List",
path: "/clinics",
query: { tab: queryParams.get("tab") || "UNREGISTERED" },
},
{
label: 'Clinics Details',
path: '',
label: "Clinics Details",
path: "",
},
];
@ -93,7 +94,7 @@ function ClinicDetails() {
const updateCompany = async (body) => {
try {
const response = await updateClinicStatus(companyData?.id, body);
const response = await updateClinicStatus(body);
if (response?.data?.error) {
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
setIsShowReasonModel(false);
@ -105,7 +106,7 @@ function ClinicDetails() {
}
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error', error);
console.error("Error", error);
}
};
@ -116,7 +117,7 @@ function ClinicDetails() {
setIsOnHoldButtonShow(false);
};
const defaultFormData = useRef({
reason: '',
reason: "",
});
const fieldRefs = {
@ -124,7 +125,7 @@ function ClinicDetails() {
};
const validationSchema = Yup.object({
reason: Yup.string().required('Reason is required.'),
reason: Yup.string().required("Reason is required."),
});
const formik = useFormik({
initialValues: defaultFormData.current,
@ -148,9 +149,9 @@ function ClinicDetails() {
};
const getNotReviewedFileCount = () => {
if (companyData?.companyDocuments) {
const notReviewedDocuments = companyData.companyDocuments.filter(
(document) => document.status === 'NOT_REVIEWED'
if (clinicData?.companyDocuments) {
const notReviewedDocuments = clinicData.companyDocuments.filter(
(document) => document.status === "NOT_REVIEWED"
);
return notReviewedDocuments.length;
}
@ -160,19 +161,19 @@ function ClinicDetails() {
const handleCancelRejection = async () => {
try {
setIsLoading(true);
// const resp = await companyCancelRejection(companyData?.id);
// if (resp?.data?.error) {
// pushNotification(resp?.data?.message, NOTIFICATION.ERROR);
// fetchData();
// } else {
// pushNotification(resp?.data?.message, NOTIFICATION.SUCCESS);
// fetchData();
// setIsOnHoldButtonShow(false);
// setIsRejectedButtonShow(false);
// setUpdateFiles(() => []);
// setButtonClickStatus('');
// }
console.log("cancel rejection")
// const resp = await companyCancelRejection(clinicData?.id);
// if (resp?.data?.error) {
// pushNotification(resp?.data?.message, NOTIFICATION.ERROR);
// fetchData();
// } else {
// pushNotification(resp?.data?.message, NOTIFICATION.SUCCESS);
// fetchData();
// setIsOnHoldButtonShow(false);
// setIsRejectedButtonShow(false);
// setUpdateFiles(() => []);
// setButtonClickStatus('');
// }
console.log("cancel rejection");
} catch (error) {
// Handle error if needed
} finally {
@ -192,17 +193,17 @@ function ClinicDetails() {
});
const data = {
userId: companyAdminData?.id,
reason: values ? values.reason : '',
clinic_id: clinicData?.id,
rejection_reason: values ? values.reason : "",
status:
buttonClickStatus === 'Rejected'
? CLINIC_STATUS.REJECTED
: buttonClickStatus === 'On Hold'
? CLINIC_STATUS.ON_HOLD
: CLINIC_STATUS.APPROVED,
documentStatus: {
...documentStatusMap,
},
buttonClickStatus === "Rejected"
? CLINIC_STATUS.REJECTED.toLowerCase()
: buttonClickStatus === "On Hold"
? CLINIC_STATUS.UNDER_REVIEW.toLowerCase()
: CLINIC_STATUS.ACTIVE.toLowerCase(),
// documentStatus: {
// ...documentStatusMap,
// },
};
return data;
};
@ -210,17 +211,17 @@ function ClinicDetails() {
// .................handle accept reject and on hold event.............
const handleRejectClick = () => {
setIsShowReasonModel(true);
setButtonClickStatus('Rejected');
setButtonClickStatus("Rejected");
};
const handleOnHoldClick = () => {
setIsShowReasonModel(true);
setButtonClickStatus('On Hold');
setButtonClickStatus("On Hold");
};
const handleAcceptClick = () => {
// setIsShowReasonModel(true);
setButtonClickStatus('Accepted');
setButtonClickStatus("Accepted");
const body = formatedData();
updateCompany(body);
@ -229,31 +230,28 @@ function ClinicDetails() {
// ..................update file use effects................
useEffect(() => {
setIsRejectedButtonShow(
updateFiles.some(
(file) => file.status === CLINIC_DOCUMENT_STATUS.REJECTED
)
clinicFiles.abn_doc_is_verified != true &&
clinicFiles.contract_doc_is_verified != true
);
setIsOnHoldButtonShow(
updateFiles.some(
(file) => file.status === CLINIC_DOCUMENT_STATUS.REJECTED
)
);
const notReviewedFileCount = getNotReviewedFileCount();
const areAllFilesApproved = updateFiles.every(
(file) => file.status === CLINIC_DOCUMENT_STATUS.APPROVED
setIsOnHoldButtonShow(
clinicFiles.abn_doc_is_verified != true &&
clinicFiles.contract_doc_is_verified != true
);
const areAllFilesApproved =
clinicFiles.abn_doc_is_verified == true &&
clinicFiles.contract_doc_is_verified == true;
if (
notReviewedFileCount === updateFiles.length &&
areAllFilesApproved &&
companyData?.status !== CLINIC_STATUS.ON_HOLD &&
companyData?.status !== CLINIC_STATUS.REJECTED
clinicData?.status === CLINIC_STATUS.UNDER_REVIEW.toLowerCase()
) {
setIsAcceptedButtonShow(true);
} else {
setIsAcceptedButtonShow(false);
}
}, [updateFiles]);
}, [clinicFiles]);
// ..............handle table change..........
const handleTabChange = (event, newValue) => {
@ -261,7 +259,7 @@ function ClinicDetails() {
};
const handleEditCompanyInfo = () => {
navigate(`/clinics/${companyData?.id}/edit`);
navigate(`/clinics/${clinicData?.id}/edit`);
};
const handleSubmitClick = async () => {
@ -275,10 +273,10 @@ function ClinicDetails() {
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",
});
}
@ -302,7 +300,7 @@ function ClinicDetails() {
pageTitle="Clinics Details"
hideAddButton
extraComponent={
companyData.status === CLINIC_STATUS.APPROVED ? (
clinicData.status === CLINIC_STATUS.ACTIVE.toLowerCase() ? (
<>
<Button
className={classes.editCompanyInfoButton}
@ -332,7 +330,7 @@ function ClinicDetails() {
<CloseIcon className={classes.statusButtonIcon} /> Reject
</Button>
<Box display="flex" className={classes.cancelRejectionBox}>
{companyData?.status === CLINIC_STATUS.REJECTED && (
{clinicData?.status === CLINIC_STATUS.REJECTED && (
<Link
className={classes.cancelRejectionLink}
onClick={handleCancelRejection}
@ -389,12 +387,12 @@ function ClinicDetails() {
<Box>
<Paper className={classes.generalInfoPaper}>
<GeneralInformation
companyData={companyData}
companyAdminData={companyAdminData}
clinicData={clinicData}
clinicAdminData={clinicAdminData}
/>
</Paper>
</Box>
{/* {companyData.status === CLINIC_STATUS.APPROVED ? (
{clinicData.status === CLINIC_STATUS.ACTIVE ? (
<>
<FormSectionHeading
title="More Details"
@ -405,57 +403,53 @@ function ClinicDetails() {
<Box>
<TabList onChange={handleTabChange}>
<Tab label="Document Details" value={1} />
{/* <Tab label="Plan Details" value={2} /> */}
{/* <Tab label="Recruitment Analytics" value={3} /> */}
{/* </TabList>
<Tab label="Plan Details" value={2} />
</TabList>
</Box>
<TabPanel className={classes.tabsPanel} value={1}>
<Box>
<FileEvaluate
companyStatus={companyData && companyData?.status}
companyName={companyData && companyData?.name}
files={companyData && companyData?.companyDocuments}
companyStatus={clinicData && clinicData?.status}
companyName={clinicData && clinicData?.name}
files={clinicData && clinicData?.companyDocuments}
onFileButtonClick={handleFileButtonClick}
/>
</Box>
</TabPanel>
<TabPanel className={classes.tabsPanel} value={2}>
<PlanDetails
companyData={companyData}
clinicData={clinicData}
reFetchData={fetchData}
/>
</TabPanel>
<TabPanel className={classes.tabsPanel} value={3}>
<RecruitmentAnalytics
totalJobPublishedCount={
companyData?.recruitmentAnalytics?.totalJobs
}
activeJobsCount={
companyData?.recruitmentAnalytics?.activeJobs
}
expiredJobsCount={
companyData?.recruitmentAnalytics?.closedJobs
}
jobsInDraftCount={
companyData?.recruitmentAnalytics?.draftJobs
}
/>
</TabPanel>
</TabContext>
</Grid>
</> */}
{/* ) : (
</>
) : (
<Box>
<FileEvaluate
companyStatus={companyData && companyData?.status}
companyName={companyData && companyData?.name}
files={companyData && companyData?.companyDocuments}
companyStatus={clinicData && clinicData?.status}
companyName={clinicData && clinicData?.name}
files={
clinicData && [
{
abn_doc: clinicData?.abn_doc,
abn_doc_is_verified: clinicFiles?.abn_doc_is_verified,
contract_doc: clinicData?.contract_doc,
contract_doc_is_verified:
clinicFiles?.contract_doc_is_verified,
logo_doc: clinicData?.logo,
logo_doc_is_verified: clinicFiles?.logo_is_verified,
},
]
}
onFileButtonClick={handleFileButtonClick}
/>
</Box>
)} */}
)}
</>
)}
{isShowReasonModel && (
<CustomModal
title={`Mark ${buttonClickStatus}`}
@ -465,15 +459,15 @@ function ClinicDetails() {
modalBodyFunction={() => (
<Box className={classes.reasonModelBox}>
<Typography>
{buttonClickStatus === 'Rejected'
? 'Please define a reason for rejecting this company.'
: 'Please define a reason for putting this company on hold.'}
{buttonClickStatus === "Rejected"
? "Please define a reason for rejecting this company."
: "Please define a reason for putting this company on hold."}
</Typography>
<Grid item xs={12} className={classes.inputTextGrid}>
<InputLabel className={classes.inputLabel}>
{buttonClickStatus === 'Rejected'
? 'Reason for Rejection'
: 'Reason for On Hold'}
{buttonClickStatus === "Rejected"
? "Reason for Rejection"
: "Reason for On Hold"}
</InputLabel>
<TextField
fullWidth
@ -492,7 +486,7 @@ function ClinicDetails() {
helperText={
formik.errors.reason && formik.touched.reason
? formik.errors.reason
: ''
: ""
}
/>
</Grid>

View File

@ -11,7 +11,7 @@ import FileDownloadIcon from '@mui/icons-material/FileDownload';
import { useStyles } from "./clinicListStyles";
import { CLINIC_STATUS, CLINIC_TYPE, PLAN_STATUS_TYPE } from "../../constants";
import { CLINIC_STATUS, CLINIC_TYPE, NOT_AVAILABLE_TEXT, PLAN_STATUS_TYPE } from "../../constants";
import { getClinics } from "../../services/clinics.service";
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import Table from "../../components/Table";
@ -115,14 +115,14 @@ const ClinicsList = () => {
enableColumnFilter: false,
enableSorting: true,
size: 100,
accessorKey: "updatedAt",
accessorKey: "update_time",
header: "Req.Raised On",
Cell: ({ row }) => (
<>
<div>
{row?.original?.requestRaisedOn
{row?.original?.update_time
? format(
new Date(row.original.requestRaisedOn),
new Date(row.original.update_time),
"dd MMM yyyy"
)
: NOT_AVAILABLE_TEXT}
@ -196,8 +196,8 @@ const ClinicsList = () => {
<Chip
className={classes.chipClass}
label={
status === CLINIC_STATUS.ON_HOLD ? (
"On Hold"
status === CLINIC_STATUS.UNDER_REVIEW.toLowerCase() ? (
"Under Review"
) : status === CLINIC_STATUS.NOT_REVIEWED ? (
"Approval Pending"
) : status ===
@ -208,7 +208,7 @@ const ClinicsList = () => {
Document Resubmitted
</div>
</div>
) : status === CLINIC_STATUS.REJECTED ? (
) : status === CLINIC_STATUS.REJECTED.toLowerCase() ? (
"Rejected"
) : (
""
@ -216,20 +216,20 @@ const ClinicsList = () => {
}
style={{
backgroundColor:
status === CLINIC_STATUS.ON_HOLD
status === CLINIC_STATUS.ON_HOLD.toLowerCase()
? theme.palette.orange.light
: status === CLINIC_STATUS.NOT_REVIEWED ||
status === CLINIC_STATUS.REJECTED
: status === CLINIC_STATUS.NOT_REVIEWED.toLowerCase() ||
status === CLINIC_STATUS.REJECTED.toLowerCase()
? theme.palette.primary.highlight
: status ===
CLINIC_STATUS.APPROVAL_PENDING_DOCUMENT_RESUBMITTED
? theme.palette.primary.highlight
: theme.palette.blue.light,
color:
status === CLINIC_STATUS.ON_HOLD
status === CLINIC_STATUS.ON_HOLD.toLowerCase()
? theme.palette.orange.main
: status === CLINIC_STATUS.NOT_REVIEWED ||
status === CLINIC_STATUS.REJECTED
: status === CLINIC_STATUS.NOT_REVIEWED.toLowerCase() ||
status === CLINIC_STATUS.REJECTED.toLowerCase()
? theme.palette.primary.main
: status ===
CLINIC_STATUS.APPROVAL_PENDING_DOCUMENT_RESUBMITTED
@ -406,9 +406,10 @@ const ClinicsList = () => {
type,
};
const resp = await getClinics(params);
console.log(resp)
return {
data: resp?.data?.data?.records,
rowCount: resp?.data?.data?.totalCount,
data: resp?.data?.data?.data?.clinics,
rowCount: resp?.data?.data?.total,
};
};
@ -444,7 +445,8 @@ const ClinicsList = () => {
},
{
label: "Clinic List",
path: "",
path: "/clinics",
query: { tab: queryParams.get('tab') || 'UNREGISTERED' }
},
];

View File

@ -42,7 +42,7 @@ const SuperAdminTotals = ({ isLoading, data }) => {
heading={`Approved`}
isLoading={isLoading}
viewAllClick={() => viewAllClick(false)}
value={rejected}
value={registered}
helperText={"Clinics"}
color={theme.palette.grey[52]}
/>
@ -52,7 +52,7 @@ const SuperAdminTotals = ({ isLoading, data }) => {
heading={`Rejected`}
isLoading={isLoading}
viewAllClick={() => viewAllClick(false)}
value={registered}
value={rejected}
helperText={"Clinics"}
color={theme.palette.grey[57]}
/>

View File

@ -5,6 +5,7 @@ import ProtectedComponent from '../../../components/ProtectedComponent';
import { getClinicsDashboardStatsById } from '../../../services/clinics.service';
import TotalNumber from '../components/TotalNumber';
import { useStyles } from '../dashboardStyles';
import { getDashboardStats } from '../../../services/dashboard.services';
const Totals = ({ data, setData }) => {
const classes = useStyles();
@ -19,12 +20,11 @@ const Totals = ({ data, setData }) => {
const fetchData = async () => {
try {
setIsLoading(true);
const response = {};
// const response = await getClinicsDashboardStatsById();
const response = await getDashboardStats();
const apiData = response?.data?.data || {};
setData({
activeJobs: apiData.recruitmentAnalytics?.activeJobs,
totalMaxJobPostings: apiData.totalMaxJobPostings,
activeJobs: apiData?.active,
totalMaxJobPostings: apiData.inactive,
});
} catch (error) {
console.error('Error fetching data:', error);

View File

@ -0,0 +1,205 @@
import React, { useState, useEffect } from "react";
import {
Box,
Typography,
TextField,
Button,
Paper,
Grid,
Snackbar,
Alert,
InputAdornment
} from "@mui/material";
import { useStyles } from "../dashboardStyles";
import { getSignupMasterPricing, setSignupMasterPricing } from "../../../services/dashboard.services";
const PaymentConfig = () => {
const classes = useStyles();
// Initial payment configuration state
const [paymentConfig, setPaymentConfig] = useState({
setup_fees: "0.00",
subscription_fees: "0.00",
per_call_charges: "0.00"
});
const fetchPaymentConfig = async () => {
try {
const response = await getSignupMasterPricing();
const apiData = response?.data?.data || {};
setPaymentConfig({
setup_fees: apiData?.setup_fees,
subscription_fees: apiData?.subscription_fees,
per_call_charges: apiData?.per_call_charges
});
} catch (error) {
console.error("Error fetching payment configuration:", error);
}
};
useEffect(() => {
fetchPaymentConfig();
}, []);
// State for tracking if form has been edited
const [isEdited, setIsEdited] = useState(false);
// State for notification
const [notification, setNotification] = useState({
open: false,
message: "",
severity: "success"
});
// Handle input change
const handleInputChange = (e) => {
const { name, value } = e.target;
// Allow empty values, numbers, and decimal points with up to 2 decimal places
// This regex allows for more flexible input including empty string and partial numbers
if (value !== "" && !/^\d*(\.\d{0,2})?$/.test(value)) {
return;
}
setPaymentConfig({
...paymentConfig,
[name]: value
});
setIsEdited(true);
};
// Handle save configuration
const handleSaveConfig = async () => {
try {
const response = await setSignupMasterPricing(paymentConfig);
const apiData = response?.data?.data || {};
setPaymentConfig({
setup_fees: apiData?.setup_fees,
subscription_fees: apiData?.subscription_fees,
per_call_charges: apiData?.per_call_charges
});
} catch (error) {
console.error("Error fetching payment configuration:", error);
}
setNotification({
open: true,
message: "Payment configuration saved successfully",
severity: "success"
});
setIsEdited(false);
// In a real implementation, you would save this data to your backend
console.log("Saving payment configuration:", paymentConfig);
};
// Handle notification close
const handleCloseNotification = () => {
setNotification({
...notification,
open: false
});
};
return (
<Paper elevation={3} sx={{ p: 3, mb: 4, borderRadius: 2 }}>
<Box mb={2}>
<Typography variant="h6" fontWeight="bold">
Payment Configuration
</Typography>
<Typography variant="body2" color="text.secondary">
Configure payment details for new clinic registrations
</Typography>
</Box>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<TextField
fullWidth
label="Setup Fee"
name="setup_fees"
value={paymentConfig.setup_fees}
onChange={handleInputChange}
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
}}
variant="outlined"
/>
</Grid>
<Grid item xs={12} md={4}>
<TextField
fullWidth
label="Subscription Fee (Monthly)"
name="subscription_fees"
value={paymentConfig.subscription_fees}
onChange={handleInputChange}
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
}}
variant="outlined"
/>
</Grid>
<Grid item xs={12} md={4}>
<TextField
fullWidth
label="Per Call Charges"
name="per_call_charges"
value={paymentConfig.per_call_charges}
onChange={handleInputChange}
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
endAdornment: <InputAdornment position="end">/call</InputAdornment>,
}}
variant="outlined"
/>
</Grid>
</Grid>
<Box mt={3} display="flex" justifyContent="flex-end">
<Button
variant="contained"
color="primary"
onClick={handleSaveConfig}
disabled={!isEdited}
sx={{
borderRadius: 50,
px: 3,
textTransform: "none",
backgroundColor: "#ff3366",
"&:hover": {
backgroundColor: "#e61653",
},
"&:disabled": {
backgroundColor: "#ffb3c3",
color: "#ffffff"
}
}}
>
Save Configuration
</Button>
</Box>
{/* Notification */}
<Snackbar
open={notification.open}
autoHideDuration={6000}
onClose={handleCloseNotification}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert
onClose={handleCloseNotification}
severity={notification.severity}
sx={{ width: "100%" }}
>
{notification.message}
</Alert>
</Snackbar>
</Paper>
);
};
export default PaymentConfig;

View File

@ -3,6 +3,8 @@ import { Box } from "@mui/system";
import React from "react";
import { useStyles } from "../dashboardStyles";
import SuperAdminTotals from "../Tiles/SuperAdminTotals";
import PaymentConfig from "./PaymentConfig";
import { getDashboardStats } from "../../../services/dashboard.services";
const SuperAdmin = () => {
const classes = useStyles();
@ -17,22 +19,14 @@ const SuperAdmin = () => {
const fetchDashboardStats = async () => {
try {
setIsLoading(true);
// const response = await getAdminDashboardStats();
// const apiData = response?.data?.data || {};
// setData({
// totalAccounts: apiData?.dashboardStatistics?.totalAccounts,
// registrationRequest: apiData?.dashboardStatistics?.registrationRequest,
// rejected: apiData?.dashboardStatistics?.rejected,
// registered: apiData?.dashboardStatistics?.registered,
// });
setTimeout(() => {
const response = await getDashboardStats();
const apiData = response?.data?.data || {};
setData({
totalAccounts: 10,
registrationRequest: 5,
rejected: 2,
registered: 8,
totalAccounts: apiData?.totalClinics,
registrationRequest: apiData?.totalUnderReviewClinics,
rejected: apiData?.totalRejectedClinics,
registered: apiData?.totalActiveClinics,
});
}, 1000);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
@ -53,6 +47,11 @@ const SuperAdmin = () => {
<SuperAdminTotals isLoading={isLoading} data={data} />
</Box>
</Box>
{/* Payment Configuration Section */}
<Box mt={4}>
<PaymentConfig />
</Box>
</Box>
);
};

View File

@ -2,15 +2,12 @@ import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import Loader from "../components/Loader";
import ThankYou from "../ThankYou/";
import { Box, Typography } from '@mui/material';
import { Box, Typography } from "@mui/material";
import SuperAdmin from "./components/SuperAdmin";
import {
selectUserRole,
setUserRole,
USER_ROLES,
} from "../../redux/userRoleSlice";
import { selectUserRole, setUserRole } from "../../redux/userRoleSlice";
import Totals from "./Tiles/Totals";
import { useStyles } from './dashboardStyles';
import { useStyles } from "./dashboardStyles";
import { USER_ROLES } from "../../constants";
function Dashboard() {
const classes = useStyles();
@ -36,12 +33,10 @@ function Dashboard() {
useEffect(() => {
// Determine user role based on user data from login reducer
if (user) {
// Check if user is a super admin
// This logic can be adjusted based on your specific role criteria
const isSuperAdmin = user.isBsAdmin ||
(user.isAdmin && user.permissions?.includes("SUPER_ADMIN_PERMISSION"));
// Set the appropriate role in Redux
const isSuperAdmin =
user.userType == USER_ROLES.SUPER_ADMIN.toLowerCase();
if (isSuperAdmin) {
dispatch(setUserRole(USER_ROLES.SUPER_ADMIN));
} else {
@ -61,41 +56,15 @@ function Dashboard() {
<Box>
{/* <ThankYou /> */}
<Box>
<Box>
<Box className={classes.dashboardTitleBox}>
<Typography variant="h4" className={classes.dashboardTitle}>
Dashboard
</Typography>
</Box>
<Totals data={data} setData={setData} />
<Box>
<Box className={classes.dashboardTitleBox}>
<Typography variant="h4" className={classes.dashboardTitle}>
Dashboard
</Typography>
</Box>
{/* <Grid
container
spacing={2.6}
className={classes.jobApplicationsAndManageSubscription}
>
<Grid
item
style={{ width: '-webkit-fill-available' }}
lg={`${hasSubscriptionManagementPermission ? 6 : 12}`}
>
<JobApplications
onJobPostClick={() => setPostAJobModal(true)}
isJobPostAvailable={data?.remainingJobPosts > 0}
/>
</Grid>
{hasSubscriptionManagementPermission && (
<Grid style={{ width: '-webkit-fill-available' }} item lg={6}>
<ManageSubscription data={data} />
</Grid>
)}
{postAJobModal && (
<CreateJobPostModal
onClose={() => setPostAJobModal(false)}
></CreateJobPostModal>
)}
</Grid> */}
<Totals data={data} setData={setData} />
</Box>
</Box>
</Box>
);

View File

@ -14,7 +14,7 @@ import React, { useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import * as Yup from 'yup';
import { messaging } from '../../config/firebase';
import { initializeMessaging } from '../../config/firebase';
import { NOTIFICATION } from '../../constants';
import { pushNotification } from '../../utils/notification';
import {
@ -64,9 +64,6 @@ function LoginForm() {
// Show success notification
pushNotification('Login successful', NOTIFICATION.SUCCESS);
formik.setSubmitting(false);
// Navigate to dashboard immediately without waiting for profile
navigate('/');
} catch (error) {

View File

@ -1,106 +1,106 @@
import { axiosInstance } from '../../config/api';
import { LOGIN, PROFILE } from './loginActionTypes';
// export const login = (data) => ({
// type: LOGIN,
// payload: axiosInstance.post('/auth/companies/login', data),
// });
export const login = (data) => ({
type: LOGIN,
payload: axiosInstance.post('/auth/login', data),
});
// export const profile = () => ({
// type: PROFILE,
// payload: axiosInstance.get('/me'),
// });
export const profile = () => ({
type: PROFILE,
payload: axiosInstance.get('/users/me'),
});
export const login = (data) => {
// Determine user type based on email
const isSuperAdmin = data.email === 'admin@gmail.com';
const userRole = isSuperAdmin ? 'SUPER_ADMIN' : 'CLINIC_ADMIN';
const userId = isSuperAdmin ? 'super-admin-123' : 'clinic-admin-123';
// export const login = (data) => {
// // Determine user type based on email
// const isSuperAdmin = data.email === 'admin@gmail.com';
// const userRole = isSuperAdmin ? 'SUPER_ADMIN' : 'CLINIC_ADMIN';
// const userId = isSuperAdmin ? 'super-admin-123' : 'clinic-admin-123';
// Store token and user type in localStorage to simulate a real login
localStorage.setItem('token', `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`);
localStorage.setItem('userType', userRole);
// // Store token and user type in localStorage to simulate a real login
// localStorage.setItem('token', `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`);
// localStorage.setItem('userType', userRole);
// Mock successful login response
const mockLoginResponse = {
data: {
message: 'Login successful',
token: `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`,
user: {
id: userId,
email: data.email,
role: userRole
}
}
};
// // Mock successful login response
// const mockLoginResponse = {
// data: {
// message: 'Login successful',
// token: `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`,
// user: {
// id: userId,
// email: data.email,
// role: userRole
// }
// }
// };
return {
type: LOGIN,
// Use a real Promise with a slight delay to simulate network request
payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300))
};
};
// return {
// type: LOGIN,
// // Use a real Promise with a slight delay to simulate network request
// payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300))
// };
// };
export const profile = () => {
// Get user type from localStorage to determine which profile to return
const userType = localStorage.getItem('userType') || 'CLINIC_ADMIN';
const isSuperAdmin = userType === 'SUPER_ADMIN';
// export const profile = () => {
// // Get user type from localStorage to determine which profile to return
// const userType = localStorage.getItem('userType') || 'CLINIC_ADMIN';
// const isSuperAdmin = userType === 'SUPER_ADMIN';
// Create appropriate mock profile based on user type
let profileData;
// // Create appropriate mock profile based on user type
// let profileData;
if (isSuperAdmin) {
// Super Admin profile
profileData = {
id: 'super-admin-123',
name: 'Super Administrator',
email: 'admin@gmail.com',
role: 'SUPER_ADMIN',
isAdmin: true,
isBsAdmin: true, // This flag identifies super admin
permissions: [
'CREATE_REC_USERS',
'READ_REC_USERS',
'DELETE_REC_USERS',
'SUPER_ADMIN_PERMISSION'
]
};
} else {
// Clinic Admin profile
profileData = {
id: 'clinic-admin-123',
name: 'Clinic Administrator',
email: 'admin@clinic.com',
role: 'CLINIC_ADMIN',
isAdmin: true,
isBsAdmin: false,
permissions: [
'CREATE_REC_USERS',
'READ_REC_USERS',
'DELETE_REC_USERS'
],
clinic: {
id: 'clinic-123',
name: 'Health Plus Clinic',
address: '123 Medical Center Blvd',
city: 'Healthcare City',
state: 'HC',
zipCode: '12345',
phone: '555-123-4567'
}
};
}
// if (isSuperAdmin) {
// // Super Admin profile
// profileData = {
// id: 'super-admin-123',
// name: 'Super Administrator',
// email: 'admin@gmail.com',
// role: 'SUPER_ADMIN',
// isAdmin: true,
// isSuperAdmin: true, // This flag identifies super admin
// permissions: [
// 'CREATE_REC_USERS',
// 'READ_REC_USERS',
// 'DELETE_REC_USERS',
// 'SUPER_ADMIN_PERMISSION'
// ]
// };
// } else {
// // Clinic Admin profile
// profileData = {
// id: 'clinic-admin-123',
// name: 'Clinic Administrator',
// email: 'admin@clinic.com',
// role: 'CLINIC_ADMIN',
// isAdmin: true,
// isSuperAdmin: false,
// permissions: [
// 'CREATE_REC_USERS',
// 'READ_REC_USERS',
// 'DELETE_REC_USERS'
// ],
// clinic: {
// id: 'clinic-123',
// name: 'Health Plus Clinic',
// address: '123 Medical Center Blvd',
// city: 'Healthcare City',
// state: 'HC',
// zipCode: '12345',
// phone: '555-123-4567'
// }
// };
// }
// Create the response with the nested data structure the reducer expects
const mockProfileResponse = {
data: {
data: profileData
}
};
// // Create the response with the nested data structure the reducer expects
// const mockProfileResponse = {
// data: {
// data: profileData
// }
// };
return {
type: PROFILE,
// Use a real Promise with a slight delay to simulate network request
payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300))
};
};
// return {
// type: PROFILE,
// // Use a real Promise with a slight delay to simulate network request
// payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300))
// };
// };

View File

@ -9,11 +9,13 @@ import {
} from './loginActionTypes';
const initialState = {};
const loginPending = (state) => ({
...state,
});
const loginFulfilled = (state) => ({
const loginFulfilled = (state, payload) => ({
...state,
token: payload?.payload?.data?.data,
});
const loginRejected = (state) => ({
...state,
@ -39,7 +41,7 @@ const profileFulfilled = (state, payload) => {
);
});
// Check if user is admin, add additional permissions
if (user.isAdmin && !user?.isBsAdmin) {
if (user.isAdmin && !user?.isSuperAdmin) {
userPermissions.push(
'CREATE_REC_USERS',
'READ_REC_USERS',

View File

@ -1,9 +1,9 @@
import AddIcon from '@mui/icons-material/Add';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import CloseIcon from '@mui/icons-material/Close';
import PersonAddIcon from '@mui/icons-material/PersonAdd';
import SearchIcon from '@mui/icons-material/Search';
import AddIcon from "@mui/icons-material/Add";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import CloseIcon from "@mui/icons-material/Close";
import PersonAddIcon from "@mui/icons-material/PersonAdd";
import SearchIcon from "@mui/icons-material/Search";
import {
Alert,
Box,
@ -26,329 +26,328 @@ import {
TableRow,
TextField,
Typography,
} from '@mui/material';
import React, { useState } from 'react';
import CustomBreadcrumbs from '../../components/CustomBreadcrumbs';
import PageHeader from '../../components/PageHeader';
} from "@mui/material";
import React, { useState } from "react";
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import PageHeader from "../../components/PageHeader";
const MasterDataManagement = () => {
// State for form fields
const [appointmentType, setAppointmentType] = useState('');
const queryParams = new URLSearchParams(location.search);
// State for form fields
const [appointmentType, setAppointmentType] = useState("");
const queryParams = new URLSearchParams(location.search);
// State for staff list
const [staffList, setStaffList] = useState([]);
// State for staff list
const [staffList, setStaffList] = useState([]);
// State for dialog
const [openDialog, setOpenDialog] = useState(false);
// State for staff dialog
const [openDialog, setOpenDialog] = useState(false);
// State for search
const [searchQuery, setSearchQuery] = useState('');
// State for search
const [searchQuery, setSearchQuery] = useState("");
// State for pagination
const [page, setPage] = useState(1);
const rowsPerPage = 10;
// State for pagination
const [page, setPage] = useState(1);
const rowsPerPage = 10;
// State for notification
const [notification, setNotification] = useState({
open: false,
message: '',
severity: 'success',
// State for notification
const [notification, setNotification] = useState({
open: false,
message: "",
severity: "success",
});
// Handle staff dialog open/close
const handleOpenDialog = () => {
setOpenDialog(true);
};
const handleCloseDialog = () => {
setOpenDialog(false);
// Clear form
setAppointmentType("");
};
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
// Add new staff member
const newStaff = {
id: staffList.length + 1,
appointmentType,
};
setStaffList([...staffList, newStaff]);
// Close dialog
handleCloseDialog();
// Show success notification
setNotification({
open: true,
message: "Staff member added successfully!",
severity: "success",
});
};
// Handle dialog open/close
const handleOpenDialog = () => {
setOpenDialog(true);
};
// Handle notification close
const handleCloseNotification = () => {
setNotification({
...notification,
open: false,
});
};
const handleCloseDialog = () => {
setOpenDialog(false);
// Clear form
setAppointmentType('');
};
// ...................breadcrumbs array........................
const breadcrumbs = [
{
label: "Dashboard",
path: "/",
},
{
label: "Master Data Management",
path: "/masterData",
},
];
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<PageHeader pageTitle="Master Data Management" hideAddButton />
// Add new staff member
const newStaff = {
id: staffList.length + 1,
appointmentType,
};
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
setStaffList([...staffList, newStaff]);
<Paper elevation={3} sx={{ p: 0, mb: 4, overflow: "hidden" }}>
{/* Staff List Header with Add Button */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
p: 3,
}}
>
<Typography variant="h6" component="h2" sx={{ fontWeight: "bold" }}>
Master Appointment Type List
</Typography>
// Close dialog
handleCloseDialog();
// Show success notification
setNotification({
open: true,
message: 'Staff member added successfully!',
severity: 'success',
});
};
// Handle notification close
const handleCloseNotification = () => {
setNotification({
...notification,
open: false,
});
};
// ...................breadcrumbs array........................
const breadcrumbs = [
{
label: 'Dashboard',
path: '/',
},
{
label: 'Master Data Management',
path: '/masterData',
},
];
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<PageHeader
pageTitle="Master Data Management"
hideAddButton
/>
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
<Paper elevation={3} sx={{ p: 0, mb: 4, overflow: 'hidden' }}>
{/* Staff List Header with Add Button */}
<Box
<Button
variant="contained"
color="error"
startIcon={<AddIcon />}
onClick={handleOpenDialog}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
p: 3,
borderRadius: 50,
textTransform: "none",
backgroundColor: "#ff3366",
"&:hover": {
backgroundColor: "#e61653",
},
}}
>
<Typography variant="h6" component="h2" sx={{ fontWeight: 'bold' }}>
Master Appointment Type List
</Typography>
Add Appointment Type
</Button>
</Box>
{/* Search Box */}
<Box sx={{ px: 3, pb: 2 }}>
<TextField
placeholder="Search Appointment Type here"
fullWidth
size="small"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
}}
sx={{
backgroundColor: "#fff",
"& .MuiOutlinedInput-root": {
borderRadius: 2,
},
}}
/>
</Box>
{/* Staff List Table */}
<TableContainer>
<Table>
<TableHead>
<TableRow sx={{ backgroundColor: "#f5f5f5" }}>
<TableCell sx={{ fontWeight: "bold" }}>Sr. No.</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>
Appointment Type
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{staffList.length > 0 ? (
staffList
.filter((staff) =>
`${staff.appointmentType}`
.toLowerCase()
.includes(searchQuery.toLowerCase())
)
.slice((page - 1) * rowsPerPage, page * rowsPerPage)
.map((staff, index) => (
<TableRow key={staff.id}>
<TableCell>
{(page - 1) * rowsPerPage + index + 1}
</TableCell>
<TableCell>{staff.appointmentType}</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={2}
align="center"
sx={{ py: 5, color: "#666" }}
>
No Appointment Type added yet
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination */}
{staffList.length > 0 && (
<Box
sx={{
display: "flex",
justifyContent: "center",
p: 2,
borderTop: "1px solid #eee",
}}
>
<Button
onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
disabled={page === 1}
startIcon={<ArrowBackIosNewIcon fontSize="small" />}
sx={{ mx: 1, color: "#666" }}
>
Previous
</Button>
<Button
variant="contained"
color="error"
startIcon={<AddIcon />}
onClick={handleOpenDialog}
disableElevation
sx={{
borderRadius: 50,
textTransform: 'none',
backgroundColor: '#ff3366',
'&:hover': {
backgroundColor: '#e61653',
mx: 1,
minWidth: "36px",
backgroundColor: "#f0f0f0",
color: "#333",
"&:hover": {
backgroundColor: "#e0e0e0",
},
}}
>
Add Appointment Type
{page}
</Button>
<Button
onClick={() => setPage((prev) => prev + 1)}
disabled={page * rowsPerPage >= staffList.length}
endIcon={<ArrowForwardIosIcon fontSize="small" />}
sx={{ mx: 1, color: "#666" }}
>
Next
</Button>
</Box>
)}
</Paper>
{/* Search Box */}
<Box sx={{ px: 3, pb: 2 }}>
{/* Add Staff Dialog */}
<Dialog
open={openDialog}
onClose={handleCloseDialog}
maxWidth="sm"
fullWidth
>
<DialogTitle
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
pb: 1,
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<PersonAddIcon sx={{ mr: 1, color: "#0a2d6b" }} />
<Typography
variant="h6"
component="span"
sx={{ fontWeight: "bold", color: "#0a2d6b" }}
>
Add New Appointment Type
</Typography>
</Box>
<IconButton onClick={handleCloseDialog} size="small">
<CloseIcon />
</IconButton>
</DialogTitle>
<Divider />
<DialogContent>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
placeholder="Search Appointment Type here"
label="Appointment Type"
fullWidth
size="small"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
}}
sx={{
backgroundColor: '#fff',
'& .MuiOutlinedInput-root': {
borderRadius: 2,
},
margin="normal"
value={appointmentType}
onChange={(e) => setAppointmentType(e.target.value)}
placeholder="Appointment Type"
required
InputLabelProps={{
shrink: true,
}}
/>
</Box>
</DialogContent>
{/* Staff List Table */}
<TableContainer>
<Table>
<TableHead>
<TableRow sx={{ backgroundColor: '#f5f5f5' }}>
<TableCell sx={{ fontWeight: 'bold' }}>Sr. No.</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Appointment Type</TableCell>
</TableRow>
</TableHead>
<TableBody>
{staffList.length > 0 ? (
staffList
.filter(
(staff) =>
`${staff.appointmentType}`
.toLowerCase()
.includes(searchQuery.toLowerCase())
)
.slice((page - 1) * rowsPerPage, page * rowsPerPage)
.map((staff, index) => (
<TableRow key={staff.id}>
<TableCell>
{(page - 1) * rowsPerPage + index + 1}
</TableCell>
<TableCell>{staff.appointmentType}</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={2}
align="center"
sx={{ py: 5, color: '#666' }}
>
No Appointment Type added yet
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination */}
{staffList.length > 0 && (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
p: 2,
borderTop: '1px solid #eee',
}}
>
<Button
onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
disabled={page === 1}
startIcon={<ArrowBackIosNewIcon fontSize="small" />}
sx={{ mx: 1, color: '#666' }}
>
Previous
</Button>
<Button
variant="contained"
disableElevation
sx={{
mx: 1,
minWidth: '36px',
backgroundColor: '#f0f0f0',
color: '#333',
'&:hover': {
backgroundColor: '#e0e0e0',
},
}}
>
{page}
</Button>
<Button
onClick={() => setPage((prev) => prev + 1)}
disabled={page * rowsPerPage >= staffList.length}
endIcon={<ArrowForwardIosIcon fontSize="small" />}
sx={{ mx: 1, color: '#666' }}
>
Next
</Button>
</Box>
)}
</Paper>
{/* Add Staff Dialog */}
<Dialog
open={openDialog}
onClose={handleCloseDialog}
maxWidth="sm"
fullWidth
>
<DialogTitle
<DialogActions sx={{ px: 3, pb: 3 }}>
<Button
variant="contained"
color="error"
fullWidth
onClick={handleSubmit}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
pb: 1,
py: 1.5,
backgroundColor: "#ff3366",
"&:hover": {
backgroundColor: "#e61653",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<PersonAddIcon sx={{ mr: 1, color: '#0a2d6b' }} />
<Typography
variant="h6"
component="span"
sx={{ fontWeight: 'bold', color: '#0a2d6b' }}
>
Add New Appointment Type
</Typography>
</Box>
<IconButton onClick={handleCloseDialog} size="small">
<CloseIcon />
</IconButton>
</DialogTitle>
Add Appointment Type
</Button>
</DialogActions>
</Dialog>
<Divider />
<DialogContent>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
label="Appointment Type"
fullWidth
margin="normal"
value={appointmentType}
onChange={(e) => setAppointmentType(e.target.value)}
placeholder="Appointment Type"
required
InputLabelProps={{
shrink: true,
}}
/>
</Box>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 3 }}>
<Button
variant="contained"
color="error"
fullWidth
onClick={handleSubmit}
sx={{
py: 1.5,
backgroundColor: '#ff3366',
'&:hover': {
backgroundColor: '#e61653',
},
}}
>
Add Appointment Type
</Button>
</DialogActions>
</Dialog>
{/* Notification */}
<Snackbar
open={notification.open}
autoHideDuration={4000}
{/* Notification */}
<Snackbar
open={notification.open}
autoHideDuration={4000}
onClose={handleCloseNotification}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert
onClose={handleCloseNotification}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
severity={notification.severity}
sx={{ width: "100%" }}
>
<Alert
onClose={handleCloseNotification}
severity={notification.severity}
sx={{ width: '100%' }}
>
{notification.message}
</Alert>
</Snackbar>
</Container>
);
{notification.message}
</Alert>
</Snackbar>
</Container>
);
};
export default MasterDataManagement;

View File

@ -228,58 +228,6 @@ const PaymentPage = () => {
}
/>
{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

View File

@ -1,6 +1,6 @@
import { getToken } from 'firebase/messaging';
import { RECRUITMENT_URL, FB_VAPID_KEY } from '../../common/envVariables';
import { messaging } from '../../config/firebase';
import { initializeMessaging, getMessagingInstance } from '../../config/firebase';
import { saveFcmToken } from '../../services/notification.service';
import { NOTIFICATION_TYPE } from './notificationConstant';
@ -10,12 +10,18 @@ export const requestNotificationPermission = async () => {
return;
}
if (!messaging) return;
try {
const currentToken = await getToken(messaging, {
// Initialize messaging if not already initialized
const messagingInstance = await initializeMessaging();
if (!messagingInstance) {
console.log('Firebase messaging is not supported in this browser');
return;
}
const currentToken = await getToken(messagingInstance, {
vapidKey: FB_VAPID_KEY,
});
if (currentToken) {
await saveFcmToken({
token: currentToken,

View File

@ -0,0 +1,330 @@
import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close";
import {
Box,
Button,
Checkbox,
Container,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControlLabel,
IconButton,
MenuItem,
Paper,
Snackbar,
Alert,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
} from "@mui/material";
import React, { useState } from "react";
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import PageHeader from "../../components/PageHeader";
const PaymentManagement = () => {
// State for payment dialog
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
const [paymentData, setPaymentData] = useState({
clinicName: "",
setupFeeWaived: false,
specialOffer: false,
configurationMonth: 3
});
// State for payments list
const [paymentsList, setPaymentsList] = useState([]);
const [editPaymentIndex, setEditPaymentIndex] = useState(null);
// State for notification
const [notification, setNotification] = useState({
open: false,
message: "",
severity: "success",
});
// Handle payment dialog submission
const handleAddPayment = () => {
// Validate input
if (!paymentData.clinicName.trim()) {
setNotification({
open: true,
message: "Please enter a clinic name",
severity: "error",
});
return;
}
if (editPaymentIndex !== null) {
// Update existing payment
const updatedPayments = [...paymentsList];
updatedPayments[editPaymentIndex] = {...paymentData};
setPaymentsList(updatedPayments);
setNotification({
open: true,
message: "Payment configuration updated successfully",
severity: "success",
});
} else {
// Add new payment
setPaymentsList([...paymentsList, {...paymentData}]);
setNotification({
open: true,
message: "Payment configuration added successfully",
severity: "success",
});
}
// Reset form and close dialog
setPaymentData({
clinicName: "",
setupFeeWaived: false,
specialOffer: false,
configurationMonth: 3
});
setEditPaymentIndex(null);
setPaymentDialogOpen(false);
};
// Handle edit payment
const handleEditPayment = (index) => {
setEditPaymentIndex(index);
setPaymentData({...paymentsList[index]});
setPaymentDialogOpen(true);
};
// Handle delete payment
const handleDeletePayment = (index) => {
const updatedPayments = [...paymentsList];
updatedPayments.splice(index, 1);
setPaymentsList(updatedPayments);
setNotification({
open: true,
message: "Payment configuration deleted successfully",
severity: "success",
});
};
// Handle notification close
const handleCloseNotification = () => {
setNotification({
...notification,
open: false,
});
};
// Breadcrumbs
const breadcrumbs = [
{ label: "Dashboard", link: "/" },
{ label: "Payment Management", link: "/payment-management" },
];
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<PageHeader pageTitle="Payment Management" hideAddButton />
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
<Paper elevation={3} sx={{ p: 0, mb: 4, overflow: "hidden" }}>
{/* Payment Management Header with Add Button */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
p: 3,
}}
>
<Typography variant="h6" component="h2" sx={{ fontWeight: "bold" }}>
Payment Configurations
</Typography>
<Button
variant="contained"
color="error"
startIcon={<AddIcon />}
onClick={() => setPaymentDialogOpen(true)}
sx={{
borderRadius: 50,
textTransform: "none",
backgroundColor: "#ff3366",
"&:hover": {
backgroundColor: "#e61653",
},
}}
>
Add Payment
</Button>
</Box>
{/* Payment List Table */}
<TableContainer>
<Table sx={{ minWidth: 650 }} aria-label="payment table">
<TableHead sx={{ backgroundColor: "#f5f5f5" }}>
<TableRow>
<TableCell sx={{ fontWeight: "bold" }}>Clinic Name</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Setup Fee Waived</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Special Offer</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Configuration Month</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paymentsList.length > 0 ? (
paymentsList.map((payment, index) => (
<TableRow key={index}>
<TableCell>{payment.clinicName}</TableCell>
<TableCell>{payment.setupFeeWaived ? "Yes" : "No"}</TableCell>
<TableCell>{payment.specialOffer ? "Yes" : "No"}</TableCell>
<TableCell>
{payment.specialOffer ? payment.configurationMonth : "-"}
</TableCell>
<TableCell>
<Button
size="small"
color="primary"
onClick={() => handleEditPayment(index)}
>
Edit
</Button>
<Button
size="small"
color="error"
onClick={() => handleDeletePayment(index)}
>
Delete
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={5} align="center">
No payment configurations found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
{/* Payment Dialog */}
<Dialog
open={paymentDialogOpen}
onClose={() => setPaymentDialogOpen(false)}
maxWidth="sm"
fullWidth
>
<DialogTitle>
{editPaymentIndex !== null ? "Edit Payment" : "Add New Payment"}
<IconButton
aria-label="close"
onClick={() => setPaymentDialogOpen(false)}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box sx={{ mt: 2 }}>
<TextField
autoFocus
margin="dense"
id="clinicName"
label="Clinic Name"
type="text"
fullWidth
variant="outlined"
value={paymentData.clinicName}
onChange={(e) => setPaymentData({...paymentData, clinicName: e.target.value})}
sx={{ mb: 2 }}
/>
<FormControlLabel
control={
<Checkbox
checked={paymentData.setupFeeWaived}
onChange={(e) => setPaymentData({...paymentData, setupFeeWaived: e.target.checked})}
/>
}
label="Setup fee waived"
sx={{ mb: 1 }}
/>
<FormControlLabel
control={
<Checkbox
checked={paymentData.specialOffer}
onChange={(e) => setPaymentData({...paymentData, specialOffer: e.target.checked})}
/>
}
label="Special Offer: First 3 months free"
sx={{ mb: 1 }}
/>
{paymentData.specialOffer && (
<TextField
select
margin="dense"
id="configurationMonth"
label="Configuration Month"
fullWidth
variant="outlined"
value={paymentData.configurationMonth}
onChange={(e) => setPaymentData({...paymentData, configurationMonth: e.target.value})}
sx={{ mt: 1 }}
>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((month) => (
<MenuItem key={month} value={month}>
{month}
</MenuItem>
))}
</TextField>
)}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setPaymentDialogOpen(false)} color="primary">
Cancel
</Button>
<Button onClick={handleAddPayment} variant="contained" color="primary">
Save
</Button>
</DialogActions>
</Dialog>
{/* Notification Snackbar */}
<Snackbar
open={notification.open}
autoHideDuration={6000}
onClose={handleCloseNotification}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert
onClose={handleCloseNotification}
severity={notification.severity}
sx={{ width: "100%" }}
>
{notification.message}
</Alert>
</Snackbar>
</Container>
);
};
export default PaymentManagement;

View File

@ -18,6 +18,7 @@ import {
Select,
TextField,
Toolbar,
Tooltip,
Typography,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
@ -54,7 +55,11 @@ import {
ONLY_ALPHA_NUMERIC_ACCEPT_REGEX,
WEBSITE_REGEX,
} from "../../constants";
import { fileUpload } from "../../services/file.upload.services";
import {
fileUpload,
getPresignedUrl,
uploadToS3,
} from "../../services/file.upload.services";
import { pushNotification } from "../../utils/notification";
import {
passwordLengthRegex,
@ -64,30 +69,44 @@ import {
} from "../../utils/regex";
import { capitalizeAllLetters, capitalizeFirstLetter } from "../../utils/share";
import PasswordValidation from "../Login/component/PasswordValidation";
import { updateFormDetails } from "./signupAction";
import { resetFormData, updateFormDetails } from "./signupAction";
import { useStyles } from "./styles/signupStyles";
import { getLatestClinicId } from "../../services/clinics.service";
import { signup } from "../../services/auth.services";
import { decodeJWT } from "../../services/jwt.services";
function YourDetailsForm() {
const classes = useStyles();
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
const [latestClinicID, setLatestClinicID] = useState(0);
const [otpField, setOtpField] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [billingUsers, setBillingUsers] = useState([]);
const [drawerOpen, setDrawerOpen] = useState(false);
const [localityOption, setLocalityOption] = useState([]);
const [countryOption, setCountryOption] = useState(["Australia", "India"]);
const [pincodeData, setPincodeData] = useState();
const selectedLocalityRef = useRef();
const [testConnection, setTestConnDone] = useState(false);
useEffect(() => {
setIsLoading(true);
getLatestClinicId().then((res) => {
setLatestClinicID(res?.data?.data);
});
setIsLoading(false);
}, []);
// Form data from Redux
const yourDetailsFormData = useSelector((state) => state.signup);
// Logo state variables
const logoRef = useRef(null);
const testConnectionRef = useRef(null);
const otpButtonRef = useRef(null);
const fileInputRef = useRef(null);
const [aspect, setAspect] = useState(1);
const [logoname, setLogoname] = useState();
@ -109,7 +128,10 @@ function YourDetailsForm() {
{ id: "91", name: "+91" },
];
const integrationOptions = ["BP Software", "Medical Director"];
const integrationOptions = [
{ id: "bp", name: "BP Software" },
{ id: "medical_director", name: "Medical Director" },
];
// Default form data
const defaultFormData = useRef({
@ -120,26 +142,25 @@ function YourDetailsForm() {
mobileNumber: "",
mobilePrefix: "",
confirmPassword: "",
currentEmail: "",
otp: "",
// Clinic details
companyName: "",
designation: "",
businessPhonePrefix: "",
businessPhone: "",
emergencyBusinessPhone: "",
emergencyBusinessPhonePrefix: "",
businessPhonePrefix: "",
businessFax: "",
companyLogo: "",
companyIndustry: "",
clinicLogo: "",
businessEmail: "",
pincode: "",
state: "",
locality: "",
country: "",
fullAddress: "",
companyPANImage: "",
companyPANImage: "",
companyABNImageNumber: "",
companyABNImage: "",
termsAccepted: "",
practiceManagementSystem: "",
practiceId: "",
@ -167,18 +188,18 @@ function YourDetailsForm() {
emergencyBusinessPhone: useRef(null),
emergencyBusinessPhonePrefix: useRef(null),
businessFax: useRef(null),
businessEmail: useRef(null),
practiceManagementSystem: useRef(null),
practiceId: useRef(null),
practiceName: useRef(null),
designation: useRef(null),
companyIndustry: useRef(null),
pincode: useRef(null),
state: useRef(null),
locality: useRef(null),
country: useRef(null),
fullAddress: useRef(null),
companyPANImage: useRef(null),
companyTANNumber: useRef(null),
companyABNImageNumber: useRef(null),
companyABNImage: useRef(null),
// contract management
contract: useRef(null),
@ -186,14 +207,10 @@ function YourDetailsForm() {
if (yourDetailsFormData) {
defaultFormData.current = { ...yourDetailsFormData };
// setLocalityOption([yourDetailsFormData?.locality])
selectedLocalityRef.current = yourDetailsFormData?.locality;
}
useEffect(() => {
if (yourDetailsFormData) {
setBillingUsers(yourDetailsFormData?.billingUsers);
}
}, [yourDetailsFormData]);
const validationSchema = Yup.object().shape({
// Personal details validation
name: Yup.string().required("Name is required"),
@ -206,6 +223,7 @@ function YourDetailsForm() {
.matches(/^[0-9]+$/, "Mobile Number must be a valid number")
.min(10, "Mobile number should have 10 digits only.")
.max(10, "Mobile number should have 10 digits only."),
mobilePrefix: Yup.string().required("Mobile Prefix is required"),
password: Yup.string()
.matches(passwordLengthRegex, "Password must be at least 8 characters")
.matches(passwordLetterRegex, "Password must contain at least one letter")
@ -218,7 +236,6 @@ function YourDetailsForm() {
confirmPassword: Yup.string()
.oneOf([Yup.ref("password"), null], "Passwords must match")
.required("The password must match"),
currentEmail: Yup.string().email("Invalid email").notRequired(),
otp: Yup.string().required("OTP is required"),
// Clinic details validation
@ -228,6 +245,20 @@ function YourDetailsForm() {
.matches(/^[0-9]+$/, "Business Phone must be a valid number")
.min(10, "Business Phone number should have 10 digits only.")
.max(10, "Business Phone number should have 10 digits only."),
businessPhonePrefix: Yup.string().required(
"Business Phone Prefix is required"
),
emergencyBusinessPhone: Yup.string()
.required("Emergency Business Phone is required")
.matches(/^[0-9]+$/, "Emergency Business Phone must be a valid number")
.min(10, "Emergency Business Phone number should have 10 digits only.")
.max(10, "Emergency Business Phone number should have 10 digits only."),
emergencyBusinessPhonePrefix: Yup.string().required(
"Emergency Business Phone Prefix is required"
),
businessEmail: Yup.string()
.required("Business Email is required")
.email("Please enter valid Email Address"),
businessFax: Yup.string()
.required("Business Fax is required")
.matches(/^[0-9]+$/, "Business Fax must be a valid number")
@ -239,35 +270,21 @@ function YourDetailsForm() {
designation: Yup.string()
.required("Designation is required")
.typeError("Designation must be 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"),
pincode: Yup.string().required("Pincode is required"),
state: Yup.string().required(),
locality: Yup.string().required("City is required"),
country: Yup.string().required("Country is required"),
fullAddress: Yup.string()
.required("Full Address is required")
.typeError("Address must be string"),
companyPANImage: Yup.string()
companyABNImageNumber: Yup.string()
.required("ABN number is required")
.matches(
ONLY_ALPHA_NUMERIC_ACCEPT_REGEX,
"ABN Number must only contain numbers and letters"
)
.length(ABN_NUMBER_LENGTH, "Enter valid ABN Number"),
companyTANNumber: Yup.string()
.required("Medicare number is required")
.matches(
ONLY_ALPHA_NUMERIC_ACCEPT_REGEX,
"Medicare Number must only contain numbers and letters"
)
.length(MEDIICARE_NUMBER_LENGTH, "Enter valid Medicare Number"),
companyGSTImage: Yup.string().required("Clinic GST document is required"),
companyPANImage: Yup.string().required("Clinic ABN document is required"),
companyTANImage: Yup.string().required(
"Clinic MEDICARE document is required"
),
companyABNImage: Yup.string().required("Clinic ABN document is required"),
contract: Yup.string().required("Contract is required"),
termsAccepted: Yup.boolean()
.oneOf([true], "You must accept the terms and conditions")
@ -330,23 +347,22 @@ function YourDetailsForm() {
const handleFormSubmit = async () => {
dispatch(updateFormDetails(formik.values));
const body = formatedData(formik.values);
console.log(body);
// const body = formatedData(formik.values);
try {
// const response = await signup(body);
// if (response?.data?.error) {
// pushNotification(response?.data?.message, NOTIFICATION.ERROR);
// } else {
// pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
// dispatch(resetFormData());
// navigate('/');
// }
// pushNotification('Your request is submitted', NOTIFICATION.SUCCESS);
// dispatch(resetFormData());
// navigate('/');
// TODO: verify otp first
const response = await signup(body);
if (response?.data?.error) {
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
return;
}
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
dispatch(resetFormData());
navigate("/auth/signup/payment");
} catch (error) {
// console.error('Error signing up:', error);
console.error('Error signing up:', error);
} finally {
formik.isSubmitting(false);
}
@ -355,9 +371,7 @@ function YourDetailsForm() {
// Initialize formik with a submission handler defined inline to avoid circular references
const formik = useFormik({
initialValues: defaultFormData.current,
// validationSchema,
validateOnBlur: true,
validateOnChange: false, // Only validate on blur, not on every keystroke
validationSchema,
onSubmit: handleFormSubmit,
});
@ -366,7 +380,7 @@ function YourDetailsForm() {
if (formik?.values?.country !== defaultFormData.current.country) {
formik.setFieldValue("pincode", "");
formik.setFieldValue("state", "");
formik.setFieldValue("locality", "");
// formik.setFieldValue("locality", "");
}
}, [formik?.values?.country]);
@ -383,11 +397,11 @@ function YourDetailsForm() {
useEffect(() => {
if (Array.isArray(pincodeData) && pincodeData.length > 0) {
formik.setFieldValue("state", pincodeData[0]?.state);
formik.setFieldValue("locality", selectedLocalityRef.current);
// formik.setFieldValue("locality", selectedLocalityRef.current);
} else {
setCountryOption(["Australia", "India"]);
formik.setFieldValue("state", "");
formik.setFieldValue("locality", "");
// formik.setFieldValue("state", "");
// formik.setFieldValue("locality", "");
formik.setFieldError("pincode", "Invalid pincode");
}
}, [pincodeData]);
@ -402,20 +416,34 @@ function YourDetailsForm() {
aspect: 1 / 1,
});
setLogoname();
formik.setFieldValue("companyLogo", "");
formik.setFieldValue("clinicLogo", "");
};
const handleFileUpload = async (value) => {
let formData = new FormData();
formData.append("file", value);
formData.append("fileName", value?.name);
try {
const data = await fileUpload(formData);
const imageUrl = data?.data?.data?.Key;
formik.setFieldValue("companyLogo", imageUrl);
return imageUrl;
// Store the file object directly in the form values
// We'll use this later for the actual upload after form submission
const filePayload = {
folder: "assests",
file_name: value.name,
};
const presignedUrlResponseClinicLogo = await getPresignedUrl(
filePayload,
);
await uploadToS3(
value,
presignedUrlResponseClinicLogo?.data?.data?.api_url
);
formik.setFieldValue("clinicLogo", presignedUrlResponseClinicLogo?.data?.data?.key);
// Return a temporary local URL for preview purposes
return URL.createObjectURL(value);
} catch (error) {
pushNotification("Error while uploading file", NOTIFICATION.ERROR);
pushNotification("Error processing file", NOTIFICATION.ERROR);
}
};
@ -508,7 +536,16 @@ function YourDetailsForm() {
// Set uploaded file URL
const setUploadedFileUrl = (documentName, fileUrl) => {
formik.setFieldValue(documentName, fileUrl);
console.log('Document Name:', documentName);
console.log('File URL:', fileUrl);
console.log('File URL Type:', typeof fileUrl);
if (documentName && fileUrl !== undefined) {
formik.setFieldValue(documentName, fileUrl);
console.log('After setting value:', formik.values[documentName]);
} else {
console.error('Invalid parameters for setUploadedFileUrl:', { documentName, fileUrl });
}
};
// handleSaveAndNext will use formik's handleSubmit
@ -516,77 +553,66 @@ function YourDetailsForm() {
// Helper function for formatted data
function formatedData(inputData) {
const data = {
name: inputData.companyName || "",
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,
street: inputData.fullAddress || "",
pinCode: inputData.pincode || "",
country: inputData.country || "",
locality: inputData.locality || "",
industryId: inputData.companyIndustry || 0,
companyAdmin: {
name: inputData.name || "",
email: inputData.email || "",
mobile: inputData.mobileNumber || "",
password: inputData.password || "",
designation: inputData.designation || "",
user: {
username: inputData.name,
email: inputData.email,
mobile: `${inputData.mobilePrefix ?? mobilePrefixOptions[0].name} ${
inputData.mobileNumber
}`,
password: inputData.password,
clinicRole: inputData.designation,
userType: "clinic_admin",
},
clinic: {
name: inputData.companyName,
address: inputData.fullAddress,
state: inputData.state,
phone: `${
inputData.businessPhonePrefix ?? mobilePrefixOptions[0].name
} ${inputData.businessPhone}`,
emergencyPhone: `${
inputData.emergencyBusinessPhonePrefix ?? mobilePrefixOptions[0].name
} ${inputData.emergencyBusinessPhone}`,
fax: inputData.businessFax,
email: inputData.businessEmail,
integration: inputData.practiceManagementSystem,
pms_id: inputData.practiceId,
practice_name: inputData.practiceName,
logo: inputData.clinicLogo || null,
country: inputData.country,
postal_code: inputData.pincode,
city: inputData.locality,
abn_number: inputData.companyABNImageNumber,
abn_doc: inputData.companyABNImage,
contract_doc: inputData.contract,
},
practiceId: inputData.practiceId || "",
practiceManagementSystem: inputData.practiceManagementSystem || "",
practiceName: inputData.practiceName || "",
companyUsers: inputData.billingUsers || "",
companyDocuments: [
...(inputData.companyLogo
? [
{
documentNumber: "LOGO",
fileURL: inputData.companyLogo,
documentType: "LOGO",
},
]
: []),
{
documentNumber: inputData.companyPANImage || "",
fileURL: inputData.companyPANImage || "",
documentType: "PAN",
},
],
contract: inputData.contract || "",
};
return data;
}
const handleAddUser = () => {
if (
billingUsers?.includes(formik?.values?.currentEmail) ||
formik?.values?.email === formik.values?.currentEmail
) {
pushNotification("Email Address must be unique", NOTIFICATION.ERROR);
const handleSaveAndNext = async () => {
if (!otpField) {
pushNotification("Please verify OTP first", NOTIFICATION.ERROR);
otpButtonRef.current?.focus();
return;
}
if (formik.values.currentEmail && billingUsers?.length < 5) {
setBillingUsers([...billingUsers, formik.values.currentEmail]);
formik.setFieldValue("currentEmail", "");
if (!testConnection) {
pushNotification("Please test the connection first", NOTIFICATION.ERROR);
// scroll to test connection button
// testConnectionRef.current?.scrollIntoView({
// behavior: "smooth",
// });
testConnectionRef.current?.focus();
return;
}
};
const handleUserDelete = (index) => {
const updatedUsers = billingUsers.filter((_, i) => i !== index);
setBillingUsers(updatedUsers);
};
const handleSaveAndNext = async () => {
const formikError = await formik.validateForm(formik.values);
const errors = Object.keys(formikError);
console.log(errors)
if (errors.length) {
// Find the first invalid field and focus it
const firstErrorField = errors[0];
@ -653,7 +679,7 @@ function YourDetailsForm() {
className={classes.formRoot}
>
<Grid item md={4} sm={6} xs={12}>
<TextField value={1} disabled />
<TextField value={latestClinicID + 1} disabled />
</Grid>
</Grid>
@ -733,6 +759,7 @@ function YourDetailsForm() {
color="info"
disabled={!formik.values.email}
onClick={handleOTPButton}
ref={otpButtonRef}
>
Request OTP
</Button>
@ -1146,7 +1173,16 @@ function YourDetailsForm() {
variant="outlined"
name="emergencyBusinessPhone"
value={formik.values.emergencyBusinessPhone}
onChange={formik.handleChange}
onChange={(e) => {
if (e.target.value.length <= 10) {
const value =
e.target.value?.match(/\d+/g) || "";
formik.setFieldValue(
"emergencyBusinessPhone",
value?.toString()
);
}
}}
onBlur={formik.handleBlur}
InputProps={{
type: "text",
@ -1253,16 +1289,7 @@ function YourDetailsForm() {
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()
);
}
}}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
InputProps={{
type: "email",
@ -1307,8 +1334,8 @@ function YourDetailsForm() {
}
>
{integrationOptions.map((option) => (
<MenuItem key={option} value={option}>
{option}
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
@ -1372,6 +1399,7 @@ function YourDetailsForm() {
variant="contained"
color="primary"
onClick={handleTestConnection}
ref={testConnectionRef}
>
Test Connection
</Button>
@ -1381,7 +1409,7 @@ function YourDetailsForm() {
<Grid item md={5} sm={12}>
<InputLabel className={classes.inputLabel}>
Add Business Logo
{(logoImage || formik.values.companyLogo) && (
{(logoImage || formik.values.clinicLogo) && (
<div>
<button
className={classes.imgButton}
@ -1394,7 +1422,7 @@ function YourDetailsForm() {
</InputLabel>
<Box display="flex">
<Box className={classes.dropZoneOuterBox}>
<label htmlFor="companyLogo">
<label htmlFor="clinicLogo">
<Dropzone
accept={{
"image/": [...IMAGE_TYPE],
@ -1416,7 +1444,7 @@ function YourDetailsForm() {
onClick: (event) => {
if (
logoImage ||
formik.values.companyLogo
formik.values.clinicLogo
) {
event.stopPropagation();
}
@ -1431,7 +1459,7 @@ function YourDetailsForm() {
}
>
{!logoImage &&
!formik.values.companyLogo ? (
!formik.values.clinicLogo ? (
<>
<Box
className={classes.addIcon}
@ -1478,13 +1506,13 @@ function YourDetailsForm() {
)}
{!logoImage &&
formik.values
.companyLogo && (
.clinicLogo && (
<img
alt="Edit me"
src={
IMAGE_LOCATION_BASE_URL +
formik.values
.companyLogo
.clinicLogo
}
className={
classes.editImage
@ -1495,8 +1523,8 @@ function YourDetailsForm() {
</>
)}
</Box>
{formik.errors.companyLogo &&
formik.touched.companyLogo && (
{formik.errors.clinicLogo &&
formik.touched.clinicLogo && (
<Box
component="span"
className={
@ -1508,9 +1536,8 @@ function YourDetailsForm() {
classes.errorText
}
>
{formik.errors.companyLogo
? formik.errors
.companyLogo
{formik.errors.clinicLogo
? formik.errors.clinicLogo
: ""}
</span>
</Box>
@ -1557,6 +1584,7 @@ function YourDetailsForm() {
error={Boolean(
formik.errors.country && formik.touched.country
)}
helperText={formik.errors.country}
>
<MenuItem value="" disabled>
<Typography className={classes.placeholderText}>
@ -1650,6 +1678,7 @@ function YourDetailsForm() {
formik.errors.locality &&
formik.touched.locality
)}
helperText={formik.errors.locality}
>
<MenuItem value="" disabled>
<Typography className={classes.placeholderText}>
@ -1723,16 +1752,16 @@ function YourDetailsForm() {
{/* PAN NUMBER GRID */}
<Grid item md={4} sm={6} xs={12}>
<InputLabel className={classes.inputLabel}>
Clinic ABN Number*
Clinic ABN/ACN Number*
</InputLabel>
<TextField
fullWidth
placeholder="Enter Clinic ABN Number"
placeholder="Enter Clinic ABN/ACN Number"
color="secondary"
variant="outlined"
name="companyPANImage"
value={formik.values.companyPANImage}
name="companyABNImageNumber"
value={formik.values.companyABNImageNumber}
onChange={(e) => {
if (
e.target.value.length <= ABN_NUMBER_LENGTH
@ -1745,26 +1774,26 @@ function YourDetailsForm() {
}}
onBlur={(e) => {
formik.setFieldValue(
"companyPANImage",
"companyABNImageNumber",
e.target.value.trim()
);
formik.handleBlur(e);
}}
inputRef={fieldRefs.companyPANImage}
inputRef={fieldRefs.companyABNImageNumber}
error={Boolean(
formik.errors.companyPANImage &&
formik.touched.companyPANImage
formik.errors.companyABNImageNumber &&
formik.touched.companyABNImageNumber
)}
helperText={
formik.errors.companyPANImage &&
formik.touched.companyPANImage
? formik.errors.companyPANImage
formik.errors.companyABNImageNumber &&
formik.touched.companyABNImageNumber
? formik.errors.companyABNImageNumber
: ""
}
InputProps={{
endAdornment: (
<InputAdornment position="end">
{!formik.errors.companyPANImage ? (
{!formik.errors.companyABNImageNumber ? (
<VerifiedIcon
className={classes.verifyIcon}
/>
@ -1778,23 +1807,37 @@ function YourDetailsForm() {
{/* PAN image upload grid */}
<Grid item md={4} sm={12} xs={12}>
<CustomFileUpload
label="Add ABN Image*"
documentName="companyPANImage"
label="Add ABN/ACN Image*"
documentName="companyABNImage"
onUploadDone={setUploadedFileUrl}
maxFileSizeInMb={MAX_FILE_SIZE_IN_MB}
maxFiles={MAX_FILES}
uploadedFileUrl={formik.values.companyPANImage}
uploadedFileUrl={formik.values.companyABNImage}
errorMessage={
formik.errors.companyPANImage &&
formik.touched.companyPANImage
? formik.errors.companyPANImage
formik.errors.companyABNImage &&
formik.touched.companyABNImage
? formik.errors.companyABNImage
: ""
}
/>
</Grid>
{/* contract grid */}
<Grid item md={4} sm={12} xs={12}>
<Grid
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
item
md={4}
sm={12}
xs={12}
>
<Box mr={2}>
<Tooltip title="Download the contract template">
<Button>Download Template</Button>
</Tooltip>
</Box>
<CustomFileUpload
label="Add Contract*"
documentName="contract"

View File

@ -8,28 +8,31 @@ const initialState = {
name: '',
email: '',
mobileNumber: '',
mobilePrefix: '',
password: '',
confirmPassword: '',
billingUsers: [],
currentEmail: '',
companyName: '',
designation: '',
businessPhone: '',
companyAbout: '',
companyLogo: '',
companyIndustry: '',
businessPhonePrefix: "",
businessPhone: "",
emergencyBusinessPhone: "",
emergencyBusinessPhonePrefix: "",
businessFax: "",
clinicLogo: '',
practiceManagementSystem: '',
practiceId: '',
practiceName: '',
pincode: '',
state: '',
locality: '',
fullAddress: '',
companyGSTNumber: '',
companyPANNumber: '',
companyTANNumber: '',
companyGSTImage: '',
companyPANImage: '',
companyTANImage: '',
companyABNImageNumber: "",
companyABNImage: "",
termsAccepted: '',
hideAndShowFunctionality: false,
contract: '',
businessEmail: '',
};
const updateFormDetails = (state, payload) => ({
...state,

View File

@ -80,7 +80,7 @@ function Users() {
const [roles, setRoles] = useState();
const [isAdmin, setIsAdmin] = useState();
const isBsAdmin = useSelector((state) => state?.login?.user?.isBsAdmin);
const isSuperAdmin = useSelector((state) => state?.login?.user?.isSuperAdmin);
/* ----------------- Get Users ----------------- */
const getData = async (filters) => {