diff --git a/.gitignore b/.gitignore index a547bf3..3b0b403 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? + +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2aabaa7..cee5ead 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "firebase": "^11.6.0", "formik": "^2.4.6", "i": "^0.3.7", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "material-react-table": "^3.2.1", "npm": "^11.3.0", @@ -4184,6 +4185,14 @@ "jss": "10.10.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/package.json b/package.json index 8cbaeb2..797cfd4 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "firebase": "^11.6.0", "formik": "^2.4.6", "i": "^0.3.7", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "material-react-table": "^3.2.1", "npm": "^11.3.0", diff --git a/src/assets/images/icon/jpeg.png b/src/assets/images/icon/jpeg.png new file mode 100644 index 0000000..3077a23 Binary files /dev/null and b/src/assets/images/icon/jpeg.png differ diff --git a/src/assets/images/icon/jpg.png b/src/assets/images/icon/jpg.png new file mode 100644 index 0000000..04f0db8 Binary files /dev/null and b/src/assets/images/icon/jpg.png differ diff --git a/src/assets/images/icon/phone.svg b/src/assets/images/icon/phone.svg new file mode 100644 index 0000000..0a69f13 --- /dev/null +++ b/src/assets/images/icon/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/icon/png.png b/src/assets/images/icon/png.png new file mode 100644 index 0000000..6425812 Binary files /dev/null and b/src/assets/images/icon/png.png differ diff --git a/src/assets/images/icon/svg.png b/src/assets/images/icon/svg.png new file mode 100644 index 0000000..562fc38 Binary files /dev/null and b/src/assets/images/icon/svg.png differ diff --git a/src/common/envVariables.js b/src/common/envVariables.js index 8427590..38d5ab6 100644 --- a/src/common/envVariables.js +++ b/src/common/envVariables.js @@ -1,5 +1,5 @@ export const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL ?? '/api/v1/recruitments'; + import.meta.env.VITE_API_BASE_URL; export const IMAGE_LOCATION_BASE_URL = import.meta.env .VITE_IMAGE_LOCATION_BASE_URL; export const BILLDESK_URL = import.meta.env.VITE_BILLDESK_URL; diff --git a/src/components/CustomFileUpload.jsx b/src/components/CustomFileUpload.jsx index a35b2cf..7e2c3ab 100644 --- a/src/components/CustomFileUpload.jsx +++ b/src/components/CustomFileUpload.jsx @@ -29,7 +29,7 @@ import { FILE_TYPE, NOTIFICATION } from '../constants'; import uploadIcon from '../assets/images/icon/upload.svg'; import PdfIcon from '../assets/images/icon/pdf.png'; import DocIcon from '../assets/images/icon/doc.png'; -import { fileUpload } from '../services/file.upload.services'; +import { fileUpload, getPresignedUrl, uploadToS3 } from '../services/file.upload.services'; import { IMAGE_LOCATION_BASE_URL } from '../common/envVariables'; const CustomFileUpload = forwardRef(function CustomFileUpload( @@ -77,14 +77,9 @@ const CustomFileUpload = forwardRef(function CustomFileUpload( useEffect(() => { const makeFullUrlIfNeeded = (url) => { - // Return early if url is undefined or empty - if (!url) { - setOldUploadedFileUrl(''); - setFileExtension(''); - setImageName(''); - return; + if(!url){ + return } - const isHttp = url.startsWith('http://') || url.startsWith('https://'); if (!isHttp) { setOldUploadedFileUrl(`${IMAGE_LOCATION_BASE_URL}${url}`); @@ -114,16 +109,50 @@ const CustomFileUpload = forwardRef(function CustomFileUpload( if (!value) { return; } - let formData = new FormData(); - formData.append('file', value); - formData.append('fileName', value?.name); + const filePayload = { + folder: "assests", + file_name: value.name, + }; try { setIsLoading(true); - const data = await fileUpload(formData); - onUploadDone(documentName, data?.data?.data?.Key); + const response = await getPresignedUrl(filePayload); + + // Debug the response structure + console.log('API Response:', response); + + // Check if we have a valid response with the expected structure + if (response?.data?.data?.Key) { + // Use the Key from the response + onUploadDone(documentName, response.data.data.Key); + await uploadToS3(value, response.data.data.api_url); + } else { + // If the expected structure is not found, try to find the key in a different location + // or use a fallback value + console.log('Response structure is different than expected'); + + // Try different possible paths to find the key + const key = response?.data?.Key || + response?.data?.data?.key || + response?.Key || + value.name; // Fallback to the file name if key not found + + console.log('Using key:', key); + onUploadDone(documentName, key); + + // Try to find the API URL similarly + const apiUrl = response?.data?.data?.api_url || + response?.data?.api_url || + response?.api_url; + + if (apiUrl) { + await uploadToS3(value, apiUrl); + } else { + console.error('Could not find API URL in response'); + } + } return; } catch (error) { - // console.error(error); + console.error('Error in handleFileUpload:', error); pushNotification('Error while uploading file', NOTIFICATION.ERROR); } finally { setIsLoading(false); diff --git a/src/components/ImagePreviewComponent.jsx b/src/components/ImagePreviewComponent.jsx new file mode 100644 index 0000000..0bc067f --- /dev/null +++ b/src/components/ImagePreviewComponent.jsx @@ -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 ( + + zoomOut()} // No need to wrap this in a function + > + + + resetTransform()} + > + + + zoomIn()} + > + + + + ); +}; + +const ImagePreviewComponent = ({ + fileUrl, + fileExtension, + handleDownload, + ref, +}) => { + const classes = useStyles(); + return ( + <> + {fileExtension === 'pdf' ? ( + <> + + + + Download + + + > + ) : fileExtension === 'doc' || + fileExtension === 'docx' || + fileExtension === + 'vnd.openxmlformats-officedocument.wordprocessingml.document' || + fileExtension === 'msword' ? ( + <> + + + + Download + + + > + ) : ( + + {(utils) => ( + + + + + + + + + )} + + )} + > + ); +}; + +export default ImagePreviewComponent; diff --git a/src/components/styles/ImagePreviewComponentStyles.js b/src/components/styles/ImagePreviewComponentStyles.js new file mode 100644 index 0000000..41a0f58 --- /dev/null +++ b/src/components/styles/ImagePreviewComponentStyles.js @@ -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), + }, +})); diff --git a/src/config/api.js b/src/config/api.js index bf7e744..2ac1711 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -7,22 +7,22 @@ import store from '../redux/store'; export const axiosInstance = axios.create({ baseURL: API_BASE_URL, - crossDomain: true, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*', - }, - withCredentials: true, - timeout: 300000, + // crossDomain: true, + // headers: { + // 'Content-Type': 'application/json', + // // 'Access-Control-Allow-Origin': '*', + // // 'Access-Control-Allow-Headers': '*', + // }, + // withCredentials: false, + // timeout: 300000, }); axiosInstance.interceptors.request.use( async function (config) { try { const token = JSON.parse(localStorage.getItem('redux')); - if (token?.data?.data) { - config.headers.Authorization = `${token?.data?.data}`; + if (token?.login?.token) { + config.headers.Authorization = `Bearer ${token?.login?.token}`; } const state = store.getState(); const companyId = state?.loginAsCompanyAdmin?.companyId; // Extract companyId @@ -47,7 +47,7 @@ axiosInstance.interceptors.request.use( axiosInstance.interceptors.response.use( function (response) { - if (response?.data && response?.data?.error !== '') { + if (response?.data && response?.data?.error !== null) { pushNotification(response?.data?.message, NOTIFICATION.ERROR); return Promise.reject(response); } diff --git a/src/config/firebase.js b/src/config/firebase.js index f5e3680..d107e43 100644 --- a/src/config/firebase.js +++ b/src/config/firebase.js @@ -1,5 +1,5 @@ import { initializeApp } from 'firebase/app'; -import { getMessaging, onMessage } from 'firebase/messaging'; +import { getMessaging, onMessage, isSupported } from 'firebase/messaging'; export const firebaseConfig = { apiKey: "AIzaSyDBDwlnQsbIxKni_UzZxDjeIk0akK-vDPM", @@ -12,7 +12,35 @@ export const firebaseConfig = { const firebaseApp = initializeApp(firebaseConfig); export default firebaseApp; -export const messaging = getMessaging(firebaseApp); -export const onForegroundMessage = () => - new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload))); +// Initialize messaging only if supported +let messagingInstance = null; + +// Check if messaging is supported before initializing +export const initializeMessaging = async () => { + try { + const isSupportedBrowser = await isSupported(); + if (isSupportedBrowser) { + messagingInstance = getMessaging(firebaseApp); + return messagingInstance; + } else { + console.log('Firebase messaging is not supported in this browser'); + return null; + } + } catch (error) { + console.error('Error checking messaging support:', error); + return null; + } +}; + +// Safe getter for messaging +export const getMessagingInstance = () => messagingInstance; + +// Safe onForegroundMessage function +export const onForegroundMessage = async () => { + const messaging = await initializeMessaging(); + if (!messaging) { + return Promise.resolve(null); // Return resolved promise with null if messaging not supported + } + return new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload))); +}; diff --git a/src/constants/index.js b/src/constants/index.js index abd74be..a59b301 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -5,6 +5,12 @@ export const NOTIFICATION = { ERROR: 'error', }; + +export const USER_ROLES = { + SUPER_ADMIN: 'SUPER_ADMIN', + CLINIC_ADMIN: 'CLINIC_ADMIN', +} + export const CLINIC_TYPE = { UNREGISTERED: 'UNREGISTERED', REGISTERED: 'REGISTERED', @@ -28,6 +34,8 @@ export const PERMISSIONS = { }; export const CLINIC_STATUS = { + UNDER_REVIEW:"UNDER_REVIEW", + ACTIVE:"ACTIVE", ON_HOLD: 'ON_HOLD', REJECTED: 'REJECTED', NOT_REVIEWED: 'NOT_REVIEWED', @@ -145,6 +153,10 @@ export const numRegex = /^[0-9\b]+$/; import PdfIcon from '../assets/images/icon/pdf.png'; import DocIcon from '../assets/images/icon/doc.png'; +import JpegIcon from '../assets/images/icon/jpeg.png'; +import JpgIcon from '../assets/images/icon/jpg.png'; +import PngIcon from '../assets/images/icon/png.png'; +import SvgIcon from '../assets/images/icon/svg.png'; export const FILE_EXTENTIONS_ICONS = { pdf: PdfIcon, @@ -152,6 +164,10 @@ export const FILE_EXTENTIONS_ICONS = { docx: DocIcon, 'vnd.openxmlformats-officedocument.wordprocessingml.document': DocIcon, msword: DocIcon, + jpeg: JpegIcon, + jpg: JpgIcon, + png: PngIcon, + svg: SvgIcon, }; export const DEBOUNCE_DELAY = 500; diff --git a/src/context/FirebaseProvider.jsx b/src/context/FirebaseProvider.jsx index e3ee572..e072946 100644 --- a/src/context/FirebaseProvider.jsx +++ b/src/context/FirebaseProvider.jsx @@ -1,7 +1,7 @@ import { onMessage } from 'firebase/messaging'; import { createContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { messaging } from '../config/firebase'; +import { initializeMessaging, getMessagingInstance } from '../config/firebase'; import { redirectByNotificationType } from '../views/Notifications/notificationUtils'; // Create the context @@ -12,8 +12,15 @@ const FirebaseProvider = (props) => { const navigate = useNavigate(); // Initialize navigation without page reload useEffect(() => { - checkNewMessage(messaging); - }, [messaging]); + const setupMessaging = async () => { + const messagingInstance = await initializeMessaging(); + if (messagingInstance) { + checkNewMessage(messagingInstance); + } + }; + + setupMessaging(); + }, []); const showBrowserNotifications = (payload) => { // Handle notification click diff --git a/src/layouts/mainLayout/components/Header.jsx b/src/layouts/mainLayout/components/Header.jsx index 35d462e..ea47174 100644 --- a/src/layouts/mainLayout/components/Header.jsx +++ b/src/layouts/mainLayout/components/Header.jsx @@ -4,7 +4,7 @@ import ProfileSection from './ProfileSection'; import { useStyles } from '../mainLayoutStyles'; import NotificationSection from './NotificationSection '; import { useSelector } from 'react-redux'; -import { CLINIC_STATUS } from '../../../constants'; +import { CLINIC_STATUS, USER_ROLES } from '../../../constants'; function Header() { const theme = useTheme(); @@ -21,7 +21,7 @@ function Header() { } > - {user?.isBsAdmin ? null : `Welcome to ${user?.company?.name}`} + {user?.userType == USER_ROLES.SUPER_ADMIN.toLowerCase() ? null : `Welcome to ${user?.created_clinics?.[0]?.name}`} diff --git a/src/layouts/mainLayout/components/ProfileSection.jsx b/src/layouts/mainLayout/components/ProfileSection.jsx index 29b8d19..d9e7971 100644 --- a/src/layouts/mainLayout/components/ProfileSection.jsx +++ b/src/layouts/mainLayout/components/ProfileSection.jsx @@ -16,6 +16,7 @@ import { useTheme } from '@mui/material/styles'; // assets import signoutImg from '../../../assets/images/icon/signout.svg'; +import phoneImg from '../../../assets/images/icon/phone.svg'; import { useStyles } from '../mainLayoutStyles'; import MainCard from './MainCard'; @@ -38,7 +39,7 @@ const ProfileSection = () => { const anchorRef = useRef(null); const user = useSelector((state) => state?.login?.user); - const isBsAdmin = user?.isBsAdmin; + const isSuperAdmin = user?.isSuperAdmin; const companyStatus = useSelector( (state) => state?.login?.user?.company?.status @@ -66,7 +67,8 @@ const ProfileSection = () => { const menuItems = [ - { id: 5, img: signoutImg, text: 'Sign Out', alt: 'signoutImg' }, + { id: 1, img: phoneImg, text: 'Contact Us', alt: 'contactUsImg' }, + { id: 2, img: signoutImg, text: 'Sign Out', alt: 'signoutImg' }, ].filter(Boolean); const renderProfile = (item, index) => ( @@ -76,6 +78,7 @@ const ProfileSection = () => { handleMenuItemClick(item)}> { const handleMenuItemClick = (item) => { switch (item.id) { case 1: - navigate('/profile-settings'); + // navigate('/contact-us'); break; case 2: - navigate(`/profile`); + commonLogoutFunc(); break; case 3: // setShowTransactionHistoryPopup(true); diff --git a/src/layouts/mainLayout/components/Sidebar.jsx b/src/layouts/mainLayout/components/Sidebar.jsx index 56ef306..dc9fd49 100644 --- a/src/layouts/mainLayout/components/Sidebar.jsx +++ b/src/layouts/mainLayout/components/Sidebar.jsx @@ -34,7 +34,7 @@ import { SIDEBAR_CONFIG } from "./sideBarConfig"; // Adjust path if necessary const Sidebar = ({ onClose, showCloseIcon }) => { const classes = useStyles(); const location = useLocation(); - const { isBSAdmin } = isBSPortal(); + const { isSuperAdmin } = isBSPortal(); const [activeLink, setActiveLink] = useState("Dashboard"); const [accordianActiveLink, setAccordianActiveLink] = useState("All Jobs"); const [parentRoute, setParentRoute] = useState(""); @@ -92,13 +92,13 @@ const Sidebar = ({ onClose, showCloseIcon }) => { (companyStatus === CLINIC_STATUS.APPROVED && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path)) || - (isBSAdmin && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path)); + (isSuperAdmin && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path)); // Only render if user has the required role if (hasRole) { // Determine if the link should be disabled const isDisabled = - (!isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature; + (!isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature; const targetPath = isDisabled ? "#" : `/${item.path}`; const isActive = activeLink === item.path; // Check if this link is the active one @@ -239,7 +239,7 @@ const Sidebar = ({ onClose, showCloseIcon }) => { {visibleChildren.map((subItem, subIndex) => { // Determine if the sub-item link should be disabled const isSubDisabled = - !isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED; // Add hideFeature logic if needed for sub-items + !isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED; // Add hideFeature logic if needed for sub-items const subTargetPath = isSubDisabled ? "#" : `/${subItem.path}`; const isSubActive = combinedRoute === subItem.path; // Check if this child link is active diff --git a/src/layouts/mainLayout/components/sideBarConfig.js b/src/layouts/mainLayout/components/sideBarConfig.js index b4c78b7..4284e25 100644 --- a/src/layouts/mainLayout/components/sideBarConfig.js +++ b/src/layouts/mainLayout/components/sideBarConfig.js @@ -9,10 +9,9 @@ import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; import SettingsIcon from '@mui/icons-material/Settings'; import ArticleIcon from '@mui/icons-material/Article'; import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined'; -import TopicIcon from '@mui/icons-material/Topic'; -import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; - -import { USER_ROLES } from '../../../redux/userRoleSlice'; +import PaymentIcon from '@mui/icons-material/Payment'; +import PaymentOutlinedIcon from '@mui/icons-material/PaymentOutlined'; +import { USER_ROLES } from '../../../constants'; // Define the sidebar configuration with proper permission fields export const SIDEBAR_CONFIG = [ @@ -48,6 +47,14 @@ export const SIDEBAR_CONFIG = [ // Only super admin can access admin staff management roles: [USER_ROLES.SUPER_ADMIN] }, + { + text: 'Payment Management', + path: 'payment-management', + icon: PaymentOutlinedIcon, + activeIcon: PaymentIcon, + // Only super admin can access payment management + roles: [USER_ROLES.SUPER_ADMIN] + }, { text: 'Doctor/Nurse Management', path: 'doctor', diff --git a/src/redux/mockUserData.js b/src/redux/mockUserData.js index e06750f..a2a4436 100644 --- a/src/redux/mockUserData.js +++ b/src/redux/mockUserData.js @@ -3,7 +3,7 @@ export const mockSuperAdmin = { id: 1, name: "Super Admin User", email: "superadmin@example.com", - isBsAdmin: true, + isSuperAdmin: true, isAdmin: true, permissions: ["SUPER_ADMIN_PERMISSION"], roles: [ @@ -25,7 +25,7 @@ export const mockClinicAdmin = { id: 2, name: "Clinic Admin User", email: "clinicadmin@example.com", - isBsAdmin: false, + isSuperAdmin: false, isAdmin: true, permissions: ["CLINIC_ADMIN_PERMISSION"], roles: [ diff --git a/src/redux/setMockUser.js b/src/redux/setMockUser.js index 9854541..f3fb216 100644 --- a/src/redux/setMockUser.js +++ b/src/redux/setMockUser.js @@ -25,7 +25,7 @@ export const setMockUser = (userType) => { }; // Export mock user types for convenience -export const MOCK_USER_TYPES = { +export const MOCK_USER_ROLES = { SUPER_ADMIN: 'superAdmin', CLINIC_ADMIN: 'clinicAdmin' }; diff --git a/src/redux/userRoleSlice.js b/src/redux/userRoleSlice.js index a9c47de..6898a07 100644 --- a/src/redux/userRoleSlice.js +++ b/src/redux/userRoleSlice.js @@ -1,9 +1,3 @@ -// User role constants -export const USER_ROLES = { - SUPER_ADMIN: 'SUPER_ADMIN', - CLINIC_ADMIN: 'CLINIC_ADMIN', -}; - // Initial state const initialState = { role: null, diff --git a/src/routes/index.js b/src/routes/index.js index a98df84..5d15106 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -12,6 +12,7 @@ import ClinicSetup from "../views/ClinicSetup"; import ClinicTranscripts from "../views/ClinicTranscripts"; import ContractManagement from "../views/ContractManagement"; import MasterDataManagement from "../views/MasterData"; +import PaymentManagement from "../views/PaymentManagement"; export const routesData = [ { @@ -26,6 +27,7 @@ export const routesData = [ { path: "/clinicSetup", component: ClinicSetup }, { path: "/transcripts", component: ClinicTranscripts }, { path: "/masterData", component: MasterDataManagement }, + { path: "/payment-management", component: PaymentManagement }, ], isProtected: true, }, diff --git a/src/routes/withPermission.jsx b/src/routes/withPermission.jsx index 029a684..caba52d 100644 --- a/src/routes/withPermission.jsx +++ b/src/routes/withPermission.jsx @@ -9,11 +9,11 @@ const withPermission = (Component) => (props) => { const companyStatus = useSelector( (state) => state?.login?.user?.company?.status ); - const { isBSAdmin } = false; - // const { isBSAdmin } = isBSPortal(); + const { isSuperAdmin } = false; + // const { isSuperAdmin } = isBSPortal(); // If the user is a BS Admin, render the component without any checks - if (isBSAdmin === true) { + if (isSuperAdmin === true) { return ; } diff --git a/src/services/auth.services.js b/src/services/auth.services.js new file mode 100644 index 0000000..e14c6f3 --- /dev/null +++ b/src/services/auth.services.js @@ -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)); + }); + }; diff --git a/src/services/clinics.service.js b/src/services/clinics.service.js index 243aecd..828ec29 100644 --- a/src/services/clinics.service.js +++ b/src/services/clinics.service.js @@ -1,45 +1,86 @@ +import { axiosInstance } from "../config/api"; import { CLINIC_TYPE } from "../constants"; import { clinicsData, registeredClinicsData } from "../mock/clinics"; +// export const getClinics = (params) => { +// switch (params.type) { +// case CLINIC_TYPE.UNREGISTERED: +// return { data: clinicsData }; +// case CLINIC_TYPE.REGISTERED: +// return { data: registeredClinicsData }; +// case CLINIC_TYPE.SUBSCRIBED: +// return { data: registeredClinicsData }; +// default: +// return { data: clinicsData }; +// } +// }; + export const getClinics = (params) => { - switch (params.type) { - case CLINIC_TYPE.UNREGISTERED: - return { data: clinicsData }; - case CLINIC_TYPE.REGISTERED: - return { data: registeredClinicsData }; - case CLINIC_TYPE.SUBSCRIBED: - return { data: registeredClinicsData }; - default: - return { data: clinicsData }; - } - }; + console.log(params); - export const getClinicsById = (id) => { - const url = `/companies/${id}`; - return new Promise((resolve, reject) => { - axiosInstance - .get(url) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; + let searchParams = new URLSearchParams(); + searchParams.append("size", params?.pagination?.pageSize ?? 10); + searchParams.append("page", params?.pagination.pageIndex ?? 0); + searchParams.append("filter_type", params?.type ?? CLINIC_TYPE.REGISTERED); + searchParams.append("search", params?.globalFilter ?? ""); - export const updateClinicStatus = (id, data) => { - const url = `/companies/${id}/company-review`; - return new Promise((resolve, reject) => { - axiosInstance - .post(url, data) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; + let url = `/clinics/?${searchParams.toString()}`; - export const getClinicsDashboardStatsById = () => { - const url = `/companies/dashboard-stats`; - return new Promise((resolve, reject) => { - axiosInstance - .get(url) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; \ No newline at end of file + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); + +}; + +export const getLatestClinicId = () => { + const url = `/clinics/latest-id`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const getClinicsById = (id) => { + const url = `/clinics/${id}`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const updateClinicStatus = (data) => { + const url = `/admin/clinic/status/`; + return new Promise((resolve, reject) => { + axiosInstance + .put(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const updateClinicDocs = (id, data) => { + const url = `/clinics/${id}`; + return new Promise((resolve, reject) => { + axiosInstance + .put(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const getClinicsDashboardStatsById = () => { + const url = `/companies/dashboard-stats`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; diff --git a/src/services/dashboard.services.js b/src/services/dashboard.services.js new file mode 100644 index 0000000..6dc7718 --- /dev/null +++ b/src/services/dashboard.services.js @@ -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)); + }); + }; \ No newline at end of file diff --git a/src/services/file.upload.services.js b/src/services/file.upload.services.js index 175d634..2d47982 100644 --- a/src/services/file.upload.services.js +++ b/src/services/file.upload.services.js @@ -23,3 +23,27 @@ export const fileUpload = (data, type = null, companyId = null) => { .catch((err) => reject(err)); }); }; + +export const getPresignedUrl = (data) => { + const url = `/s3`; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const uploadToS3 = (file, putUrl) =>{ + const url = putUrl; + return new Promise((resolve, reject) => { + axiosInstance + .put(url, file, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +} \ No newline at end of file diff --git a/src/services/jwt.services.js b/src/services/jwt.services.js new file mode 100644 index 0000000..89fc55d --- /dev/null +++ b/src/services/jwt.services.js @@ -0,0 +1,5 @@ +import { jwtDecode } from "jwt-decode"; + +export const decodeJWT = (token) => { + return jwtDecode(token); +} \ No newline at end of file diff --git a/src/utils/share.js b/src/utils/share.js index 32d65d5..139ca3d 100644 --- a/src/utils/share.js +++ b/src/utils/share.js @@ -1,6 +1,6 @@ import { logout } from "../services/users.service"; import { deleteToken } from "firebase/messaging"; -import { messaging } from "../config/firebase"; +import { getMessagingInstance } from "../config/firebase"; export const isLoggedIn = () => { let redux = JSON.parse(localStorage.getItem("redux")); @@ -27,8 +27,8 @@ export const resetLocalStorage = () => { export const isBSPortal = () => { let redux = JSON.parse(localStorage.getItem("redux")); - const isBSAdmin = redux?.login?.user?.isBsAdmin; - return { isBSAdmin }; + const isSuperAdmin = redux?.login?.user?.isSuperAdmin; + return { isSuperAdmin }; }; export const commonLogoutFunc = async (redirectPath = "/auth/login") => { diff --git a/src/views/ClinicDetails/component/FileEvaluate.jsx b/src/views/ClinicDetails/component/FileEvaluate.jsx index c6a1e27..2f8e27d 100644 --- a/src/views/ClinicDetails/component/FileEvaluate.jsx +++ b/src/views/ClinicDetails/component/FileEvaluate.jsx @@ -29,90 +29,78 @@ const FileEvaluate = ({ const [reviewedLogoFiles, setReviewedLogoFiles] = useState([]); const [notReviewedLogoFiles, setNotReviewedLogoFiles] = useState([]); const [reviewedOtherFiles, setReviewedOtherFiles] = useState([]); - const [notReviewedOtherFiles, setNotReviewedOtherFile] = useState([]); + const [notReviewedOtherFiles, setNotReviewedOtherFiles] = useState([]); - const getAscendingArray = (array) => { - const gstFiles = array.filter((file) => file.documentType === 'GST'); - const panFiles = array.filter((file) => file.documentType === 'PAN'); - const tanFiles = array.filter((file) => file.documentType === 'TAN'); - const filteredArray = []; - const maxLength = Math.max( - gstFiles.length, - panFiles.length, - tanFiles.length - ); + // const getAscendingArray = (array) => { + - for (let i = 0; i < maxLength; i++) { - if (gstFiles[i]) { - filteredArray.push(gstFiles[i]); - } - if (panFiles[i]) { - filteredArray.push(panFiles[i]); - } - if (tanFiles[i]) { - filteredArray.push(tanFiles[i]); - } - } - - return filteredArray; - }; + // return filteredArray; + // }; useEffect(() => { - // ...........reviewed logo file set............... - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - file.documentType === 'LOGO' && - (file.status === CLINIC_DOCUMENT_STATUS.APPROVED || - file.status === CLINIC_DOCUMENT_STATUS.REJECTED) - ); - setReviewedLogoFiles(filteredFiles); - } - // .............no review logo files set............ - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - file.documentType === 'LOGO' && - file.status === CLINIC_DOCUMENT_STATUS.NOT_REVIEWED - ); - setNotReviewedLogoFiles(filteredFiles); - } - // ..............reviewed other file set............ - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - (file.documentType === 'PAN' || - file.documentType === 'TAN' || - file.documentType === 'GST') && - (file.status === CLINIC_DOCUMENT_STATUS.APPROVED || - file.status === CLINIC_DOCUMENT_STATUS.REJECTED) - ); - setReviewedOtherFiles(getAscendingArray(filteredFiles)); - } - // .............. no reviewed other file set........... - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - (file.documentType === 'PAN' || - file.documentType === 'TAN' || - file.documentType === 'GST') && - file.status === CLINIC_DOCUMENT_STATUS.NOT_REVIEWED - ); - setNotReviewedOtherFile(getAscendingArray(filteredFiles)); - } + if (!Array.isArray(files)) return; + + // Process all files at once to avoid multiple iterations + const reviewedLogo = []; + const notReviewedLogo = []; + const reviewedOther = []; + const notReviewedOther = []; + + files.forEach(file => { + // Handle logo documents + if (file.logo_doc) { + if (file.logo_doc_is_verified) { + reviewedLogo.push({file: file.logo_doc, documentType: 'LOGO', isVerified: file.logo_doc_is_verified}); + } else { + notReviewedLogo.push({file: file.logo_doc, documentType: 'LOGO', isVerified: file.logo_doc_is_verified}); + } + } + + // Handle ABN and contract documents (excluding logo docs which are handled separately) + if (file.abn_doc ) { + if (file.abn_doc_is_verified) { + reviewedOther.push({file: file.abn_doc, documentType: 'ABN', isVerified: file.abn_doc_is_verified}); + } + else{ + notReviewedOther.push({file: file.abn_doc, documentType: 'ABN', isVerified: file.abn_doc_is_verified}); + } + } + + if (file.contract_doc) { + if (file.contract_doc_is_verified) { + reviewedOther.push({file: file.contract_doc, documentType: 'CONTRACT', isVerified: file.contract_doc_is_verified}); + } else{ + notReviewedOther.push({file: file.contract_doc, documentType: 'CONTRACT', isVerified: file.contract_doc_is_verified}); + } + } + }); + + // Update state with filtered files + setReviewedLogoFiles(reviewedLogo); + setNotReviewedLogoFiles(notReviewedLogo); + setReviewedOtherFiles(reviewedOther); + setNotReviewedOtherFiles(notReviewedOther); + + // Debug logs + console.log('Files processed:', files.length); + console.log('reviewedLogoFiles:', reviewedLogo); + console.log('notReviewedLogoFiles:', notReviewedLogo); + console.log('reviewedOtherFiles:', reviewedOther); + console.log('notReviewedOtherFiles:', notReviewedOther); }, [files]); // .........................get file name and extention function....................... const getFileNameUsingFile = (file) => { if (file) { - const url = new URL(file.fileURL); - return url.pathname.split('/').pop(); + // const url = new URL(file); + // return url.pathname.split('/').pop(); + return file } return; }; const getFileExtentionUsingFile = (file) => { if (file) { - const url = new URL(file.fileURL); + const url = new URL(file.file); const fileName = url.pathname.split('/').pop(); return fileName.split('.').pop(); } @@ -129,7 +117,7 @@ const FileEvaluate = ({ : CLINIC_DOCUMENT_STATUS.REJECTED, }; - if (file.documentType === 'LOGO') { + if (file.logo_doc) { const updatedFiles = [...notReviewedLogoFiles]; updatedFiles.splice(index, 1, newFile); setNotReviewedLogoFiles(updatedFiles); @@ -181,8 +169,12 @@ const FileEvaluate = ({ const handleDownload = (file) => { if (file) { const anchor = document.createElement('a'); - anchor.href = file.fileURL; - anchor.download = getFileNameUsingFile(file); + anchor.href = file.file; // file is now a direct URL + + // Extract filename from URL for download attribute + const fileName = file.file.split('/').pop(); + anchor.download = fileName; + anchor.click(); } }; @@ -200,7 +192,7 @@ const FileEvaluate = ({ return ( <> {/* ............CLINIC is Approved that time show only card............. */} - {companyStatus === CLINIC_STATUS.APPROVED ? ( + {companyStatus === CLINIC_STATUS.ACTIVE ? ( <> {reviewedLogoFiles.map((file, index) => ( @@ -217,10 +209,10 @@ const FileEvaluate = ({ CLINIC LOGO INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {companyName} LOGO @@ -228,7 +220,7 @@ const FileEvaluate = ({ @@ -251,10 +243,10 @@ const FileEvaluate = ({ CLINIC {file.documentType} INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {file.documentNumber} @@ -287,10 +279,10 @@ const FileEvaluate = ({ CLINIC LOGO INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {companyName} LOGO @@ -341,10 +333,10 @@ const FileEvaluate = ({ CLINIC {file.documentType} INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {companyName} LOGO @@ -494,17 +486,17 @@ const FileEvaluate = ({ CLINIC {file.documentType} INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} - {file.documentNumber} - - {file.documentNumber.length === GST_NUMBER_LENGTH && + */} + {/* {file.documentNumber.length === GST_NUMBER_LENGTH && file.documentType !== 'TAN' && file.documentType !== 'PAN' && ( - )} + )} */} @@ -632,17 +624,15 @@ const FileEvaluate = ({ - {getFileNameUsingFile(previewFile)} + {previewFile.split('/').pop()} handleDownload(previewFile)} classes={classes} /> diff --git a/src/views/ClinicDetails/component/GeneralInformation.jsx b/src/views/ClinicDetails/component/GeneralInformation.jsx index d8b8e4a..bdc7b22 100644 --- a/src/views/ClinicDetails/component/GeneralInformation.jsx +++ b/src/views/ClinicDetails/component/GeneralInformation.jsx @@ -9,13 +9,13 @@ import { profile } from '../../Login/loginAction'; import { setClinicId } from '../store/logInAsClinicAdminAction'; import { useStyles } from './styles/generalInformationStyles'; -const GeneralInformation = ({ companyData, companyAdminData }) => { +const GeneralInformation = ({ clinicData, clinicAdminData }) => { const dispatch = useDispatch(); const navigate = useNavigate(); const classes = useStyles(); const handleViewCompanyDashboard = async () => { - dispatch(setClinicId({ companyId: companyData?.id })); + dispatch(setClinicId({ companyId: clinicData?.id })); await dispatch(profile()); navigate('/'); }; @@ -32,13 +32,13 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { Uploaded:{' '} - {companyData?.updatedAt - ? format(new Date(companyData?.createdAt), 'dd MMM yyyy') + {clinicData?.updatedAt + ? format(new Date(clinicData?.createdAt), 'dd MMM yyyy') : ''} - {companyData?.status === CLINIC_STATUS.APPROVED && ( + {clinicData?.status === CLINIC_STATUS.APPROVED && ( { - Para Hills + {clinicData?.name} Raised On:{' '} - {companyData?.requestRaisedOn - ? format(new Date(companyData?.requestRaisedOn), 'dd MMM yyyy') + {clinicData?.update_time + ? format(new Date(clinicData?.update_time), 'dd MMM yyyy') : NOT_AVAILABLE_TEXT} @@ -74,17 +74,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { /> - Website: {companyData?.website} - - - - - - User ID: admin@gmail.com + {clinicData?.email} @@ -93,7 +83,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyAdminData?.name} + {clinicAdminData?.name} User Name @@ -101,8 +91,8 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyAdminData?.designation - ? companyAdminData?.designation + {clinicAdminData?.designation + ? clinicAdminData?.designation : NOT_AVAILABLE_TEXT} @@ -111,8 +101,8 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyAdminData?.mobile - ? `+91-${companyAdminData.mobile}` + {clinicAdminData?.phone + ? `${clinicAdminData.phone}` : NOT_AVAILABLE_TEXT} @@ -121,7 +111,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyData?.pinCode ? companyData?.pinCode : NOT_AVAILABLE_TEXT} + {clinicData?.postal_code ? clinicData?.postal_code : NOT_AVAILABLE_TEXT} Pincode @@ -133,7 +123,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyData?.street ? companyData?.street : NOT_AVAILABLE_TEXT} + {clinicData?.address ? clinicData?.address : NOT_AVAILABLE_TEXT} Full Address diff --git a/src/views/ClinicDetails/index.jsx b/src/views/ClinicDetails/index.jsx index e9810fa..7d14ecb 100644 --- a/src/views/ClinicDetails/index.jsx +++ b/src/views/ClinicDetails/index.jsx @@ -1,6 +1,6 @@ -import CloseIcon from '@mui/icons-material/Close'; -import DoneIcon from '@mui/icons-material/Done'; -import { LoadingButton } from '@mui/lab'; +import CloseIcon from "@mui/icons-material/Close"; +import DoneIcon from "@mui/icons-material/Done"; +import { LoadingButton } from "@mui/lab"; import { Box, Button, @@ -10,30 +10,31 @@ import { Tab, TextField, Typography, -} from '@mui/material'; -import { useFormik } from 'formik'; -import React, { useEffect, useRef, useState } from 'react'; -import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import onHoldDisableIcon from '../../assets/images/icon/onHoldDisable.svg'; -import onHoldEnableIcon from '../../assets/images/icon/onHoldEnable.svg'; -import CustomBreadcrumbs from '../../components/CustomBreadcrumbs'; -import Loader from '../../components/Loader'; -import PageHeader from '../../components/PageHeader'; +} from "@mui/material"; +import { useFormik } from "formik"; +import React, { useEffect, useRef, useState } from "react"; +import { Link, useLocation, useNavigate, useParams } from "react-router-dom"; +import * as Yup from "yup"; +import onHoldDisableIcon from "../../assets/images/icon/onHoldDisable.svg"; +import onHoldEnableIcon from "../../assets/images/icon/onHoldEnable.svg"; +import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; +import Loader from "../../components/Loader"; +import PageHeader from "../../components/PageHeader"; import { CLINIC_DOCUMENT_STATUS, CLINIC_STATUS, NOTIFICATION, -} from '../../constants'; +} from "../../constants"; import { getClinicsById, updateClinicStatus, -} from '../../services/clinics.service'; -import { pushNotification } from '../../utils/notification'; -import CustomModal from '../Modal/Modal'; -import { useStyles } from './clinicDetailsStyles'; +} from "../../services/clinics.service"; +import { pushNotification } from "../../utils/notification"; +import CustomModal from "../Modal/Modal"; +import { useStyles } from "./clinicDetailsStyles"; +import FileEvaluate from "./component/FileEvaluate"; -import GeneralInformation from './component/GeneralInformation'; +import GeneralInformation from "./component/GeneralInformation"; function ClinicDetails() { const classes = useStyles(); @@ -42,11 +43,12 @@ function ClinicDetails() { const navigate = useNavigate(); const queryParams = new URLSearchParams(location.search); const [isLoading, setIsLoading] = useState(false); - const [companyData, setCompanyData] = useState(''); - const [companyAdminData, setCompanyAdminData] = useState(''); + const [clinicData, setClinicData] = useState(""); + const [clinicFiles, setClinicFiles] = useState(""); + const [clinicAdminData, setClinicAdminData] = useState(""); const [isShowReasonModel, setIsShowReasonModel] = useState(false); const [updateFiles, setUpdateFiles] = useState([]); - const [buttonClickStatus, setButtonClickStatus] = useState(''); + const [buttonClickStatus, setButtonClickStatus] = useState(""); const [isRejectButtonShow, setIsRejectedButtonShow] = useState(false); const [isOnHoldButtonShow, setIsOnHoldButtonShow] = useState(false); const [isAcceptButtonShow, setIsAcceptedButtonShow] = useState(false); @@ -57,13 +59,12 @@ function ClinicDetails() { try { setIsLoading(true); const response = await getClinicsById(id); - setCompanyData(response?.data?.data); - setCompanyAdminData( - response?.data?.data?.companyUsers?.find((user) => user.isAdmin) || null - ); + setClinicData(response?.data?.data?.clinic); + setClinicFiles(response?.data?.data?.clinic_files); + setClinicAdminData(response?.data?.data?.creator); } catch (error) { // eslint-disable-next-line no-console - console.error('Error fetching data:', error); + console.error("Error fetching data:", error); } finally { setIsLoading(false); } @@ -75,17 +76,17 @@ function ClinicDetails() { // ...................breadcrumbs array........................ const breadcrumbs = [ { - label: 'Dashboard', - path: '/', + label: "Dashboard", + path: "/", }, { - label: 'Clinic List', - path: '/clinics', - query: { tab: queryParams.get('tab') || 'UNREGISTERED' }, + label: "Clinic List", + path: "/clinics", + query: { tab: queryParams.get("tab") || "UNREGISTERED" }, }, { - label: 'Clinics Details', - path: '', + label: "Clinics Details", + path: "", }, ]; @@ -93,7 +94,7 @@ function ClinicDetails() { const updateCompany = async (body) => { try { - const response = await updateClinicStatus(companyData?.id, body); + const response = await updateClinicStatus(body); if (response?.data?.error) { pushNotification(response?.data?.message, NOTIFICATION.ERROR); setIsShowReasonModel(false); @@ -105,7 +106,7 @@ function ClinicDetails() { } } catch (error) { // eslint-disable-next-line no-console - console.error('Error', error); + console.error("Error", error); } }; @@ -116,7 +117,7 @@ function ClinicDetails() { setIsOnHoldButtonShow(false); }; const defaultFormData = useRef({ - reason: '', + reason: "", }); const fieldRefs = { @@ -124,7 +125,7 @@ function ClinicDetails() { }; const validationSchema = Yup.object({ - reason: Yup.string().required('Reason is required.'), + reason: Yup.string().required("Reason is required."), }); const formik = useFormik({ initialValues: defaultFormData.current, @@ -148,9 +149,9 @@ function ClinicDetails() { }; const getNotReviewedFileCount = () => { - if (companyData?.companyDocuments) { - const notReviewedDocuments = companyData.companyDocuments.filter( - (document) => document.status === 'NOT_REVIEWED' + if (clinicData?.companyDocuments) { + const notReviewedDocuments = clinicData.companyDocuments.filter( + (document) => document.status === "NOT_REVIEWED" ); return notReviewedDocuments.length; } @@ -160,19 +161,19 @@ function ClinicDetails() { const handleCancelRejection = async () => { try { setIsLoading(true); - // const resp = await companyCancelRejection(companyData?.id); - // if (resp?.data?.error) { - // pushNotification(resp?.data?.message, NOTIFICATION.ERROR); - // fetchData(); - // } else { - // pushNotification(resp?.data?.message, NOTIFICATION.SUCCESS); - // fetchData(); - // setIsOnHoldButtonShow(false); - // setIsRejectedButtonShow(false); - // setUpdateFiles(() => []); - // setButtonClickStatus(''); - // } - console.log("cancel rejection") + // const resp = await companyCancelRejection(clinicData?.id); + // if (resp?.data?.error) { + // pushNotification(resp?.data?.message, NOTIFICATION.ERROR); + // fetchData(); + // } else { + // pushNotification(resp?.data?.message, NOTIFICATION.SUCCESS); + // fetchData(); + // setIsOnHoldButtonShow(false); + // setIsRejectedButtonShow(false); + // setUpdateFiles(() => []); + // setButtonClickStatus(''); + // } + console.log("cancel rejection"); } catch (error) { // Handle error if needed } finally { @@ -192,17 +193,17 @@ function ClinicDetails() { }); const data = { - userId: companyAdminData?.id, - reason: values ? values.reason : '', + clinic_id: clinicData?.id, + rejection_reason: values ? values.reason : "", status: - buttonClickStatus === 'Rejected' - ? CLINIC_STATUS.REJECTED - : buttonClickStatus === 'On Hold' - ? CLINIC_STATUS.ON_HOLD - : CLINIC_STATUS.APPROVED, - documentStatus: { - ...documentStatusMap, - }, + buttonClickStatus === "Rejected" + ? CLINIC_STATUS.REJECTED.toLowerCase() + : buttonClickStatus === "On Hold" + ? CLINIC_STATUS.UNDER_REVIEW.toLowerCase() + : CLINIC_STATUS.ACTIVE.toLowerCase(), + // documentStatus: { + // ...documentStatusMap, + // }, }; return data; }; @@ -210,17 +211,17 @@ function ClinicDetails() { // .................handle accept reject and on hold event............. const handleRejectClick = () => { setIsShowReasonModel(true); - setButtonClickStatus('Rejected'); + setButtonClickStatus("Rejected"); }; const handleOnHoldClick = () => { setIsShowReasonModel(true); - setButtonClickStatus('On Hold'); + setButtonClickStatus("On Hold"); }; const handleAcceptClick = () => { // setIsShowReasonModel(true); - setButtonClickStatus('Accepted'); + setButtonClickStatus("Accepted"); const body = formatedData(); updateCompany(body); @@ -229,31 +230,28 @@ function ClinicDetails() { // ..................update file use effects................ useEffect(() => { setIsRejectedButtonShow( - updateFiles.some( - (file) => file.status === CLINIC_DOCUMENT_STATUS.REJECTED - ) + clinicFiles.abn_doc_is_verified != true && + clinicFiles.contract_doc_is_verified != true ); - setIsOnHoldButtonShow( - updateFiles.some( - (file) => file.status === CLINIC_DOCUMENT_STATUS.REJECTED - ) - ); - const notReviewedFileCount = getNotReviewedFileCount(); - const areAllFilesApproved = updateFiles.every( - (file) => file.status === CLINIC_DOCUMENT_STATUS.APPROVED + setIsOnHoldButtonShow( + clinicFiles.abn_doc_is_verified != true && + clinicFiles.contract_doc_is_verified != true ); + + const areAllFilesApproved = + clinicFiles.abn_doc_is_verified == true && + clinicFiles.contract_doc_is_verified == true; + if ( - notReviewedFileCount === updateFiles.length && areAllFilesApproved && - companyData?.status !== CLINIC_STATUS.ON_HOLD && - companyData?.status !== CLINIC_STATUS.REJECTED + clinicData?.status === CLINIC_STATUS.UNDER_REVIEW.toLowerCase() ) { setIsAcceptedButtonShow(true); } else { setIsAcceptedButtonShow(false); } - }, [updateFiles]); + }, [clinicFiles]); // ..............handle table change.......... const handleTabChange = (event, newValue) => { @@ -261,7 +259,7 @@ function ClinicDetails() { }; const handleEditCompanyInfo = () => { - navigate(`/clinics/${companyData?.id}/edit`); + navigate(`/clinics/${clinicData?.id}/edit`); }; const handleSubmitClick = async () => { @@ -275,10 +273,10 @@ function ClinicDetails() { if (firstErrorRef) { // Scroll to the first invalid field smoothly - if (typeof firstErrorRef?.scrollIntoView === 'function') { + if (typeof firstErrorRef?.scrollIntoView === "function") { firstErrorRef?.scrollIntoView({ - behavior: 'smooth', - block: 'center', + behavior: "smooth", + block: "center", }); } @@ -302,7 +300,7 @@ function ClinicDetails() { pageTitle="Clinics Details" hideAddButton extraComponent={ - companyData.status === CLINIC_STATUS.APPROVED ? ( + clinicData.status === CLINIC_STATUS.ACTIVE.toLowerCase() ? ( <> Reject - {companyData?.status === CLINIC_STATUS.REJECTED && ( + {clinicData?.status === CLINIC_STATUS.REJECTED && ( - {/* {companyData.status === CLINIC_STATUS.APPROVED ? ( + {clinicData.status === CLINIC_STATUS.ACTIVE ? ( <> - {/* */} - {/* */} - {/* + + - - - - > */} - {/* ) : ( + > + ) : ( - )} */} + )} > )} + {isShowReasonModel && ( ( - {buttonClickStatus === 'Rejected' - ? 'Please define a reason for rejecting this company.' - : 'Please define a reason for putting this company on hold.'} + {buttonClickStatus === "Rejected" + ? "Please define a reason for rejecting this company." + : "Please define a reason for putting this company on hold."} - {buttonClickStatus === 'Rejected' - ? 'Reason for Rejection' - : 'Reason for On Hold'} + {buttonClickStatus === "Rejected" + ? "Reason for Rejection" + : "Reason for On Hold"} diff --git a/src/views/ClinicsList/index.jsx b/src/views/ClinicsList/index.jsx index 2f7dfe3..d879b91 100644 --- a/src/views/ClinicsList/index.jsx +++ b/src/views/ClinicsList/index.jsx @@ -11,7 +11,7 @@ import FileDownloadIcon from '@mui/icons-material/FileDownload'; import { useStyles } from "./clinicListStyles"; -import { CLINIC_STATUS, CLINIC_TYPE, PLAN_STATUS_TYPE } from "../../constants"; +import { CLINIC_STATUS, CLINIC_TYPE, NOT_AVAILABLE_TEXT, PLAN_STATUS_TYPE } from "../../constants"; import { getClinics } from "../../services/clinics.service"; import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; import Table from "../../components/Table"; @@ -115,14 +115,14 @@ const ClinicsList = () => { enableColumnFilter: false, enableSorting: true, size: 100, - accessorKey: "updatedAt", + accessorKey: "update_time", header: "Req.Raised On", Cell: ({ row }) => ( <> - {row?.original?.requestRaisedOn + {row?.original?.update_time ? format( - new Date(row.original.requestRaisedOn), + new Date(row.original.update_time), "dd MMM yyyy" ) : NOT_AVAILABLE_TEXT} @@ -196,8 +196,8 @@ const ClinicsList = () => { { Document Resubmitted - ) : status === CLINIC_STATUS.REJECTED ? ( + ) : status === CLINIC_STATUS.REJECTED.toLowerCase() ? ( "Rejected" ) : ( "" @@ -216,20 +216,20 @@ const ClinicsList = () => { } style={{ backgroundColor: - status === CLINIC_STATUS.ON_HOLD + status === CLINIC_STATUS.ON_HOLD.toLowerCase() ? theme.palette.orange.light - : status === CLINIC_STATUS.NOT_REVIEWED || - status === CLINIC_STATUS.REJECTED + : status === CLINIC_STATUS.NOT_REVIEWED.toLowerCase() || + status === CLINIC_STATUS.REJECTED.toLowerCase() ? theme.palette.primary.highlight : status === CLINIC_STATUS.APPROVAL_PENDING_DOCUMENT_RESUBMITTED ? theme.palette.primary.highlight : theme.palette.blue.light, color: - status === CLINIC_STATUS.ON_HOLD + status === CLINIC_STATUS.ON_HOLD.toLowerCase() ? theme.palette.orange.main - : status === CLINIC_STATUS.NOT_REVIEWED || - status === CLINIC_STATUS.REJECTED + : status === CLINIC_STATUS.NOT_REVIEWED.toLowerCase() || + status === CLINIC_STATUS.REJECTED.toLowerCase() ? theme.palette.primary.main : status === CLINIC_STATUS.APPROVAL_PENDING_DOCUMENT_RESUBMITTED @@ -406,9 +406,10 @@ const ClinicsList = () => { type, }; const resp = await getClinics(params); + console.log(resp) return { - data: resp?.data?.data?.records, - rowCount: resp?.data?.data?.totalCount, + data: resp?.data?.data?.data?.clinics, + rowCount: resp?.data?.data?.total, }; }; @@ -444,7 +445,8 @@ const ClinicsList = () => { }, { label: "Clinic List", - path: "", + path: "/clinics", + query: { tab: queryParams.get('tab') || 'UNREGISTERED' } }, ]; diff --git a/src/views/Dashboard/Tiles/SuperAdminTotals.jsx b/src/views/Dashboard/Tiles/SuperAdminTotals.jsx index a5709eb..0874ee6 100644 --- a/src/views/Dashboard/Tiles/SuperAdminTotals.jsx +++ b/src/views/Dashboard/Tiles/SuperAdminTotals.jsx @@ -42,7 +42,7 @@ const SuperAdminTotals = ({ isLoading, data }) => { heading={`Approved`} isLoading={isLoading} viewAllClick={() => viewAllClick(false)} - value={rejected} + value={registered} helperText={"Clinics"} color={theme.palette.grey[52]} /> @@ -52,7 +52,7 @@ const SuperAdminTotals = ({ isLoading, data }) => { heading={`Rejected`} isLoading={isLoading} viewAllClick={() => viewAllClick(false)} - value={registered} + value={rejected} helperText={"Clinics"} color={theme.palette.grey[57]} /> diff --git a/src/views/Dashboard/Tiles/Totals.jsx b/src/views/Dashboard/Tiles/Totals.jsx index 14d27dd..58f646e 100644 --- a/src/views/Dashboard/Tiles/Totals.jsx +++ b/src/views/Dashboard/Tiles/Totals.jsx @@ -5,6 +5,7 @@ import ProtectedComponent from '../../../components/ProtectedComponent'; import { getClinicsDashboardStatsById } from '../../../services/clinics.service'; import TotalNumber from '../components/TotalNumber'; import { useStyles } from '../dashboardStyles'; +import { getDashboardStats } from '../../../services/dashboard.services'; const Totals = ({ data, setData }) => { const classes = useStyles(); @@ -19,12 +20,11 @@ const Totals = ({ data, setData }) => { const fetchData = async () => { try { setIsLoading(true); - const response = {}; - // const response = await getClinicsDashboardStatsById(); + const response = await getDashboardStats(); const apiData = response?.data?.data || {}; setData({ - activeJobs: apiData.recruitmentAnalytics?.activeJobs, - totalMaxJobPostings: apiData.totalMaxJobPostings, + activeJobs: apiData?.active, + totalMaxJobPostings: apiData.inactive, }); } catch (error) { console.error('Error fetching data:', error); diff --git a/src/views/Dashboard/components/PaymentConfig.jsx b/src/views/Dashboard/components/PaymentConfig.jsx new file mode 100644 index 0000000..ed78ef2 --- /dev/null +++ b/src/views/Dashboard/components/PaymentConfig.jsx @@ -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 ( + + + + Payment Configuration + + + Configure payment details for new clinic registrations + + + + + + $, + }} + variant="outlined" + /> + + + + $, + }} + variant="outlined" + /> + + + + $, + endAdornment: /call, + }} + variant="outlined" + /> + + + + + + Save Configuration + + + + {/* Notification */} + + + {notification.message} + + + + ); +}; + +export default PaymentConfig; diff --git a/src/views/Dashboard/components/SuperAdmin.jsx b/src/views/Dashboard/components/SuperAdmin.jsx index 904a7e4..644cc3f 100644 --- a/src/views/Dashboard/components/SuperAdmin.jsx +++ b/src/views/Dashboard/components/SuperAdmin.jsx @@ -3,6 +3,8 @@ import { Box } from "@mui/system"; import React from "react"; import { useStyles } from "../dashboardStyles"; import SuperAdminTotals from "../Tiles/SuperAdminTotals"; +import PaymentConfig from "./PaymentConfig"; +import { getDashboardStats } from "../../../services/dashboard.services"; const SuperAdmin = () => { const classes = useStyles(); @@ -17,22 +19,14 @@ const SuperAdmin = () => { const fetchDashboardStats = async () => { try { setIsLoading(true); - // const response = await getAdminDashboardStats(); - // const apiData = response?.data?.data || {}; - // setData({ - // totalAccounts: apiData?.dashboardStatistics?.totalAccounts, - // registrationRequest: apiData?.dashboardStatistics?.registrationRequest, - // rejected: apiData?.dashboardStatistics?.rejected, - // registered: apiData?.dashboardStatistics?.registered, - // }); - setTimeout(() => { + const response = await getDashboardStats(); + const apiData = response?.data?.data || {}; setData({ - totalAccounts: 10, - registrationRequest: 5, - rejected: 2, - registered: 8, + totalAccounts: apiData?.totalClinics, + registrationRequest: apiData?.totalUnderReviewClinics, + rejected: apiData?.totalRejectedClinics, + registered: apiData?.totalActiveClinics, }); - }, 1000); } catch (error) { console.error("Error fetching data:", error); } finally { @@ -53,6 +47,11 @@ const SuperAdmin = () => { + + {/* Payment Configuration Section */} + + + ); }; diff --git a/src/views/Dashboard/index.jsx b/src/views/Dashboard/index.jsx index eec9a36..fc24aca 100644 --- a/src/views/Dashboard/index.jsx +++ b/src/views/Dashboard/index.jsx @@ -2,15 +2,12 @@ import React, { useEffect, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import Loader from "../components/Loader"; import ThankYou from "../ThankYou/"; -import { Box, Typography } from '@mui/material'; +import { Box, Typography } from "@mui/material"; import SuperAdmin from "./components/SuperAdmin"; -import { - selectUserRole, - setUserRole, - USER_ROLES, -} from "../../redux/userRoleSlice"; +import { selectUserRole, setUserRole } from "../../redux/userRoleSlice"; import Totals from "./Tiles/Totals"; -import { useStyles } from './dashboardStyles'; +import { useStyles } from "./dashboardStyles"; +import { USER_ROLES } from "../../constants"; function Dashboard() { const classes = useStyles(); @@ -36,12 +33,10 @@ function Dashboard() { useEffect(() => { // Determine user role based on user data from login reducer if (user) { - // Check if user is a super admin // This logic can be adjusted based on your specific role criteria - const isSuperAdmin = user.isBsAdmin || - (user.isAdmin && user.permissions?.includes("SUPER_ADMIN_PERMISSION")); - // Set the appropriate role in Redux + const isSuperAdmin = + user.userType == USER_ROLES.SUPER_ADMIN.toLowerCase(); if (isSuperAdmin) { dispatch(setUserRole(USER_ROLES.SUPER_ADMIN)); } else { @@ -61,41 +56,15 @@ function Dashboard() { {/* */} - - - - Dashboard - - - + + + + Dashboard + - {/* - - setPostAJobModal(true)} - isJobPostAvailable={data?.remainingJobPosts > 0} - /> - - {hasSubscriptionManagementPermission && ( - - - - )} - {postAJobModal && ( - setPostAJobModal(false)} - > - )} - */} + + ); diff --git a/src/views/Login/LoginForm.jsx b/src/views/Login/LoginForm.jsx index 762f45f..f06059e 100644 --- a/src/views/Login/LoginForm.jsx +++ b/src/views/Login/LoginForm.jsx @@ -14,7 +14,7 @@ import React, { useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import * as Yup from 'yup'; -import { messaging } from '../../config/firebase'; +import { initializeMessaging } from '../../config/firebase'; import { NOTIFICATION } from '../../constants'; import { pushNotification } from '../../utils/notification'; import { @@ -64,9 +64,6 @@ function LoginForm() { // Show success notification pushNotification('Login successful', NOTIFICATION.SUCCESS); - - formik.setSubmitting(false); - // Navigate to dashboard immediately without waiting for profile navigate('/'); } catch (error) { diff --git a/src/views/Login/loginAction.js b/src/views/Login/loginAction.js index 8eda59c..a09e902 100644 --- a/src/views/Login/loginAction.js +++ b/src/views/Login/loginAction.js @@ -1,106 +1,106 @@ import { axiosInstance } from '../../config/api'; import { LOGIN, PROFILE } from './loginActionTypes'; -// export const login = (data) => ({ -// type: LOGIN, -// payload: axiosInstance.post('/auth/companies/login', data), -// }); +export const login = (data) => ({ + type: LOGIN, + payload: axiosInstance.post('/auth/login', data), +}); -// export const profile = () => ({ -// type: PROFILE, -// payload: axiosInstance.get('/me'), -// }); +export const profile = () => ({ + type: PROFILE, + payload: axiosInstance.get('/users/me'), +}); -export const login = (data) => { - // Determine user type based on email - const isSuperAdmin = data.email === 'admin@gmail.com'; - const userRole = isSuperAdmin ? 'SUPER_ADMIN' : 'CLINIC_ADMIN'; - const userId = isSuperAdmin ? 'super-admin-123' : 'clinic-admin-123'; +// export const login = (data) => { +// // Determine user type based on email +// const isSuperAdmin = data.email === 'admin@gmail.com'; +// const userRole = isSuperAdmin ? 'SUPER_ADMIN' : 'CLINIC_ADMIN'; +// const userId = isSuperAdmin ? 'super-admin-123' : 'clinic-admin-123'; - // Store token and user type in localStorage to simulate a real login - localStorage.setItem('token', `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`); - localStorage.setItem('userType', userRole); +// // Store token and user type in localStorage to simulate a real login +// localStorage.setItem('token', `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`); +// localStorage.setItem('userType', userRole); - // Mock successful login response - const mockLoginResponse = { - data: { - message: 'Login successful', - token: `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`, - user: { - id: userId, - email: data.email, - role: userRole - } - } - }; +// // Mock successful login response +// const mockLoginResponse = { +// data: { +// message: 'Login successful', +// token: `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`, +// user: { +// id: userId, +// email: data.email, +// role: userRole +// } +// } +// }; - return { - type: LOGIN, - // Use a real Promise with a slight delay to simulate network request - payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300)) - }; -}; +// return { +// type: LOGIN, +// // Use a real Promise with a slight delay to simulate network request +// payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300)) +// }; +// }; -export const profile = () => { - // Get user type from localStorage to determine which profile to return - const userType = localStorage.getItem('userType') || 'CLINIC_ADMIN'; - const isSuperAdmin = userType === 'SUPER_ADMIN'; +// export const profile = () => { +// // Get user type from localStorage to determine which profile to return +// const userType = localStorage.getItem('userType') || 'CLINIC_ADMIN'; +// const isSuperAdmin = userType === 'SUPER_ADMIN'; - // Create appropriate mock profile based on user type - let profileData; +// // Create appropriate mock profile based on user type +// let profileData; - if (isSuperAdmin) { - // Super Admin profile - profileData = { - id: 'super-admin-123', - name: 'Super Administrator', - email: 'admin@gmail.com', - role: 'SUPER_ADMIN', - isAdmin: true, - isBsAdmin: true, // This flag identifies super admin - permissions: [ - 'CREATE_REC_USERS', - 'READ_REC_USERS', - 'DELETE_REC_USERS', - 'SUPER_ADMIN_PERMISSION' - ] - }; - } else { - // Clinic Admin profile - profileData = { - id: 'clinic-admin-123', - name: 'Clinic Administrator', - email: 'admin@clinic.com', - role: 'CLINIC_ADMIN', - isAdmin: true, - isBsAdmin: false, - permissions: [ - 'CREATE_REC_USERS', - 'READ_REC_USERS', - 'DELETE_REC_USERS' - ], - clinic: { - id: 'clinic-123', - name: 'Health Plus Clinic', - address: '123 Medical Center Blvd', - city: 'Healthcare City', - state: 'HC', - zipCode: '12345', - phone: '555-123-4567' - } - }; - } +// if (isSuperAdmin) { +// // Super Admin profile +// profileData = { +// id: 'super-admin-123', +// name: 'Super Administrator', +// email: 'admin@gmail.com', +// role: 'SUPER_ADMIN', +// isAdmin: true, +// isSuperAdmin: true, // This flag identifies super admin +// permissions: [ +// 'CREATE_REC_USERS', +// 'READ_REC_USERS', +// 'DELETE_REC_USERS', +// 'SUPER_ADMIN_PERMISSION' +// ] +// }; +// } else { +// // Clinic Admin profile +// profileData = { +// id: 'clinic-admin-123', +// name: 'Clinic Administrator', +// email: 'admin@clinic.com', +// role: 'CLINIC_ADMIN', +// isAdmin: true, +// isSuperAdmin: false, +// permissions: [ +// 'CREATE_REC_USERS', +// 'READ_REC_USERS', +// 'DELETE_REC_USERS' +// ], +// clinic: { +// id: 'clinic-123', +// name: 'Health Plus Clinic', +// address: '123 Medical Center Blvd', +// city: 'Healthcare City', +// state: 'HC', +// zipCode: '12345', +// phone: '555-123-4567' +// } +// }; +// } - // Create the response with the nested data structure the reducer expects - const mockProfileResponse = { - data: { - data: profileData - } - }; +// // Create the response with the nested data structure the reducer expects +// const mockProfileResponse = { +// data: { +// data: profileData +// } +// }; - return { - type: PROFILE, - // Use a real Promise with a slight delay to simulate network request - payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300)) - }; -}; +// return { +// type: PROFILE, +// // Use a real Promise with a slight delay to simulate network request +// payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300)) +// }; +// }; diff --git a/src/views/Login/loginReducer.js b/src/views/Login/loginReducer.js index 50043bf..dc29ed3 100644 --- a/src/views/Login/loginReducer.js +++ b/src/views/Login/loginReducer.js @@ -9,11 +9,13 @@ import { } from './loginActionTypes'; const initialState = {}; + const loginPending = (state) => ({ ...state, }); -const loginFulfilled = (state) => ({ +const loginFulfilled = (state, payload) => ({ ...state, + token: payload?.payload?.data?.data, }); const loginRejected = (state) => ({ ...state, @@ -39,7 +41,7 @@ const profileFulfilled = (state, payload) => { ); }); // Check if user is admin, add additional permissions - if (user.isAdmin && !user?.isBsAdmin) { + if (user.isAdmin && !user?.isSuperAdmin) { userPermissions.push( 'CREATE_REC_USERS', 'READ_REC_USERS', diff --git a/src/views/MasterData/index.jsx b/src/views/MasterData/index.jsx index 3b470be..cf3cd23 100644 --- a/src/views/MasterData/index.jsx +++ b/src/views/MasterData/index.jsx @@ -1,9 +1,9 @@ -import AddIcon from '@mui/icons-material/Add'; -import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; -import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; -import CloseIcon from '@mui/icons-material/Close'; -import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from "@mui/icons-material/Add"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; +import CloseIcon from "@mui/icons-material/Close"; +import PersonAddIcon from "@mui/icons-material/PersonAdd"; +import SearchIcon from "@mui/icons-material/Search"; import { Alert, Box, @@ -26,329 +26,328 @@ import { TableRow, TextField, Typography, -} from '@mui/material'; -import React, { useState } from 'react'; -import CustomBreadcrumbs from '../../components/CustomBreadcrumbs'; -import PageHeader from '../../components/PageHeader'; +} from "@mui/material"; +import React, { useState } from "react"; +import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; +import PageHeader from "../../components/PageHeader"; const MasterDataManagement = () => { - // State for form fields - const [appointmentType, setAppointmentType] = useState(''); - const queryParams = new URLSearchParams(location.search); - - // State for staff list - const [staffList, setStaffList] = useState([]); - - // State for dialog - const [openDialog, setOpenDialog] = useState(false); - - // State for search - const [searchQuery, setSearchQuery] = useState(''); - - // State for pagination - const [page, setPage] = useState(1); - const rowsPerPage = 10; - - // State for notification - const [notification, setNotification] = useState({ - open: false, - message: '', - severity: 'success', - }); - - // Handle dialog open/close - const handleOpenDialog = () => { - setOpenDialog(true); - }; - - const handleCloseDialog = () => { - setOpenDialog(false); - // Clear form - setAppointmentType(''); - }; - - // Handle form submission - const handleSubmit = (e) => { - e.preventDefault(); - - // Add new staff member - const newStaff = { - id: staffList.length + 1, - appointmentType, - }; - - setStaffList([...staffList, newStaff]); - - // Close dialog - handleCloseDialog(); - - // Show success notification - setNotification({ - open: true, - message: 'Staff member added successfully!', - severity: 'success', - }); - }; - - // Handle notification close - const handleCloseNotification = () => { - setNotification({ - ...notification, - open: false, - }); - }; - - // ...................breadcrumbs array........................ - const breadcrumbs = [ - { - label: 'Dashboard', - path: '/', - }, - { - label: 'Master Data Management', - path: '/masterData', - }, - ]; - - return ( - + // State for form fields + const [appointmentType, setAppointmentType] = useState(""); + const queryParams = new URLSearchParams(location.search); - - - - - - {/* Staff List Header with Add Button */} - { + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + // Clear form + setAppointmentType(""); + }; + + // Handle form submission + const handleSubmit = (e) => { + e.preventDefault(); + + // Add new staff member + const newStaff = { + id: staffList.length + 1, + appointmentType, + }; + + setStaffList([...staffList, newStaff]); + + // Close dialog + handleCloseDialog(); + + // Show success notification + setNotification({ + open: true, + message: "Staff member added successfully!", + severity: "success", + }); + }; + + // Handle notification close + const handleCloseNotification = () => { + setNotification({ + ...notification, + open: false, + }); + }; + + // ...................breadcrumbs array........................ + const breadcrumbs = [ + { + label: "Dashboard", + path: "/", + }, + { + label: "Master Data Management", + path: "/masterData", + }, + ]; + + return ( + + + + + + + {/* Staff List Header with Add Button */} + + + Master Appointment Type List + + + } + onClick={handleOpenDialog} sx={{ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - p: 3, + borderRadius: 50, + textTransform: "none", + backgroundColor: "#ff3366", + "&:hover": { + backgroundColor: "#e61653", + }, }} > - - Master Appointment Type List - - + Add Appointment Type + + + + {/* Search Box */} + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{ + backgroundColor: "#fff", + "& .MuiOutlinedInput-root": { + borderRadius: 2, + }, + }} + /> + + + {/* Staff List Table */} + + + + + Sr. No. + + Appointment Type + + + + + {staffList.length > 0 ? ( + staffList + .filter((staff) => + `${staff.appointmentType}` + .toLowerCase() + .includes(searchQuery.toLowerCase()) + ) + .slice((page - 1) * rowsPerPage, page * rowsPerPage) + .map((staff, index) => ( + + + {(page - 1) * rowsPerPage + index + 1} + + {staff.appointmentType} + + )) + ) : ( + + + No Appointment Type added yet + + + )} + + + + + {/* Pagination */} + {staffList.length > 0 && ( + + setPage((prev) => Math.max(prev - 1, 1))} + disabled={page === 1} + startIcon={} + sx={{ mx: 1, color: "#666" }} + > + Previous + + } - onClick={handleOpenDialog} + disableElevation sx={{ - borderRadius: 50, - textTransform: 'none', - backgroundColor: '#ff3366', - '&:hover': { - backgroundColor: '#e61653', + mx: 1, + minWidth: "36px", + backgroundColor: "#f0f0f0", + color: "#333", + "&:hover": { + backgroundColor: "#e0e0e0", }, }} > - Add Appointment Type + {page} + + + setPage((prev) => prev + 1)} + disabled={page * rowsPerPage >= staffList.length} + endIcon={} + sx={{ mx: 1, color: "#666" }} + > + Next - - {/* Search Box */} - + )} + + + + + {/* Add Staff Dialog */} + + + + + + Add New Appointment Type + + + + + + + + + + + setSearchQuery(e.target.value)} - InputProps={{ - startAdornment: ( - - - - ), - }} - sx={{ - backgroundColor: '#fff', - '& .MuiOutlinedInput-root': { - borderRadius: 2, - }, + margin="normal" + value={appointmentType} + onChange={(e) => setAppointmentType(e.target.value)} + placeholder="Appointment Type" + required + InputLabelProps={{ + shrink: true, }} /> - - {/* Staff List Table */} - - - - - Sr. No. - Appointment Type - - - - {staffList.length > 0 ? ( - staffList - .filter( - (staff) => - `${staff.appointmentType}` - .toLowerCase() - .includes(searchQuery.toLowerCase()) - ) - .slice((page - 1) * rowsPerPage, page * rowsPerPage) - .map((staff, index) => ( - - - {(page - 1) * rowsPerPage + index + 1} - - {staff.appointmentType} - - )) - ) : ( - - - No Appointment Type added yet - - - )} - - - - - {/* Pagination */} - {staffList.length > 0 && ( - - setPage((prev) => Math.max(prev - 1, 1))} - disabled={page === 1} - startIcon={} - sx={{ mx: 1, color: '#666' }} - > - Previous - - - - {page} - - - setPage((prev) => prev + 1)} - disabled={page * rowsPerPage >= staffList.length} - endIcon={} - sx={{ mx: 1, color: '#666' }} - > - Next - - - )} - - - {/* Add Staff Dialog */} - - + + + - - - - Add New Appointment Type - - - - - - - - - - - - setAppointmentType(e.target.value)} - placeholder="Appointment Type" - required - InputLabelProps={{ - shrink: true, - }} - /> - - - - - - Add Appointment Type - - - - - {/* Notification */} - + + + + {/* Notification */} + + - - {notification.message} - - - - ); + {notification.message} + + + + ); }; export default MasterDataManagement; diff --git a/src/views/MockPayment/index.jsx b/src/views/MockPayment/index.jsx index 46703ac..4608e26 100644 --- a/src/views/MockPayment/index.jsx +++ b/src/views/MockPayment/index.jsx @@ -227,59 +227,7 @@ const PaymentPage = () => { } /> - - {paymentMethod === 'card' && ( - - - setCardNumber(formatCardNumber(e.target.value)) - } - placeholder="1234 5678 9012 3456" - inputProps={{ maxLength: 19 }} - /> - - - - - setCardExpiry(formatExpiry(e.target.value)) - } - placeholder="MM/YY" - inputProps={{ maxLength: 5 }} - /> - - - setCardCVC(e.target.value)} - placeholder="123" - inputProps={{ maxLength: 3 }} - /> - - - - setName(e.target.value)} - placeholder="John Smith" - /> - - )} - + {paymentMethod === 'net_banking' && ( { return; } - if (!messaging) return; - try { - const currentToken = await getToken(messaging, { + // Initialize messaging if not already initialized + const messagingInstance = await initializeMessaging(); + if (!messagingInstance) { + console.log('Firebase messaging is not supported in this browser'); + return; + } + + const currentToken = await getToken(messagingInstance, { vapidKey: FB_VAPID_KEY, }); + if (currentToken) { await saveFcmToken({ token: currentToken, diff --git a/src/views/PaymentManagement/index.jsx b/src/views/PaymentManagement/index.jsx new file mode 100644 index 0000000..61a6405 --- /dev/null +++ b/src/views/PaymentManagement/index.jsx @@ -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 ( + + + + + + + {/* Payment Management Header with Add Button */} + + + Payment Configurations + + + } + onClick={() => setPaymentDialogOpen(true)} + sx={{ + borderRadius: 50, + textTransform: "none", + backgroundColor: "#ff3366", + "&:hover": { + backgroundColor: "#e61653", + }, + }} + > + Add Payment + + + + {/* Payment List Table */} + + + + + Clinic Name + Setup Fee Waived + Special Offer + Configuration Month + Actions + + + + {paymentsList.length > 0 ? ( + paymentsList.map((payment, index) => ( + + {payment.clinicName} + {payment.setupFeeWaived ? "Yes" : "No"} + {payment.specialOffer ? "Yes" : "No"} + + {payment.specialOffer ? payment.configurationMonth : "-"} + + + handleEditPayment(index)} + > + Edit + + handleDeletePayment(index)} + > + Delete + + + + )) + ) : ( + + + No payment configurations found + + + )} + + + + + + {/* Payment Dialog */} + setPaymentDialogOpen(false)} + maxWidth="sm" + fullWidth + > + + {editPaymentIndex !== null ? "Edit Payment" : "Add New Payment"} + setPaymentDialogOpen(false)} + sx={{ + position: 'absolute', + right: 8, + top: 8, + color: (theme) => theme.palette.grey[500], + }} + > + + + + + + setPaymentData({...paymentData, clinicName: e.target.value})} + sx={{ mb: 2 }} + /> + + setPaymentData({...paymentData, setupFeeWaived: e.target.checked})} + /> + } + label="Setup fee waived" + sx={{ mb: 1 }} + /> + + setPaymentData({...paymentData, specialOffer: e.target.checked})} + /> + } + label="Special Offer: First 3 months free" + sx={{ mb: 1 }} + /> + + {paymentData.specialOffer && ( + setPaymentData({...paymentData, configurationMonth: e.target.value})} + sx={{ mt: 1 }} + > + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((month) => ( + + {month} + + ))} + + )} + + + + setPaymentDialogOpen(false)} color="primary"> + Cancel + + + Save + + + + + {/* Notification Snackbar */} + + + {notification.message} + + + + ); +}; + +export default PaymentManagement; \ No newline at end of file diff --git a/src/views/Signup/YourDetailsForm.jsx b/src/views/Signup/YourDetailsForm.jsx index 1d4f127..04f436a 100644 --- a/src/views/Signup/YourDetailsForm.jsx +++ b/src/views/Signup/YourDetailsForm.jsx @@ -18,6 +18,7 @@ import { Select, TextField, Toolbar, + Tooltip, Typography, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; @@ -54,7 +55,11 @@ import { ONLY_ALPHA_NUMERIC_ACCEPT_REGEX, WEBSITE_REGEX, } from "../../constants"; -import { fileUpload } from "../../services/file.upload.services"; +import { + fileUpload, + getPresignedUrl, + uploadToS3, +} from "../../services/file.upload.services"; import { pushNotification } from "../../utils/notification"; import { passwordLengthRegex, @@ -64,30 +69,44 @@ import { } from "../../utils/regex"; import { capitalizeAllLetters, capitalizeFirstLetter } from "../../utils/share"; import PasswordValidation from "../Login/component/PasswordValidation"; -import { updateFormDetails } from "./signupAction"; +import { resetFormData, updateFormDetails } from "./signupAction"; import { useStyles } from "./styles/signupStyles"; +import { getLatestClinicId } from "../../services/clinics.service"; +import { signup } from "../../services/auth.services"; +import { decodeJWT } from "../../services/jwt.services"; function YourDetailsForm() { const classes = useStyles(); const dispatch = useDispatch(); const navigate = useNavigate(); const theme = useTheme(); + const [latestClinicID, setLatestClinicID] = useState(0); const [otpField, setOtpField] = useState(false); const [isLoading, setIsLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [billingUsers, setBillingUsers] = useState([]); const [drawerOpen, setDrawerOpen] = useState(false); const [localityOption, setLocalityOption] = useState([]); const [countryOption, setCountryOption] = useState(["Australia", "India"]); const [pincodeData, setPincodeData] = useState(); const selectedLocalityRef = useRef(); + const [testConnection, setTestConnDone] = useState(false); + + useEffect(() => { + setIsLoading(true); + getLatestClinicId().then((res) => { + setLatestClinicID(res?.data?.data); + }); + setIsLoading(false); + }, []); // Form data from Redux const yourDetailsFormData = useSelector((state) => state.signup); // Logo state variables const logoRef = useRef(null); + const testConnectionRef = useRef(null); + const otpButtonRef = useRef(null); const fileInputRef = useRef(null); const [aspect, setAspect] = useState(1); const [logoname, setLogoname] = useState(); @@ -109,7 +128,10 @@ function YourDetailsForm() { { id: "91", name: "+91" }, ]; - const integrationOptions = ["BP Software", "Medical Director"]; + const integrationOptions = [ + { id: "bp", name: "BP Software" }, + { id: "medical_director", name: "Medical Director" }, + ]; // Default form data const defaultFormData = useRef({ @@ -120,26 +142,25 @@ function YourDetailsForm() { mobileNumber: "", mobilePrefix: "", confirmPassword: "", - currentEmail: "", otp: "", // Clinic details companyName: "", designation: "", + businessPhonePrefix: "", businessPhone: "", emergencyBusinessPhone: "", emergencyBusinessPhonePrefix: "", - businessPhonePrefix: "", businessFax: "", - companyLogo: "", - companyIndustry: "", + clinicLogo: "", + businessEmail: "", pincode: "", state: "", locality: "", country: "", fullAddress: "", - companyPANImage: "", - companyPANImage: "", + companyABNImageNumber: "", + companyABNImage: "", termsAccepted: "", practiceManagementSystem: "", practiceId: "", @@ -167,18 +188,18 @@ function YourDetailsForm() { emergencyBusinessPhone: useRef(null), emergencyBusinessPhonePrefix: useRef(null), businessFax: useRef(null), + businessEmail: useRef(null), practiceManagementSystem: useRef(null), practiceId: useRef(null), practiceName: useRef(null), designation: useRef(null), - companyIndustry: useRef(null), pincode: useRef(null), state: useRef(null), locality: useRef(null), country: useRef(null), fullAddress: useRef(null), - companyPANImage: useRef(null), - companyTANNumber: useRef(null), + companyABNImageNumber: useRef(null), + companyABNImage: useRef(null), // contract management contract: useRef(null), @@ -186,14 +207,10 @@ function YourDetailsForm() { if (yourDetailsFormData) { defaultFormData.current = { ...yourDetailsFormData }; + // setLocalityOption([yourDetailsFormData?.locality]) + selectedLocalityRef.current = yourDetailsFormData?.locality; } - useEffect(() => { - if (yourDetailsFormData) { - setBillingUsers(yourDetailsFormData?.billingUsers); - } - }, [yourDetailsFormData]); - const validationSchema = Yup.object().shape({ // Personal details validation name: Yup.string().required("Name is required"), @@ -206,6 +223,7 @@ function YourDetailsForm() { .matches(/^[0-9]+$/, "Mobile Number must be a valid number") .min(10, "Mobile number should have 10 digits only.") .max(10, "Mobile number should have 10 digits only."), + mobilePrefix: Yup.string().required("Mobile Prefix is required"), password: Yup.string() .matches(passwordLengthRegex, "Password must be at least 8 characters") .matches(passwordLetterRegex, "Password must contain at least one letter") @@ -218,7 +236,6 @@ function YourDetailsForm() { confirmPassword: Yup.string() .oneOf([Yup.ref("password"), null], "Passwords must match") .required("The password must match"), - currentEmail: Yup.string().email("Invalid email").notRequired(), otp: Yup.string().required("OTP is required"), // Clinic details validation @@ -228,6 +245,20 @@ function YourDetailsForm() { .matches(/^[0-9]+$/, "Business Phone must be a valid number") .min(10, "Business Phone number should have 10 digits only.") .max(10, "Business Phone number should have 10 digits only."), + businessPhonePrefix: Yup.string().required( + "Business Phone Prefix is required" + ), + emergencyBusinessPhone: Yup.string() + .required("Emergency Business Phone is required") + .matches(/^[0-9]+$/, "Emergency Business Phone must be a valid number") + .min(10, "Emergency Business Phone number should have 10 digits only.") + .max(10, "Emergency Business Phone number should have 10 digits only."), + emergencyBusinessPhonePrefix: Yup.string().required( + "Emergency Business Phone Prefix is required" + ), + businessEmail: Yup.string() + .required("Business Email is required") + .email("Please enter valid Email Address"), businessFax: Yup.string() .required("Business Fax is required") .matches(/^[0-9]+$/, "Business Fax must be a valid number") @@ -239,35 +270,21 @@ function YourDetailsForm() { designation: Yup.string() .required("Designation is required") .typeError("Designation must be string"), - businessPhone: Yup.string() - .required("Clinic website is required") - .matches(WEBSITE_REGEX, "Please enter a valid URL"), - companyIndustry: Yup.string().required("Clinic Industry is required"), + pincode: Yup.string().required("Pincode is required"), state: Yup.string().required(), locality: Yup.string().required("City is required"), country: Yup.string().required("Country is required"), fullAddress: Yup.string() .required("Full Address is required") .typeError("Address must be string"), - companyPANImage: Yup.string() + companyABNImageNumber: Yup.string() .required("ABN number is required") .matches( ONLY_ALPHA_NUMERIC_ACCEPT_REGEX, "ABN Number must only contain numbers and letters" ) .length(ABN_NUMBER_LENGTH, "Enter valid ABN Number"), - companyTANNumber: Yup.string() - .required("Medicare number is required") - .matches( - ONLY_ALPHA_NUMERIC_ACCEPT_REGEX, - "Medicare Number must only contain numbers and letters" - ) - .length(MEDIICARE_NUMBER_LENGTH, "Enter valid Medicare Number"), - companyGSTImage: Yup.string().required("Clinic GST document is required"), - companyPANImage: Yup.string().required("Clinic ABN document is required"), - companyTANImage: Yup.string().required( - "Clinic MEDICARE document is required" - ), + companyABNImage: Yup.string().required("Clinic ABN document is required"), contract: Yup.string().required("Contract is required"), termsAccepted: Yup.boolean() .oneOf([true], "You must accept the terms and conditions") @@ -330,23 +347,22 @@ function YourDetailsForm() { const handleFormSubmit = async () => { dispatch(updateFormDetails(formik.values)); + const body = formatedData(formik.values); + console.log(body); - // const body = formatedData(formik.values); try { - // const response = await signup(body); - // if (response?.data?.error) { - // pushNotification(response?.data?.message, NOTIFICATION.ERROR); - // } else { - // pushNotification(response?.data?.message, NOTIFICATION.SUCCESS); - // dispatch(resetFormData()); - // navigate('/'); - // } - // pushNotification('Your request is submitted', NOTIFICATION.SUCCESS); - // dispatch(resetFormData()); - // navigate('/'); + // TODO: verify otp first + + const response = await signup(body); + if (response?.data?.error) { + pushNotification(response?.data?.message, NOTIFICATION.ERROR); + return; + } + pushNotification(response?.data?.message, NOTIFICATION.SUCCESS); + dispatch(resetFormData()); navigate("/auth/signup/payment"); } catch (error) { - // console.error('Error signing up:', error); + console.error('Error signing up:', error); } finally { formik.isSubmitting(false); } @@ -355,9 +371,7 @@ function YourDetailsForm() { // Initialize formik with a submission handler defined inline to avoid circular references const formik = useFormik({ initialValues: defaultFormData.current, - // validationSchema, - validateOnBlur: true, - validateOnChange: false, // Only validate on blur, not on every keystroke + validationSchema, onSubmit: handleFormSubmit, }); @@ -366,7 +380,7 @@ function YourDetailsForm() { if (formik?.values?.country !== defaultFormData.current.country) { formik.setFieldValue("pincode", ""); formik.setFieldValue("state", ""); - formik.setFieldValue("locality", ""); + // formik.setFieldValue("locality", ""); } }, [formik?.values?.country]); @@ -383,11 +397,11 @@ function YourDetailsForm() { useEffect(() => { if (Array.isArray(pincodeData) && pincodeData.length > 0) { formik.setFieldValue("state", pincodeData[0]?.state); - formik.setFieldValue("locality", selectedLocalityRef.current); + // formik.setFieldValue("locality", selectedLocalityRef.current); } else { setCountryOption(["Australia", "India"]); - formik.setFieldValue("state", ""); - formik.setFieldValue("locality", ""); + // formik.setFieldValue("state", ""); + // formik.setFieldValue("locality", ""); formik.setFieldError("pincode", "Invalid pincode"); } }, [pincodeData]); @@ -402,20 +416,34 @@ function YourDetailsForm() { aspect: 1 / 1, }); setLogoname(); - formik.setFieldValue("companyLogo", ""); + formik.setFieldValue("clinicLogo", ""); }; const handleFileUpload = async (value) => { - let formData = new FormData(); - formData.append("file", value); - formData.append("fileName", value?.name); try { - const data = await fileUpload(formData); - const imageUrl = data?.data?.data?.Key; - formik.setFieldValue("companyLogo", imageUrl); - return imageUrl; + // Store the file object directly in the form values + // We'll use this later for the actual upload after form submission + + const filePayload = { + folder: "assests", + file_name: value.name, + }; + + const presignedUrlResponseClinicLogo = await getPresignedUrl( + filePayload, + ); + + await uploadToS3( + value, + presignedUrlResponseClinicLogo?.data?.data?.api_url + ); + + formik.setFieldValue("clinicLogo", presignedUrlResponseClinicLogo?.data?.data?.key); + + // Return a temporary local URL for preview purposes + return URL.createObjectURL(value); } catch (error) { - pushNotification("Error while uploading file", NOTIFICATION.ERROR); + pushNotification("Error processing file", NOTIFICATION.ERROR); } }; @@ -508,7 +536,16 @@ function YourDetailsForm() { // Set uploaded file URL const setUploadedFileUrl = (documentName, fileUrl) => { - formik.setFieldValue(documentName, fileUrl); + console.log('Document Name:', documentName); + console.log('File URL:', fileUrl); + console.log('File URL Type:', typeof fileUrl); + + if (documentName && fileUrl !== undefined) { + formik.setFieldValue(documentName, fileUrl); + console.log('After setting value:', formik.values[documentName]); + } else { + console.error('Invalid parameters for setUploadedFileUrl:', { documentName, fileUrl }); + } }; // handleSaveAndNext will use formik's handleSubmit @@ -516,77 +553,66 @@ function YourDetailsForm() { // Helper function for formatted data function formatedData(inputData) { const data = { - name: inputData.companyName || "", - businessPhone: inputData.businessPhone || "", - emergencyBusinessPhone: inputData.emergencyBusinessPhone || "", - emergencyBusinessPhonePrefix: - inputData.emergencyBusinessPhonePrefix || "", - businessPhonePrefix: inputData.businessPhonePrefix || "", - businessFax: inputData.businessFax || "", - city: inputData.locality || "", - state: inputData.state || "", - logo: inputData.companyLogo || null, - street: inputData.fullAddress || "", - pinCode: inputData.pincode || "", - country: inputData.country || "", - locality: inputData.locality || "", - industryId: inputData.companyIndustry || 0, - companyAdmin: { - name: inputData.name || "", - email: inputData.email || "", - mobile: inputData.mobileNumber || "", - password: inputData.password || "", - designation: inputData.designation || "", + user: { + username: inputData.name, + email: inputData.email, + mobile: `${inputData.mobilePrefix ?? mobilePrefixOptions[0].name} ${ + inputData.mobileNumber + }`, + password: inputData.password, + clinicRole: inputData.designation, + userType: "clinic_admin", + }, + clinic: { + name: inputData.companyName, + address: inputData.fullAddress, + state: inputData.state, + phone: `${ + inputData.businessPhonePrefix ?? mobilePrefixOptions[0].name + } ${inputData.businessPhone}`, + emergencyPhone: `${ + inputData.emergencyBusinessPhonePrefix ?? mobilePrefixOptions[0].name + } ${inputData.emergencyBusinessPhone}`, + fax: inputData.businessFax, + email: inputData.businessEmail, + integration: inputData.practiceManagementSystem, + pms_id: inputData.practiceId, + practice_name: inputData.practiceName, + logo: inputData.clinicLogo || null, + country: inputData.country, + postal_code: inputData.pincode, + city: inputData.locality, + abn_number: inputData.companyABNImageNumber, + abn_doc: inputData.companyABNImage, + contract_doc: inputData.contract, }, - practiceId: inputData.practiceId || "", - practiceManagementSystem: inputData.practiceManagementSystem || "", - practiceName: inputData.practiceName || "", - companyUsers: inputData.billingUsers || "", - companyDocuments: [ - ...(inputData.companyLogo - ? [ - { - documentNumber: "LOGO", - fileURL: inputData.companyLogo, - documentType: "LOGO", - }, - ] - : []), - { - documentNumber: inputData.companyPANImage || "", - fileURL: inputData.companyPANImage || "", - documentType: "PAN", - }, - ], - contract: inputData.contract || "", }; return data; } - const handleAddUser = () => { - if ( - billingUsers?.includes(formik?.values?.currentEmail) || - formik?.values?.email === formik.values?.currentEmail - ) { - pushNotification("Email Address must be unique", NOTIFICATION.ERROR); + const handleSaveAndNext = async () => { + if (!otpField) { + pushNotification("Please verify OTP first", NOTIFICATION.ERROR); + otpButtonRef.current?.focus(); return; } - if (formik.values.currentEmail && billingUsers?.length < 5) { - setBillingUsers([...billingUsers, formik.values.currentEmail]); - formik.setFieldValue("currentEmail", ""); + + if (!testConnection) { + pushNotification("Please test the connection first", NOTIFICATION.ERROR); + // scroll to test connection button + // testConnectionRef.current?.scrollIntoView({ + // behavior: "smooth", + // }); + testConnectionRef.current?.focus(); + return; } - }; - const handleUserDelete = (index) => { - const updatedUsers = billingUsers.filter((_, i) => i !== index); - setBillingUsers(updatedUsers); - }; - - const handleSaveAndNext = async () => { const formikError = await formik.validateForm(formik.values); const errors = Object.keys(formikError); + console.log(errors) + if (errors.length) { // Find the first invalid field and focus it const firstErrorField = errors[0]; @@ -653,7 +679,7 @@ function YourDetailsForm() { className={classes.formRoot} > - + @@ -733,6 +759,7 @@ function YourDetailsForm() { color="info" disabled={!formik.values.email} onClick={handleOTPButton} + ref={otpButtonRef} > Request OTP @@ -1146,7 +1173,16 @@ function YourDetailsForm() { variant="outlined" name="emergencyBusinessPhone" value={formik.values.emergencyBusinessPhone} - onChange={formik.handleChange} + onChange={(e) => { + if (e.target.value.length <= 10) { + const value = + e.target.value?.match(/\d+/g) || ""; + formik.setFieldValue( + "emergencyBusinessPhone", + value?.toString() + ); + } + }} onBlur={formik.handleBlur} InputProps={{ type: "text", @@ -1253,16 +1289,7 @@ function YourDetailsForm() { variant="outlined" name="businessEmail" value={formik.values.businessEmail} - onChange={(e) => { - if (e.target.value.length <= 10) { - const value = - e.target.value?.match(/\d+/g) || ""; - formik.setFieldValue( - "businessEmail", - value?.toString() - ); - } - }} + onChange={formik.handleChange} onBlur={formik.handleBlur} InputProps={{ type: "email", @@ -1307,8 +1334,8 @@ function YourDetailsForm() { } > {integrationOptions.map((option) => ( - - {option} + + {option.name} ))} @@ -1372,6 +1399,7 @@ function YourDetailsForm() { variant="contained" color="primary" onClick={handleTestConnection} + ref={testConnectionRef} > Test Connection @@ -1381,7 +1409,7 @@ function YourDetailsForm() { Add Business Logo - {(logoImage || formik.values.companyLogo) && ( + {(logoImage || formik.values.clinicLogo) && ( - + { if ( logoImage || - formik.values.companyLogo + formik.values.clinicLogo ) { event.stopPropagation(); } @@ -1431,7 +1459,7 @@ function YourDetailsForm() { } > {!logoImage && - !formik.values.companyLogo ? ( + !formik.values.clinicLogo ? ( <> )} - {formik.errors.companyLogo && - formik.touched.companyLogo && ( + {formik.errors.clinicLogo && + formik.touched.clinicLogo && ( - {formik.errors.companyLogo - ? formik.errors - .companyLogo + {formik.errors.clinicLogo + ? formik.errors.clinicLogo : ""} @@ -1557,6 +1584,7 @@ function YourDetailsForm() { error={Boolean( formik.errors.country && formik.touched.country )} + helperText={formik.errors.country} > @@ -1650,6 +1678,7 @@ function YourDetailsForm() { formik.errors.locality && formik.touched.locality )} + helperText={formik.errors.locality} > @@ -1723,16 +1752,16 @@ function YourDetailsForm() { {/* PAN NUMBER GRID */} - Clinic ABN Number* + Clinic ABN/ACN Number* { if ( e.target.value.length <= ABN_NUMBER_LENGTH @@ -1745,26 +1774,26 @@ function YourDetailsForm() { }} onBlur={(e) => { formik.setFieldValue( - "companyPANImage", + "companyABNImageNumber", e.target.value.trim() ); formik.handleBlur(e); }} - inputRef={fieldRefs.companyPANImage} + inputRef={fieldRefs.companyABNImageNumber} error={Boolean( - formik.errors.companyPANImage && - formik.touched.companyPANImage + formik.errors.companyABNImageNumber && + formik.touched.companyABNImageNumber )} helperText={ - formik.errors.companyPANImage && - formik.touched.companyPANImage - ? formik.errors.companyPANImage + formik.errors.companyABNImageNumber && + formik.touched.companyABNImageNumber + ? formik.errors.companyABNImageNumber : "" } InputProps={{ endAdornment: ( - {!formik.errors.companyPANImage ? ( + {!formik.errors.companyABNImageNumber ? ( @@ -1778,23 +1807,37 @@ function YourDetailsForm() { {/* PAN image upload grid */} {/* contract grid */} - + + + + Download Template + + ({ ...state, diff --git a/src/views/User/index.jsx b/src/views/User/index.jsx index 9c26b40..0343b6d 100644 --- a/src/views/User/index.jsx +++ b/src/views/User/index.jsx @@ -80,7 +80,7 @@ function Users() { const [roles, setRoles] = useState(); const [isAdmin, setIsAdmin] = useState(); - const isBsAdmin = useSelector((state) => state?.login?.user?.isBsAdmin); + const isSuperAdmin = useSelector((state) => state?.login?.user?.isSuperAdmin); /* ----------------- Get Users ----------------- */ const getData = async (filters) => {