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 *.njsproj
*.sln *.sln
*.sw? *.sw?
.env

9
package-lock.json generated
View File

@ -21,6 +21,7 @@
"firebase": "^11.6.0", "firebase": "^11.6.0",
"formik": "^2.4.6", "formik": "^2.4.6",
"i": "^0.3.7", "i": "^0.3.7",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"material-react-table": "^3.2.1", "material-react-table": "^3.2.1",
"npm": "^11.3.0", "npm": "^11.3.0",
@ -4184,6 +4185,14 @@
"jss": "10.10.0" "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": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View File

@ -23,6 +23,7 @@
"firebase": "^11.6.0", "firebase": "^11.6.0",
"formik": "^2.4.6", "formik": "^2.4.6",
"i": "^0.3.7", "i": "^0.3.7",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"material-react-table": "^3.2.1", "material-react-table": "^3.2.1",
"npm": "^11.3.0", "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 = 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 export const IMAGE_LOCATION_BASE_URL = import.meta.env
.VITE_IMAGE_LOCATION_BASE_URL; .VITE_IMAGE_LOCATION_BASE_URL;
export const BILLDESK_URL = import.meta.env.VITE_BILLDESK_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 uploadIcon from '../assets/images/icon/upload.svg';
import PdfIcon from '../assets/images/icon/pdf.png'; import PdfIcon from '../assets/images/icon/pdf.png';
import DocIcon from '../assets/images/icon/doc.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'; import { IMAGE_LOCATION_BASE_URL } from '../common/envVariables';
const CustomFileUpload = forwardRef(function CustomFileUpload( const CustomFileUpload = forwardRef(function CustomFileUpload(
@ -77,14 +77,9 @@ const CustomFileUpload = forwardRef(function CustomFileUpload(
useEffect(() => { useEffect(() => {
const makeFullUrlIfNeeded = (url) => { const makeFullUrlIfNeeded = (url) => {
// Return early if url is undefined or empty
if(!url){ if(!url){
setOldUploadedFileUrl(''); return
setFileExtension('');
setImageName('');
return;
} }
const isHttp = url.startsWith('http://') || url.startsWith('https://'); const isHttp = url.startsWith('http://') || url.startsWith('https://');
if (!isHttp) { if (!isHttp) {
setOldUploadedFileUrl(`${IMAGE_LOCATION_BASE_URL}${url}`); setOldUploadedFileUrl(`${IMAGE_LOCATION_BASE_URL}${url}`);
@ -114,16 +109,50 @@ const CustomFileUpload = forwardRef(function CustomFileUpload(
if (!value) { if (!value) {
return; return;
} }
let formData = new FormData(); const filePayload = {
formData.append('file', value); folder: "assests",
formData.append('fileName', value?.name); file_name: value.name,
};
try { try {
setIsLoading(true); setIsLoading(true);
const data = await fileUpload(formData); const response = await getPresignedUrl(filePayload);
onUploadDone(documentName, data?.data?.data?.Key);
// 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; return;
} catch (error) { } catch (error) {
// console.error(error); console.error('Error in handleFileUpload:', error);
pushNotification('Error while uploading file', NOTIFICATION.ERROR); pushNotification('Error while uploading file', NOTIFICATION.ERROR);
} finally { } finally {
setIsLoading(false); 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({ export const axiosInstance = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
crossDomain: true, // crossDomain: true,
headers: { // headers: {
'Content-Type': 'application/json', // 'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // // 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*', // // 'Access-Control-Allow-Headers': '*',
}, // },
withCredentials: true, // withCredentials: false,
timeout: 300000, // timeout: 300000,
}); });
axiosInstance.interceptors.request.use( axiosInstance.interceptors.request.use(
async function (config) { async function (config) {
try { try {
const token = JSON.parse(localStorage.getItem('redux')); const token = JSON.parse(localStorage.getItem('redux'));
if (token?.data?.data) { if (token?.login?.token) {
config.headers.Authorization = `${token?.data?.data}`; config.headers.Authorization = `Bearer ${token?.login?.token}`;
} }
const state = store.getState(); const state = store.getState();
const companyId = state?.loginAsCompanyAdmin?.companyId; // Extract companyId const companyId = state?.loginAsCompanyAdmin?.companyId; // Extract companyId
@ -47,7 +47,7 @@ axiosInstance.interceptors.request.use(
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
function (response) { function (response) {
if (response?.data && response?.data?.error !== '') { if (response?.data && response?.data?.error !== null) {
pushNotification(response?.data?.message, NOTIFICATION.ERROR); pushNotification(response?.data?.message, NOTIFICATION.ERROR);
return Promise.reject(response); return Promise.reject(response);
} }

View File

@ -1,5 +1,5 @@
import { initializeApp } from 'firebase/app'; import { initializeApp } from 'firebase/app';
import { getMessaging, onMessage } from 'firebase/messaging'; import { getMessaging, onMessage, isSupported } from 'firebase/messaging';
export const firebaseConfig = { export const firebaseConfig = {
apiKey: "AIzaSyDBDwlnQsbIxKni_UzZxDjeIk0akK-vDPM", apiKey: "AIzaSyDBDwlnQsbIxKni_UzZxDjeIk0akK-vDPM",
@ -12,7 +12,35 @@ export const firebaseConfig = {
const firebaseApp = initializeApp(firebaseConfig); const firebaseApp = initializeApp(firebaseConfig);
export default firebaseApp; export default firebaseApp;
export const messaging = getMessaging(firebaseApp);
export const onForegroundMessage = () => // Initialize messaging only if supported
new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload))); 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', ERROR: 'error',
}; };
export const USER_ROLES = {
SUPER_ADMIN: 'SUPER_ADMIN',
CLINIC_ADMIN: 'CLINIC_ADMIN',
}
export const CLINIC_TYPE = { export const CLINIC_TYPE = {
UNREGISTERED: 'UNREGISTERED', UNREGISTERED: 'UNREGISTERED',
REGISTERED: 'REGISTERED', REGISTERED: 'REGISTERED',
@ -28,6 +34,8 @@ export const PERMISSIONS = {
}; };
export const CLINIC_STATUS = { export const CLINIC_STATUS = {
UNDER_REVIEW:"UNDER_REVIEW",
ACTIVE:"ACTIVE",
ON_HOLD: 'ON_HOLD', ON_HOLD: 'ON_HOLD',
REJECTED: 'REJECTED', REJECTED: 'REJECTED',
NOT_REVIEWED: 'NOT_REVIEWED', NOT_REVIEWED: 'NOT_REVIEWED',
@ -145,6 +153,10 @@ export const numRegex = /^[0-9\b]+$/;
import PdfIcon from '../assets/images/icon/pdf.png'; import PdfIcon from '../assets/images/icon/pdf.png';
import DocIcon from '../assets/images/icon/doc.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 = { export const FILE_EXTENTIONS_ICONS = {
pdf: PdfIcon, pdf: PdfIcon,
@ -152,6 +164,10 @@ export const FILE_EXTENTIONS_ICONS = {
docx: DocIcon, docx: DocIcon,
'vnd.openxmlformats-officedocument.wordprocessingml.document': DocIcon, 'vnd.openxmlformats-officedocument.wordprocessingml.document': DocIcon,
msword: DocIcon, msword: DocIcon,
jpeg: JpegIcon,
jpg: JpgIcon,
png: PngIcon,
svg: SvgIcon,
}; };
export const DEBOUNCE_DELAY = 500; export const DEBOUNCE_DELAY = 500;

View File

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

View File

@ -4,7 +4,7 @@ import ProfileSection from './ProfileSection';
import { useStyles } from '../mainLayoutStyles'; import { useStyles } from '../mainLayoutStyles';
import NotificationSection from './NotificationSection '; import NotificationSection from './NotificationSection ';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { CLINIC_STATUS } from '../../../constants'; import { CLINIC_STATUS, USER_ROLES } from '../../../constants';
function Header() { function Header() {
const theme = useTheme(); const theme = useTheme();
@ -21,7 +21,7 @@ function Header() {
} }
> >
<Typography> <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> </Typography>
<Box className={classes.profilDiv}> <Box className={classes.profilDiv}>
<NotificationSection /> <NotificationSection />

View File

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

View File

@ -34,7 +34,7 @@ import { SIDEBAR_CONFIG } from "./sideBarConfig"; // Adjust path if necessary
const Sidebar = ({ onClose, showCloseIcon }) => { const Sidebar = ({ onClose, showCloseIcon }) => {
const classes = useStyles(); const classes = useStyles();
const location = useLocation(); const location = useLocation();
const { isBSAdmin } = isBSPortal(); const { isSuperAdmin } = isBSPortal();
const [activeLink, setActiveLink] = useState("Dashboard"); const [activeLink, setActiveLink] = useState("Dashboard");
const [accordianActiveLink, setAccordianActiveLink] = useState("All Jobs"); const [accordianActiveLink, setAccordianActiveLink] = useState("All Jobs");
const [parentRoute, setParentRoute] = useState(""); const [parentRoute, setParentRoute] = useState("");
@ -92,13 +92,13 @@ const Sidebar = ({ onClose, showCloseIcon }) => {
(companyStatus === CLINIC_STATUS.APPROVED && (companyStatus === CLINIC_STATUS.APPROVED &&
HIDE_FUNCTIONALITY && HIDE_FUNCTIONALITY &&
HIDE_MODULES.includes(item?.path)) || 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 // Only render if user has the required role
if (hasRole) { if (hasRole) {
// Determine if the link should be disabled // Determine if the link should be disabled
const isDisabled = const isDisabled =
(!isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature; (!isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature;
const targetPath = isDisabled ? "#" : `/${item.path}`; const targetPath = isDisabled ? "#" : `/${item.path}`;
const isActive = activeLink === item.path; // Check if this link is the active one 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) => { {visibleChildren.map((subItem, subIndex) => {
// Determine if the sub-item link should be disabled // Determine if the sub-item link should be disabled
const isSubDisabled = 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 subTargetPath = isSubDisabled ? "#" : `/${subItem.path}`;
const isSubActive = combinedRoute === subItem.path; // Check if this child link is active 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 SettingsIcon from '@mui/icons-material/Settings';
import ArticleIcon from '@mui/icons-material/Article'; import ArticleIcon from '@mui/icons-material/Article';
import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined'; import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined';
import TopicIcon from '@mui/icons-material/Topic'; import PaymentIcon from '@mui/icons-material/Payment';
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; import PaymentOutlinedIcon from '@mui/icons-material/PaymentOutlined';
import { USER_ROLES } from '../../../constants';
import { USER_ROLES } from '../../../redux/userRoleSlice';
// Define the sidebar configuration with proper permission fields // Define the sidebar configuration with proper permission fields
export const SIDEBAR_CONFIG = [ export const SIDEBAR_CONFIG = [
@ -48,6 +47,14 @@ export const SIDEBAR_CONFIG = [
// Only super admin can access admin staff management // Only super admin can access admin staff management
roles: [USER_ROLES.SUPER_ADMIN] 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', text: 'Doctor/Nurse Management',
path: 'doctor', path: 'doctor',

View File

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

View File

@ -25,7 +25,7 @@ export const setMockUser = (userType) => {
}; };
// Export mock user types for convenience // Export mock user types for convenience
export const MOCK_USER_TYPES = { export const MOCK_USER_ROLES = {
SUPER_ADMIN: 'superAdmin', SUPER_ADMIN: 'superAdmin',
CLINIC_ADMIN: 'clinicAdmin' 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 // Initial state
const initialState = { const initialState = {
role: null, role: null,

View File

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

View File

@ -9,11 +9,11 @@ const withPermission = (Component) => (props) => {
const companyStatus = useSelector( const companyStatus = useSelector(
(state) => state?.login?.user?.company?.status (state) => state?.login?.user?.company?.status
); );
const { isBSAdmin } = false; const { isSuperAdmin } = false;
// const { isBSAdmin } = isBSPortal(); // const { isSuperAdmin } = isBSPortal();
// If the user is a BS Admin, render the component without any checks // If the user is a BS Admin, render the component without any checks
if (isBSAdmin === true) { if (isSuperAdmin === true) {
return <Component {...props} />; 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,21 +1,42 @@
import { axiosInstance } from "../config/api";
import { CLINIC_TYPE } from "../constants"; import { CLINIC_TYPE } from "../constants";
import { clinicsData, registeredClinicsData } from "../mock/clinics"; 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) => { export const getClinics = (params) => {
switch (params.type) { console.log(params);
case CLINIC_TYPE.UNREGISTERED:
return { data: clinicsData }; let searchParams = new URLSearchParams();
case CLINIC_TYPE.REGISTERED: searchParams.append("size", params?.pagination?.pageSize ?? 10);
return { data: registeredClinicsData }; searchParams.append("page", params?.pagination.pageIndex ?? 0);
case CLINIC_TYPE.SUBSCRIBED: searchParams.append("filter_type", params?.type ?? CLINIC_TYPE.REGISTERED);
return { data: registeredClinicsData }; searchParams.append("search", params?.globalFilter ?? "");
default:
return { data: clinicsData }; let url = `/clinics/?${searchParams.toString()}`;
}
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
}; };
export const getClinicsById = (id) => { export const getLatestClinicId = () => {
const url = `/companies/${id}`; const url = `/clinics/latest-id`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axiosInstance axiosInstance
.get(url) .get(url)
@ -24,11 +45,31 @@ export const getClinics = (params) => {
}); });
}; };
export const updateClinicStatus = (id, data) => { export const getClinicsById = (id) => {
const url = `/companies/${id}/company-review`; const url = `/clinics/${id}`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axiosInstance axiosInstance
.post(url, data) .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)) .then((response) => resolve(response))
.catch((err) => reject(err)); .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)); .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 { logout } from "../services/users.service";
import { deleteToken } from "firebase/messaging"; import { deleteToken } from "firebase/messaging";
import { messaging } from "../config/firebase"; import { getMessagingInstance } from "../config/firebase";
export const isLoggedIn = () => { export const isLoggedIn = () => {
let redux = JSON.parse(localStorage.getItem("redux")); let redux = JSON.parse(localStorage.getItem("redux"));
@ -27,8 +27,8 @@ export const resetLocalStorage = () => {
export const isBSPortal = () => { export const isBSPortal = () => {
let redux = JSON.parse(localStorage.getItem("redux")); let redux = JSON.parse(localStorage.getItem("redux"));
const isBSAdmin = redux?.login?.user?.isBsAdmin; const isSuperAdmin = redux?.login?.user?.isSuperAdmin;
return { isBSAdmin }; return { isSuperAdmin };
}; };
export const commonLogoutFunc = async (redirectPath = "/auth/login") => { export const commonLogoutFunc = async (redirectPath = "/auth/login") => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import ProtectedComponent from '../../../components/ProtectedComponent';
import { getClinicsDashboardStatsById } from '../../../services/clinics.service'; import { getClinicsDashboardStatsById } from '../../../services/clinics.service';
import TotalNumber from '../components/TotalNumber'; import TotalNumber from '../components/TotalNumber';
import { useStyles } from '../dashboardStyles'; import { useStyles } from '../dashboardStyles';
import { getDashboardStats } from '../../../services/dashboard.services';
const Totals = ({ data, setData }) => { const Totals = ({ data, setData }) => {
const classes = useStyles(); const classes = useStyles();
@ -19,12 +20,11 @@ const Totals = ({ data, setData }) => {
const fetchData = async () => { const fetchData = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
const response = {}; const response = await getDashboardStats();
// const response = await getClinicsDashboardStatsById();
const apiData = response?.data?.data || {}; const apiData = response?.data?.data || {};
setData({ setData({
activeJobs: apiData.recruitmentAnalytics?.activeJobs, activeJobs: apiData?.active,
totalMaxJobPostings: apiData.totalMaxJobPostings, totalMaxJobPostings: apiData.inactive,
}); });
} catch (error) { } catch (error) {
console.error('Error fetching data:', 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 React from "react";
import { useStyles } from "../dashboardStyles"; import { useStyles } from "../dashboardStyles";
import SuperAdminTotals from "../Tiles/SuperAdminTotals"; import SuperAdminTotals from "../Tiles/SuperAdminTotals";
import PaymentConfig from "./PaymentConfig";
import { getDashboardStats } from "../../../services/dashboard.services";
const SuperAdmin = () => { const SuperAdmin = () => {
const classes = useStyles(); const classes = useStyles();
@ -17,22 +19,14 @@ const SuperAdmin = () => {
const fetchDashboardStats = async () => { const fetchDashboardStats = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
// const response = await getAdminDashboardStats(); const response = await getDashboardStats();
// const apiData = response?.data?.data || {}; const apiData = response?.data?.data || {};
// setData({
// totalAccounts: apiData?.dashboardStatistics?.totalAccounts,
// registrationRequest: apiData?.dashboardStatistics?.registrationRequest,
// rejected: apiData?.dashboardStatistics?.rejected,
// registered: apiData?.dashboardStatistics?.registered,
// });
setTimeout(() => {
setData({ setData({
totalAccounts: 10, totalAccounts: apiData?.totalClinics,
registrationRequest: 5, registrationRequest: apiData?.totalUnderReviewClinics,
rejected: 2, rejected: apiData?.totalRejectedClinics,
registered: 8, registered: apiData?.totalActiveClinics,
}); });
}, 1000);
} catch (error) { } catch (error) {
console.error("Error fetching data:", error); console.error("Error fetching data:", error);
} finally { } finally {
@ -53,6 +47,11 @@ const SuperAdmin = () => {
<SuperAdminTotals isLoading={isLoading} data={data} /> <SuperAdminTotals isLoading={isLoading} data={data} />
</Box> </Box>
</Box> </Box>
{/* Payment Configuration Section */}
<Box mt={4}>
<PaymentConfig />
</Box>
</Box> </Box>
); );
}; };

View File

@ -2,15 +2,12 @@ import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import Loader from "../components/Loader"; import Loader from "../components/Loader";
import ThankYou from "../ThankYou/"; import ThankYou from "../ThankYou/";
import { Box, Typography } from '@mui/material'; import { Box, Typography } from "@mui/material";
import SuperAdmin from "./components/SuperAdmin"; import SuperAdmin from "./components/SuperAdmin";
import { import { selectUserRole, setUserRole } from "../../redux/userRoleSlice";
selectUserRole,
setUserRole,
USER_ROLES,
} from "../../redux/userRoleSlice";
import Totals from "./Tiles/Totals"; import Totals from "./Tiles/Totals";
import { useStyles } from './dashboardStyles'; import { useStyles } from "./dashboardStyles";
import { USER_ROLES } from "../../constants";
function Dashboard() { function Dashboard() {
const classes = useStyles(); const classes = useStyles();
@ -36,12 +33,10 @@ function Dashboard() {
useEffect(() => { useEffect(() => {
// Determine user role based on user data from login reducer // Determine user role based on user data from login reducer
if (user) { if (user) {
// Check if user is a super admin // Check if user is a super admin
// This logic can be adjusted based on your specific role criteria // This logic can be adjusted based on your specific role criteria
const isSuperAdmin = user.isBsAdmin || const isSuperAdmin =
(user.isAdmin && user.permissions?.includes("SUPER_ADMIN_PERMISSION")); user.userType == USER_ROLES.SUPER_ADMIN.toLowerCase();
// Set the appropriate role in Redux
if (isSuperAdmin) { if (isSuperAdmin) {
dispatch(setUserRole(USER_ROLES.SUPER_ADMIN)); dispatch(setUserRole(USER_ROLES.SUPER_ADMIN));
} else { } else {
@ -69,32 +64,6 @@ function Dashboard() {
</Box> </Box>
<Totals data={data} setData={setData} /> <Totals data={data} setData={setData} />
</Box> </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> */}
</Box> </Box>
</Box> </Box>
); );

View File

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

View File

@ -1,106 +1,106 @@
import { axiosInstance } from '../../config/api'; import { axiosInstance } from '../../config/api';
import { LOGIN, PROFILE } from './loginActionTypes'; import { LOGIN, PROFILE } from './loginActionTypes';
// export const login = (data) => ({ export const login = (data) => ({
// type: LOGIN,
// payload: axiosInstance.post('/auth/companies/login', data),
// });
// export const profile = () => ({
// type: PROFILE,
// payload: axiosInstance.get('/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';
// 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
}
}
};
return {
type: LOGIN, type: LOGIN,
// Use a real Promise with a slight delay to simulate network request payload: axiosInstance.post('/auth/login', data),
payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300)) });
};
};
export const profile = () => { 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;
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'
}
};
}
// Create the response with the nested data structure the reducer expects
const mockProfileResponse = {
data: {
data: profileData
}
};
return {
type: PROFILE, type: PROFILE,
// Use a real Promise with a slight delay to simulate network request payload: axiosInstance.get('/users/me'),
payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300)) });
};
}; // 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);
// // 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))
// };
// };
// 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;
// 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
// }
// };
// 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'; } from './loginActionTypes';
const initialState = {}; const initialState = {};
const loginPending = (state) => ({ const loginPending = (state) => ({
...state, ...state,
}); });
const loginFulfilled = (state) => ({ const loginFulfilled = (state, payload) => ({
...state, ...state,
token: payload?.payload?.data?.data,
}); });
const loginRejected = (state) => ({ const loginRejected = (state) => ({
...state, ...state,
@ -39,7 +41,7 @@ const profileFulfilled = (state, payload) => {
); );
}); });
// Check if user is admin, add additional permissions // Check if user is admin, add additional permissions
if (user.isAdmin && !user?.isBsAdmin) { if (user.isAdmin && !user?.isSuperAdmin) {
userPermissions.push( userPermissions.push(
'CREATE_REC_USERS', 'CREATE_REC_USERS',
'READ_REC_USERS', 'READ_REC_USERS',

View File

@ -1,9 +1,9 @@
import AddIcon from '@mui/icons-material/Add'; import AddIcon from "@mui/icons-material/Add";
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from "@mui/icons-material/Close";
import PersonAddIcon from '@mui/icons-material/PersonAdd'; import PersonAddIcon from "@mui/icons-material/PersonAdd";
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from "@mui/icons-material/Search";
import { import {
Alert, Alert,
Box, Box,
@ -26,23 +26,23 @@ import {
TableRow, TableRow,
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from "@mui/material";
import React, { useState } from 'react'; import React, { useState } from "react";
import CustomBreadcrumbs from '../../components/CustomBreadcrumbs'; import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import PageHeader from '../../components/PageHeader'; import PageHeader from "../../components/PageHeader";
const MasterDataManagement = () => { const MasterDataManagement = () => {
// State for form fields // State for form fields
const [appointmentType, setAppointmentType] = useState(''); const [appointmentType, setAppointmentType] = useState("");
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
// State for staff list // State for staff list
const [staffList, setStaffList] = useState([]); const [staffList, setStaffList] = useState([]);
// State for dialog // State for staff dialog
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
// State for search // State for search
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState("");
// State for pagination // State for pagination
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
@ -51,11 +51,11 @@ const MasterDataManagement = () => {
// State for notification // State for notification
const [notification, setNotification] = useState({ const [notification, setNotification] = useState({
open: false, open: false,
message: '', message: "",
severity: 'success', severity: "success",
}); });
// Handle dialog open/close // Handle staff dialog open/close
const handleOpenDialog = () => { const handleOpenDialog = () => {
setOpenDialog(true); setOpenDialog(true);
}; };
@ -63,7 +63,7 @@ const MasterDataManagement = () => {
const handleCloseDialog = () => { const handleCloseDialog = () => {
setOpenDialog(false); setOpenDialog(false);
// Clear form // Clear form
setAppointmentType(''); setAppointmentType("");
}; };
// Handle form submission // Handle form submission
@ -84,8 +84,8 @@ const MasterDataManagement = () => {
// Show success notification // Show success notification
setNotification({ setNotification({
open: true, open: true,
message: 'Staff member added successfully!', message: "Staff member added successfully!",
severity: 'success', severity: "success",
}); });
}; };
@ -100,36 +100,32 @@ const MasterDataManagement = () => {
// ...................breadcrumbs array........................ // ...................breadcrumbs array........................
const breadcrumbs = [ const breadcrumbs = [
{ {
label: 'Dashboard', label: "Dashboard",
path: '/', path: "/",
}, },
{ {
label: 'Master Data Management', label: "Master Data Management",
path: '/masterData', path: "/masterData",
}, },
]; ];
return ( return (
<Container maxWidth="lg" sx={{ py: 4 }}> <Container maxWidth="lg" sx={{ py: 4 }}>
<PageHeader pageTitle="Master Data Management" hideAddButton />
<PageHeader
pageTitle="Master Data Management"
hideAddButton
/>
<CustomBreadcrumbs breadcrumbs={breadcrumbs} /> <CustomBreadcrumbs breadcrumbs={breadcrumbs} />
<Paper elevation={3} sx={{ p: 0, mb: 4, overflow: 'hidden' }}> <Paper elevation={3} sx={{ p: 0, mb: 4, overflow: "hidden" }}>
{/* Staff List Header with Add Button */} {/* Staff List Header with Add Button */}
<Box <Box
sx={{ sx={{
display: 'flex', display: "flex",
justifyContent: 'space-between', justifyContent: "space-between",
alignItems: 'center', alignItems: "center",
p: 3, p: 3,
}} }}
> >
<Typography variant="h6" component="h2" sx={{ fontWeight: 'bold' }}> <Typography variant="h6" component="h2" sx={{ fontWeight: "bold" }}>
Master Appointment Type List Master Appointment Type List
</Typography> </Typography>
@ -140,10 +136,10 @@ const MasterDataManagement = () => {
onClick={handleOpenDialog} onClick={handleOpenDialog}
sx={{ sx={{
borderRadius: 50, borderRadius: 50,
textTransform: 'none', textTransform: "none",
backgroundColor: '#ff3366', backgroundColor: "#ff3366",
'&:hover': { "&:hover": {
backgroundColor: '#e61653', backgroundColor: "#e61653",
}, },
}} }}
> >
@ -167,8 +163,8 @@ const MasterDataManagement = () => {
), ),
}} }}
sx={{ sx={{
backgroundColor: '#fff', backgroundColor: "#fff",
'& .MuiOutlinedInput-root': { "& .MuiOutlinedInput-root": {
borderRadius: 2, borderRadius: 2,
}, },
}} }}
@ -179,16 +175,17 @@ const MasterDataManagement = () => {
<TableContainer> <TableContainer>
<Table> <Table>
<TableHead> <TableHead>
<TableRow sx={{ backgroundColor: '#f5f5f5' }}> <TableRow sx={{ backgroundColor: "#f5f5f5" }}>
<TableCell sx={{ fontWeight: 'bold' }}>Sr. No.</TableCell> <TableCell sx={{ fontWeight: "bold" }}>Sr. No.</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Appointment Type</TableCell> <TableCell sx={{ fontWeight: "bold" }}>
Appointment Type
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{staffList.length > 0 ? ( {staffList.length > 0 ? (
staffList staffList
.filter( .filter((staff) =>
(staff) =>
`${staff.appointmentType}` `${staff.appointmentType}`
.toLowerCase() .toLowerCase()
.includes(searchQuery.toLowerCase()) .includes(searchQuery.toLowerCase())
@ -207,7 +204,7 @@ const MasterDataManagement = () => {
<TableCell <TableCell
colSpan={2} colSpan={2}
align="center" align="center"
sx={{ py: 5, color: '#666' }} sx={{ py: 5, color: "#666" }}
> >
No Appointment Type added yet No Appointment Type added yet
</TableCell> </TableCell>
@ -221,17 +218,17 @@ const MasterDataManagement = () => {
{staffList.length > 0 && ( {staffList.length > 0 && (
<Box <Box
sx={{ sx={{
display: 'flex', display: "flex",
justifyContent: 'center', justifyContent: "center",
p: 2, p: 2,
borderTop: '1px solid #eee', borderTop: "1px solid #eee",
}} }}
> >
<Button <Button
onClick={() => setPage((prev) => Math.max(prev - 1, 1))} onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
disabled={page === 1} disabled={page === 1}
startIcon={<ArrowBackIosNewIcon fontSize="small" />} startIcon={<ArrowBackIosNewIcon fontSize="small" />}
sx={{ mx: 1, color: '#666' }} sx={{ mx: 1, color: "#666" }}
> >
Previous Previous
</Button> </Button>
@ -241,11 +238,11 @@ const MasterDataManagement = () => {
disableElevation disableElevation
sx={{ sx={{
mx: 1, mx: 1,
minWidth: '36px', minWidth: "36px",
backgroundColor: '#f0f0f0', backgroundColor: "#f0f0f0",
color: '#333', color: "#333",
'&:hover': { "&:hover": {
backgroundColor: '#e0e0e0', backgroundColor: "#e0e0e0",
}, },
}} }}
> >
@ -256,7 +253,7 @@ const MasterDataManagement = () => {
onClick={() => setPage((prev) => prev + 1)} onClick={() => setPage((prev) => prev + 1)}
disabled={page * rowsPerPage >= staffList.length} disabled={page * rowsPerPage >= staffList.length}
endIcon={<ArrowForwardIosIcon fontSize="small" />} endIcon={<ArrowForwardIosIcon fontSize="small" />}
sx={{ mx: 1, color: '#666' }} sx={{ mx: 1, color: "#666" }}
> >
Next Next
</Button> </Button>
@ -264,6 +261,8 @@ const MasterDataManagement = () => {
)} )}
</Paper> </Paper>
{/* Add Staff Dialog */} {/* Add Staff Dialog */}
<Dialog <Dialog
open={openDialog} open={openDialog}
@ -273,18 +272,18 @@ const MasterDataManagement = () => {
> >
<DialogTitle <DialogTitle
sx={{ sx={{
display: 'flex', display: "flex",
alignItems: 'center', alignItems: "center",
justifyContent: 'space-between', justifyContent: "space-between",
pb: 1, pb: 1,
}} }}
> >
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: "flex", alignItems: "center" }}>
<PersonAddIcon sx={{ mr: 1, color: '#0a2d6b' }} /> <PersonAddIcon sx={{ mr: 1, color: "#0a2d6b" }} />
<Typography <Typography
variant="h6" variant="h6"
component="span" component="span"
sx={{ fontWeight: 'bold', color: '#0a2d6b' }} sx={{ fontWeight: "bold", color: "#0a2d6b" }}
> >
Add New Appointment Type Add New Appointment Type
</Typography> </Typography>
@ -321,9 +320,9 @@ const MasterDataManagement = () => {
onClick={handleSubmit} onClick={handleSubmit}
sx={{ sx={{
py: 1.5, py: 1.5,
backgroundColor: '#ff3366', backgroundColor: "#ff3366",
'&:hover': { "&:hover": {
backgroundColor: '#e61653', backgroundColor: "#e61653",
}, },
}} }}
> >
@ -337,12 +336,12 @@ const MasterDataManagement = () => {
open={notification.open} open={notification.open}
autoHideDuration={4000} autoHideDuration={4000}
onClose={handleCloseNotification} onClose={handleCloseNotification}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
> >
<Alert <Alert
onClose={handleCloseNotification} onClose={handleCloseNotification}
severity={notification.severity} severity={notification.severity}
sx={{ width: '100%' }} sx={{ width: "100%" }}
> >
{notification.message} {notification.message}
</Alert> </Alert>

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' && ( {paymentMethod === 'net_banking' && (
<Box mt={2}> <Box mt={2}>
<TextField <TextField

View File

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

View File

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

View File

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