feat: clinic admin setup

This commit is contained in:
deepvasoya 2025-05-08 19:36:56 +05:30
parent 4717c67cca
commit 050cf206f5
11 changed files with 1088 additions and 195 deletions

View File

@ -77,13 +77,19 @@ const CustomFileUpload = forwardRef(function CustomFileUpload(
useEffect(() => { useEffect(() => {
const makeFullUrlIfNeeded = (url) => { const makeFullUrlIfNeeded = (url) => {
// Return early if url is undefined or empty
if (!url) {
setOldUploadedFileUrl('');
setFileExtension('');
setImageName('');
return;
}
const isHttp = url.startsWith('http://') || url.startsWith('https://'); const isHttp = url.startsWith('http://') || url.startsWith('https://');
if (!isHttp) { if (!isHttp) {
setOldUploadedFileUrl(url ? `${IMAGE_LOCATION_BASE_URL}${url}` : ''); setOldUploadedFileUrl(`${IMAGE_LOCATION_BASE_URL}${url}`);
setFileExtension( setFileExtension(url.split('.').pop() || '');
uploadedFileUrl ? uploadedFileUrl.split('.').pop() : '' setImageName(url.split('/').pop() || '');
);
setImageName(uploadedFileUrl ? uploadedFileUrl.split('/').pop() : '');
return; return;
} }
const urlObject = new URL(url); const urlObject = new URL(url);

View File

@ -382,30 +382,30 @@ const Table = memo(
muiSelectAllCheckboxProps={{ muiSelectAllCheckboxProps={{
className: classes?.tableCheckbox, className: classes?.tableCheckbox,
}} }}
// renderRowActionMenuItems={({ row, closeMenu }) => renderRowActionMenuItems={({ row, closeMenu }) =>
// actions?.filter(action => !action.render)?.map((action, index) => actions?.filter(action => !action.render)?.map((action, index) =>
// !(action?.renderAction?.(row) ?? true) ? null : ( !(action?.renderAction?.(row) ?? true) ? null : (
// <MenuItem <MenuItem
// key={index} key={index}
// className={classes.menuItem} className={classes.menuItem}
// onClick={(event) => { onClick={(event) => {
// event.stopPropagation(); event.stopPropagation();
// action.onClick && action.onClick(row); action.onClick && action.onClick(row);
// closeMenu(); closeMenu();
// }} }}
// disabled={ disabled={
// action?.isDisabledValue action?.isDisabledValue
// ? action?.isDisabledValue === ? action?.isDisabledValue ===
// row?.original?.[action?.rowKey] row?.original?.[action?.rowKey]
// : false : false
// } }
// > >
// {action?.icon} {action?.text}{" "} {action?.icon} {action?.text}{" "}
// {action.textFn && action.textFn(row)} {action.textFn && action.textFn(row)}
// </MenuItem> </MenuItem>
// ) )
// ) ?? [] ) ?? []
// } }
renderTopToolbarCustomActions={({ table }) => { renderTopToolbarCustomActions={({ table }) => {
const handleActive = () => { const handleActive = () => {
const data = table const data = table

View File

@ -84,6 +84,10 @@ export const NOT_ALLOWED_BLANK_SPACE_REGEX = /^(?! +$)[\s\S]+$/;
export const HEX_COLOR_CODE_REGEX = /^#([A-Fa-f0-9]{6})$/; export const HEX_COLOR_CODE_REGEX = /^#([A-Fa-f0-9]{6})$/;
export const CLINIC_GREETINGS_LENGTH = 1000;
export const CLINIC_SCENARIOS_LENGTH = 5000;
export const FILE_TYPE = [ export const FILE_TYPE = [
'.jpeg', '.jpeg',
'.jpg', '.jpg',

View File

@ -6,6 +6,7 @@ export const userDataMock = {
name: "test", name: "test",
email: "test@gmail.com", email: "test@gmail.com",
userType: "Manager", userType: "Manager",
appointmentTypes: ["Adult Immunisation", "Child Immunisation"],
specialities: "Doctor", specialities: "Doctor",
createdAt: "2025-01-01", createdAt: "2025-01-01",
status: "Active", status: "Active",
@ -15,6 +16,7 @@ export const userDataMock = {
name: "John Doe", name: "John Doe",
email: "johndoe@example.com", email: "johndoe@example.com",
userType: "Employee", userType: "Employee",
appointmentTypes: ["Adult Immunisation"],
specialities: "Engineer", specialities: "Engineer",
createdAt: "2024-11-15", createdAt: "2024-11-15",
status: "Inactive", status: "Inactive",
@ -24,6 +26,7 @@ export const userDataMock = {
name: "Jane Smith", name: "Jane Smith",
email: "janesmith@example.com", email: "janesmith@example.com",
userType: "Admin", userType: "Admin",
appointmentTypes: ["Adult Immunisation"],
specialities: "Manager", specialities: "Manager",
createdAt: "2024-12-01", createdAt: "2024-12-01",
status: "Active", status: "Active",
@ -32,6 +35,7 @@ export const userDataMock = {
id: 4, id: 4,
name: "Alice Brown", name: "Alice Brown",
email: "alicebrown@example.com", email: "alicebrown@example.com",
appointmentTypes: ["Adult Immunisation"],
userType: "Manager", userType: "Manager",
specialities: "HR", specialities: "HR",
createdAt: "2023-06-22", createdAt: "2023-06-22",
@ -51,6 +55,7 @@ export const userDataMock = {
name: "Charlie Black", name: "Charlie Black",
email: "charlieblack@example.com", email: "charlieblack@example.com",
userType: "Admin", userType: "Admin",
appointmentTypes: ["Adult Immunisation"],
specialities: "IT Support", specialities: "IT Support",
createdAt: "2023-07-05", createdAt: "2023-07-05",
status: "Inactive", status: "Inactive",
@ -60,6 +65,7 @@ export const userDataMock = {
name: "Eve White", name: "Eve White",
email: "evewhite@example.com", email: "evewhite@example.com",
userType: "Manager", userType: "Manager",
appointmentTypes: ["Adult Immunisation"],
specialities: "Finance", specialities: "Finance",
createdAt: "2025-03-30", createdAt: "2025-03-30",
status: "Active", status: "Active",

View File

@ -28,14 +28,20 @@ export const getUsers = (params) => {
}; };
export const getUserById = (id) => { export const getUserById = (id) => {
const url = `/users/${id}`; const data = userDataMock;
return new Promise((resolve, reject) => { return data.data.records.find((item) => item.id === id)
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
}; };
// export const getUserById = (id) => {
// const url = `/users/${id}`;
// return new Promise((resolve, reject) => {
// axiosInstance
// .get(url)
// .then((response) => resolve(response))
// .catch((err) => reject(err));
// });
// };
export const getRoles = ({ page }) => export const getRoles = ({ page }) =>
page == 0 page == 0
? { ? {

View File

@ -1,12 +1,12 @@
import SendIcon from '@mui/icons-material/Send'; import SendIcon from "@mui/icons-material/Send";
import Box from '@mui/material/Box'; import Box from "@mui/material/Box";
import Button from '@mui/material/Button'; import Button from "@mui/material/Button";
import FormControl from '@mui/material/FormControl'; import FormControl from "@mui/material/FormControl";
import Grid from '@mui/material/Grid'; import Grid from "@mui/material/Grid";
import InputLabel from '@mui/material/InputLabel'; import InputLabel from "@mui/material/InputLabel";
import * as React from 'react'; import * as React from "react";
import { pushNotification } from '../../utils/notification'; import { pushNotification } from "../../utils/notification";
import { NOTIFICATION_TYPE } from '../Notifications/notificationConstant'; import { NOTIFICATION_TYPE } from "../Notifications/notificationConstant";
import { import {
Dialog, Dialog,
DialogActions, DialogActions,
@ -21,33 +21,37 @@ import {
Stepper, Stepper,
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from "@mui/material";
import WarningAmberIcon from '@mui/icons-material/WarningAmber'; import WarningAmberIcon from "@mui/icons-material/WarningAmber";
import {
CLINIC_GREETINGS_LENGTH,
CLINIC_SCENARIOS_LENGTH,
} from "../../constants";
// Integration software options // Integration software options
const integrationOptions = ['BP Software', 'Medical Director']; const integrationOptions = ["BP Software", "Medical Director"];
// Steps for the stepper // Steps for the stepper
const steps = [ const steps = [
'General Information', "General Information",
'AI Receptionist Setup', "AI Receptionist Setup",
'Scenarios Setup', "Scenarios Setup",
'General Info Setup', "General Info Setup",
'Integration Settings', "Integration Settings",
]; ];
const voiceModels = [ const voiceModels = [
{ id: 'stream', name: 'Stream', gender: 'female' }, { id: "stream", name: "Stream", gender: "female" },
{ id: 'sandy', name: 'Sandy', gender: 'female' }, { id: "sandy", name: "Sandy", gender: "female" },
{ id: 'breeze', name: 'Breeze', gender: 'female' }, { id: "breeze", name: "Breeze", gender: "female" },
{ id: 'wolf', name: 'Wolf', gender: 'male' }, { id: "wolf", name: "Wolf", gender: "male" },
{ id: 'stan', name: 'Sten', gender: 'male' }, { id: "stan", name: "Sten", gender: "male" },
{ id: 'blaze', name: 'Blaze', gender: 'male' }, { id: "blaze", name: "Blaze", gender: "male" },
]; ];
const voiceModelGender = [ const voiceModelGender = [
{ id: 'male', name: 'Male' }, { id: "male", name: "Male" },
{ id: 'female', name: 'Female' }, { id: "female", name: "Female" },
]; ];
export default function ClinicSetup() { export default function ClinicSetup() {
@ -56,6 +60,7 @@ export default function ClinicSetup() {
const [openDialog, setOpenDialog] = React.useState(false); const [openDialog, setOpenDialog] = React.useState(false);
const [audioContext, setAudioContext] = React.useState(null); const [audioContext, setAudioContext] = React.useState(null);
const [testConnDone, setTestConnDone] = React.useState(false); const [testConnDone, setTestConnDone] = React.useState(false);
const [confirmPhoneNumber, setConfirmPhoneNumber] = React.useState(false);
// Completed steps state // Completed steps state
const [completed, setCompleted] = React.useState({}); const [completed, setCompleted] = React.useState({});
@ -63,13 +68,13 @@ export default function ClinicSetup() {
// Form state // Form state
const [formData, setFormData] = React.useState({ const [formData, setFormData] = React.useState({
// General Information // General Information
clinicPhone: '159875654', clinicPhone: "159875654",
clinicAddress: '1 Wilkinson Road, Para Hills SA 5096', clinicAddress: "1 Wilkinson Road, Para Hills SA 5096",
otherInfo: '', otherInfo: "",
// Greetings Setup // Greetings Setup
clinicGreetings: clinicGreetings:
'Welcome to 365 days medical center group, para hills clinic. We are here to help you.', "Welcome to 365 days medical center group, para hills clinic. We are here to help you.",
clinicScenarios: `Booking for care plan /Care plan Review clinicScenarios: `Booking for care plan /Care plan Review
Existing patients- Book with regular GP and Nurse on same day ( if not possible book different day, F2F with nurse for care plan and another days F2F consultation with Dr to sign of care plan ) Existing patients- Book with regular GP and Nurse on same day ( if not possible book different day, F2F with nurse for care plan and another days F2F consultation with Dr to sign of care plan )
New patients- At the moment we are not taking New patients on long term care but tell them they can see GP and can discuss with GP before booking for care plan . New patients- At the moment we are not taking New patients on long term care but tell them they can see GP and can discuss with GP before booking for care plan .
@ -141,12 +146,12 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
- Other pets must remain outside the building except for veterinary consultations (available Tuesdays and Thursdays)`, - Other pets must remain outside the building except for veterinary consultations (available Tuesdays and Thursdays)`,
// Integration Settings // Integration Settings
integrationSoftware: '', integrationSoftware: "",
practiceId: '', practiceId: "",
practiceName: '', practiceName: "",
// Voice Configuration // Voice Configuration
voice: '', voice: "",
voiceGender: voiceModelGender[1].id, voiceGender: voiceModelGender[1].id,
}); });
@ -157,12 +162,22 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
// reset form data to initial state // reset form data to initial state
setFormData( setFormData(
Object.keys(formData).reduce((acc, key) => { Object.keys(formData).reduce((acc, key) => {
acc[key] = ''; acc[key] = "";
return acc; return acc;
}, {}) }, {})
); );
}; };
const handleConfirmPhoneNumber = () => {
setConfirmPhoneNumber(true);
};
React.useEffect(() => {
if(formData.clinicPhone=="159875654"){
setConfirmPhoneNumber(true);
}
}, []);
React.useEffect(() => { React.useEffect(() => {
// Initialize audio context when component mounts // Initialize audio context when component mounts
function initAudioContext() { function initAudioContext() {
@ -172,8 +187,8 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
if (!window.AudioContext) { if (!window.AudioContext) {
pushNotification( pushNotification(
'Your browser does not support AudioContext and cannot play back audio.', "Your browser does not support AudioContext and cannot play back audio.",
'error' "error"
); );
return null; return null;
} }
@ -189,7 +204,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
// Cleanup function // Cleanup function
return () => { return () => {
if (context && context.state !== 'closed') { if (context && context.state !== "closed") {
context.close(); context.close();
} }
}; };
@ -209,6 +224,11 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
// Handle next button click // Handle next button click
const handleNext = () => { const handleNext = () => {
if (!confirmPhoneNumber) {
pushNotification("Please confirm phone number", "error");
return;
}
const newActiveStep = const newActiveStep =
isLastStep() && !allStepsCompleted() isLastStep() && !allStepsCompleted()
? // It's the last step, but not all steps have been completed, ? // It's the last step, but not all steps have been completed,
@ -221,30 +241,30 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
// Listen to voice using rime.ai API // Listen to voice using rime.ai API
const listenVoice = () => { const listenVoice = () => {
if (!audioContext) { if (!audioContext) {
pushNotification('Audio context not initialized', 'error'); pushNotification("Audio context not initialized", "error");
return; return;
} }
if (!formData.clinicGreetings || formData.clinicGreetings.length === 0) { if (!formData.clinicGreetings || formData.clinicGreetings.length === 0) {
pushNotification( pushNotification(
'Please enter clinic greetings to listen preview', "Please enter clinic greetings to listen preview",
'error' "error"
); );
return; return;
} }
const options = { const options = {
method: 'POST', method: "POST",
headers: { headers: {
Accept: 'audio/mp3', Accept: "audio/mp3",
Authorization: 'Bearer ElD2r2Dn9Lsl5qB5TupaGcKlWSEf2llo9CkHi2OrOOU', Authorization: "Bearer ElD2r2Dn9Lsl5qB5TupaGcKlWSEf2llo9CkHi2OrOOU",
'Content-Type': 'application/json', "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
speaker: formData.voice || 'wolf', speaker: formData.voice || "wolf",
text: formData.clinicGreetings, text: formData.clinicGreetings,
modelId: 'mist', modelId: "mist",
lang: 'eng', lang: "eng",
samplingRate: 22050, samplingRate: 22050,
speedAlpha: 1.0, speedAlpha: 1.0,
reduceLatency: false, reduceLatency: false,
@ -252,9 +272,9 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
}; };
// Show loading state // Show loading state
pushNotification('Generating audio...', 'info'); pushNotification("Generating audio...", "info");
fetch('https://users.rime.ai/v1/rime-tts', options) fetch("https://users.rime.ai/v1/rime-tts", options)
.then((response) => { .then((response) => {
// Check if the response is successful // Check if the response is successful
if (!response.ok) { if (!response.ok) {
@ -269,12 +289,12 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
arrayBuffer, arrayBuffer,
(buffer) => playAudioBuffer(buffer, audioContext), (buffer) => playAudioBuffer(buffer, audioContext),
(error) => { (error) => {
pushNotification('Failed to decode audio data', 'error'); pushNotification("Failed to decode audio data", "error");
} }
); );
}) })
.catch((err) => { .catch((err) => {
pushNotification(`Error: ${err.message}`, 'error'); pushNotification(`Error: ${err.message}`, "error");
}); });
}; };
@ -291,7 +311,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
source.start(0); source.start(0);
// Notify user // Notify user
pushNotification('Playing audio...', 'success'); pushNotification("Playing audio...", "success");
} }
// Handle back button click // Handle back button click
@ -301,28 +321,32 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
// Handle step click // Handle step click
const handleStep = (step) => () => { const handleStep = (step) => () => {
// If trying to skip ahead with errors in current step, validate first // First check if phone number is confirmed for any step navigation
if (step > activeStep) { if (!confirmPhoneNumber) {
if (validateStep()) { pushNotification(
"Please confirm phone number before navigating between steps",
"error"
);
return;
}
// Then check if the step is completed
if (completed[step]) {
setActiveStep(step); setActiveStep(step);
} else { } else {
// Alert user about validation errors // Alert user about validation errors
pushNotification( pushNotification(
'Please complete the current step before proceeding', "Please complete the current step before proceeding",
NOTIFICATION_TYPE.ERROR "error"
); );
} }
} else {
// Allow going backward anytime
setActiveStep(step);
}
}; };
// test connection // test connection
const handleTestConnection = () => { const handleTestConnection = () => {
// logic here // logic here
setTestConnDone(true); setTestConnDone(true);
pushNotification('Test connection successful', 'success'); pushNotification("Test connection successful", "success");
}; };
// Form validation state // Form validation state
@ -357,8 +381,8 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
// Validate only if software is selected // Validate only if software is selected
if (formData.integrationSoftware) { if (formData.integrationSoftware) {
const integrationErrors = { const integrationErrors = {
practiceId: formData.practiceId.trim() === '', practiceId: formData.practiceId.trim() === "",
practiceName: formData.practiceName.trim() === '', practiceName: formData.practiceName.trim() === "",
}; };
setErrors(integrationErrors); setErrors(integrationErrors);
return !Object.values(integrationErrors).some((isError) => isError); return !Object.values(integrationErrors).some((isError) => isError);
@ -387,6 +411,10 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
const handleChange = (e) => { const handleChange = (e) => {
const { name, value } = e.target; const { name, value } = e.target;
if (name === "clinicPhone" && value != formData.clinicPhone) {
setConfirmPhoneNumber(false);
}
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[name]: value, [name]: value,
@ -419,14 +447,14 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
const validateForm = () => { const validateForm = () => {
// Check all steps // Check all steps
const generalInfoValid = const generalInfoValid =
formData.clinicPhone.trim() !== '' && formData.clinicPhone.trim() !== "" &&
formData.clinicAddress.trim() !== ''; formData.clinicAddress.trim() !== "";
let integrationValid = true; let integrationValid = true;
if (formData.integrationSoftware) { if (formData.integrationSoftware) {
integrationValid = integrationValid =
formData.practiceId.trim() !== '' && formData.practiceId.trim() !== "" &&
formData.practiceName.trim() !== ''; formData.practiceName.trim() !== "";
} }
return generalInfoValid && integrationValid; return generalInfoValid && integrationValid;
@ -437,39 +465,39 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
e.preventDefault(); e.preventDefault();
if (validateForm()) { if (validateForm()) {
console.log('Form submitted:', formData); console.log("Form submitted:", formData);
// Here you would typically send the data to your backend // Here you would typically send the data to your backend
pushNotification('Clinic setup updated successfully', 'success'); pushNotification("Clinic setup updated successfully", "success");
// Reset the form after submission // Reset the form after submission
handleReset(); handleReset();
} else { } else {
// Find the first step with errors and go to it // Find the first step with errors and go to it
if ( if (
formData.clinicPhone.trim() === '' || formData.clinicPhone.trim() === "" ||
formData.clinicAddress.trim() === '' formData.clinicAddress.trim() === ""
) { ) {
setActiveStep(0); setActiveStep(0);
setErrors({ setErrors({
...errors, ...errors,
clinicPhone: formData.clinicPhone.trim() === '', clinicPhone: formData.clinicPhone.trim() === "",
clinicAddress: formData.clinicAddress.trim() === '', clinicAddress: formData.clinicAddress.trim() === "",
}); });
} else if ( } else if (
formData.integrationSoftware && formData.integrationSoftware &&
(formData.practiceId.trim() === '' || (formData.practiceId.trim() === "" ||
formData.practiceName.trim() === '') formData.practiceName.trim() === "")
) { ) {
setActiveStep(3); setActiveStep(3);
setErrors({ setErrors({
...errors, ...errors,
practiceId: formData.practiceId.trim() === '', practiceId: formData.practiceId.trim() === "",
practiceName: formData.practiceName.trim() === '', practiceName: formData.practiceName.trim() === "",
}); });
} }
// Show an alert // Show an alert
pushNotification( pushNotification(
'Please fill in all required fields before submitting', "Please fill in all required fields before submitting",
NOTIFICATION_TYPE.ERROR NOTIFICATION_TYPE.ERROR
); );
} }
@ -492,32 +520,32 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
display="flex" display="flex"
> >
<TextField <TextField
style={{ width: '40%' }} type="number"
style={{ width: "40%" }}
label="Clinic Phone Number" label="Clinic Phone Number"
name="clinicPhone" name="clinicPhone"
value={formData.clinicPhone} value={formData.clinicPhone}
onChange={handleChange} onChange={handleChange}
helperText={ helperText={
errors.clinicPhone errors.clinicPhone
? 'Phone number is required' ? "Phone number is required"
: 'Unique single number for your clinic' : "Unique single number for your clinic"
} }
variant="outlined" variant="outlined"
error={errors.clinicPhone} error={errors.clinicPhone}
/> />
<Button <Button
style={{ marginLeft: '10px' }} style={{ marginLeft: "10px" }}
variant="contained" variant="contained"
color="primary" color="primary"
startIcon={<SendIcon />} startIcon={<SendIcon />}
href="https://login.twilio.com/u/signup" onClick={handleConfirmPhoneNumber}
target="_blank"
> >
Configure Phone Number Confirm
</Button> </Button>
<> <>
<Button <Button
style={{ marginLeft: '10px' }} style={{ marginLeft: "10px" }}
variant="outlined" variant="outlined"
color="error" color="error"
startIcon={<WarningAmberIcon />} startIcon={<WarningAmberIcon />}
@ -532,7 +560,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
{'Confirm Disable AI Receptionist'} {"Confirm Disable AI Receptionist"}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
@ -553,7 +581,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
<Button <Button
onClick={() => { onClick={() => {
pushNotification( pushNotification(
'AI Receptionist disabled successfully', "AI Receptionist disabled successfully",
NOTIFICATION_TYPE.SUCCESS NOTIFICATION_TYPE.SUCCESS
); );
setOpenDialog(false); setOpenDialog(false);
@ -579,7 +607,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
variant="outlined" variant="outlined"
error={errors.clinicAddress} error={errors.clinicAddress}
helperText={ helperText={
errors.clinicAddress ? 'Clinic address is required' : '' errors.clinicAddress ? "Clinic address is required" : ""
} }
/> />
</Grid> </Grid>
@ -610,15 +638,15 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
multiline multiline
rows={3} rows={3}
variant="outlined" variant="outlined"
helperText={`${formData.clinicGreetings.length}/200 characters`} helperText={`${formData.clinicGreetings.length}/${CLINIC_GREETINGS_LENGTH} characters`}
inputProps={{ maxLength: 200 }} inputProps={{ maxLength: CLINIC_GREETINGS_LENGTH }}
/> />
<div <div
style={{ style={{
display: 'flex', display: "flex",
maxWidth: '40%', maxWidth: "40%",
// marginTop: '10px', // marginTop: '10px',
justifyContent: 'space-between', justifyContent: "space-between",
}} }}
> >
<Button <Button
@ -650,10 +678,10 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
onChange={handleChange} onChange={handleChange}
value={ value={
formData.voice || formData.voice ||
(formData.voiceGender === 'male' (formData.voiceGender === "male"
? voiceModels.find((model) => model.gender === 'male') ? voiceModels.find((model) => model.gender === "male")
.id .id
: voiceModels.find((model) => model.gender === 'female') : voiceModels.find((model) => model.gender === "female")
.id) .id)
} }
displayEmpty displayEmpty
@ -677,7 +705,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
return ( return (
<> <>
<TextField <TextField
style={{ marginTop: '10px' }} style={{ marginTop: "10px" }}
fullWidth fullWidth
label="Scenarios" label="Scenarios"
name="clinicScenarios" name="clinicScenarios"
@ -685,9 +713,9 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
onChange={handleChange} onChange={handleChange}
multiline multiline
rows={8} rows={8}
helperText={`${formData.clinicScenarios.length}/1500 characters`} helperText={`${formData.clinicScenarios.length}/${CLINIC_SCENARIOS_LENGTH} characters`}
variant="outlined" variant="outlined"
inputProps={{ maxLength: 3000 }} inputProps={{ maxLength: CLINIC_SCENARIOS_LENGTH }}
/> />
</> </>
); );
@ -696,7 +724,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
return ( return (
<> <>
<TextField <TextField
style={{ marginTop: '10px' }} style={{ marginTop: "10px" }}
fullWidth fullWidth
label="General Info" label="General Info"
name="clinicOthers" name="clinicOthers"
@ -782,8 +810,8 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
error={!!formData.integrationSoftware && errors.practiceId} error={!!formData.integrationSoftware && errors.practiceId}
helperText={ helperText={
!!formData.integrationSoftware && errors.practiceId !!formData.integrationSoftware && errors.practiceId
? 'Practice ID is required' ? "Practice ID is required"
: '' : ""
} }
/> />
</Grid> </Grid>
@ -799,8 +827,8 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
error={!!formData.integrationSoftware && errors.practiceName} error={!!formData.integrationSoftware && errors.practiceName}
helperText={ helperText={
!!formData.integrationSoftware && errors.practiceName !!formData.integrationSoftware && errors.practiceName
? 'Practice Name is required' ? "Practice Name is required"
: '' : ""
} }
/> />
</Grid> </Grid>
@ -808,17 +836,17 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
); );
default: default:
return 'Unknown step'; return "Unknown step";
} }
}; };
return ( return (
<Box component="form" onSubmit={handleSubmit} sx={{ width: '100%' }}> <Box component="form" onSubmit={handleSubmit} sx={{ width: "100%" }}>
<Paper elevation={0} sx={{ p: 2 }}> <Paper elevation={0} sx={{ p: 2 }}>
<Typography <Typography
variant="h4" variant="h4"
component="h1" component="h1"
sx={{ fontWeight: 'bold', mb: 3 }} sx={{ fontWeight: "bold", mb: 3 }}
> >
Clinic Setup Clinic Setup
</Typography> </Typography>
@ -828,12 +856,21 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
{steps.map((label, index) => { {steps.map((label, index) => {
const stepProps = {}; const stepProps = {};
const labelProps = {}; const labelProps = {};
// Determine if this step is clickable
const isClickable = confirmPhoneNumber && completed[index];
return ( return (
<Step key={label} {...stepProps} completed={completed[index]}> <Step key={label} {...stepProps} completed={completed[index]}>
<StepLabel <StepLabel
{...labelProps} {...labelProps}
onClick={handleStep(index)} onClick={handleStep(index)}
sx={{ cursor: 'pointer' }} sx={{
cursor: isClickable ? "pointer" : "not-allowed",
opacity: isClickable ? 1 : 0.7,
"& .MuiStepLabel-label": {
color: isClickable ? "text.primary" : "text.disabled",
},
}}
> >
{label} {label}
</StepLabel> </StepLabel>
@ -843,14 +880,14 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
</Stepper> </Stepper>
{/* Step Content */} {/* Step Content */}
<Box sx={{ mt: 2, mb: 2, minHeight: '300px' }}> <Box sx={{ mt: 2, mb: 2, minHeight: "300px" }}>
{allStepsCompleted() ? ( {allStepsCompleted() ? (
<React.Fragment> <React.Fragment>
<Typography sx={{ mt: 2, mb: 1 }}> <Typography sx={{ mt: 2, mb: 1 }}>
All steps completed - Setup is ready to be saved. All steps completed - Setup is ready to be saved.
</Typography> </Typography>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}> <Box sx={{ display: "flex", flexDirection: "row", pt: 2 }}>
<Box sx={{ flex: '1 1 auto' }} /> <Box sx={{ flex: "1 1 auto" }} />
{/* <Button onClick={handleReset} sx={{ mr: 1 }}> {/* <Button onClick={handleReset} sx={{ mr: 1 }}>
Reset Reset
</Button> */} </Button> */}
@ -864,7 +901,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
{renderStepContent(activeStep)} {renderStepContent(activeStep)}
{/* Navigation Buttons */} {/* Navigation Buttons */}
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, mt: 4 }}> <Box sx={{ display: "flex", flexDirection: "row", pt: 2, mt: 4 }}>
<Button <Button
color="inherit" color="inherit"
disabled={activeStep === 0} disabled={activeStep === 0}
@ -874,7 +911,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
> >
Back Back
</Button> </Button>
<Box sx={{ flex: '1 1 auto' }} /> <Box sx={{ flex: "1 1 auto" }} />
{activeStep == 4 && ( {activeStep == 4 && (
<Button <Button
onClick={handleTestConnection} onClick={handleTestConnection}
@ -900,7 +937,7 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
color="primary" color="primary"
onClick={handleComplete} onClick={handleComplete}
> >
{isLastStep() ? 'Finish' : 'Complete Step'} {isLastStep() ? "Finish" : "Complete Step"}
</Button> </Button>
)} )}
</Box> </Box>

View File

@ -0,0 +1,325 @@
import makeStyles from '@mui/styles/makeStyles';
import { pxToRem } from '../../theme/typography';
export const useStyles = makeStyles((theme) => ({
appBarStyle: {
backgroundColor: theme.palette.grey[28],
},
contentBox: {
marginTop: '3%',
},
mainContent: {
marginTop: theme.spacing(4),
marginBottom: theme.spacing(4),
marginLeft: theme.spacing(4),
marginRight: theme.spacing(4),
width: '100%',
overflowX: 'scroll',
'&::-webkit-scrollbar': {
height: '6px',
},
},
logoBox: {
display: 'flex',
width: '100%',
marginLeft: '1%',
[theme.breakpoints.down('md')]: {
width: 'auto',
},
},
logo: {
maxHeight: '61px',
objectFit: 'contain',
justifyContent: 'center',
},
whitePaper: {
width: '100%',
marginBottom: theme.spacing(0.3),
paddingBottom: theme.spacing(0.7),
borderRadius: `${theme.spacing(2)} ${theme.spacing(2)} ${theme.spacing(
1
)} ${theme.spacing(1)}`,
},
inputLabel: {
marginBottom: theme.spacing(0.5),
fontSize: pxToRem(12),
color: theme.palette.grey[10],
},
textAreaLast: {
width: '-webkit-fill-available',
'&.MuiButtonBase-root': {
padding: theme.spacing(0.8),
},
},
helperText: {
color: theme.palette.error.main,
fontStyle: 'italic',
},
formRoot: {
paddingTop: theme.spacing(3.2),
},
formPassswordHeading: {
fontSize: pxToRem(14),
fontWeight: '600',
},
passwordTextFiled: {
marginTop: theme.spacing(0),
},
icon: {
cursor: 'pointer',
paddingLeft: theme.spacing(3),
display: 'flex',
alignItems: 'center',
width: '50px',
[theme.breakpoints.up('md')]: {
width: '50px',
},
[theme.breakpoints.up('lg')]: {
width: '50px',
},
[theme.breakpoints.up('xl')]: {
width: '50px',
},
},
formPasswordHeadingRoot: {
paddingTop: theme.spacing(2),
paddingLeft: theme.spacing(2),
width: '100%',
},
subjectText: {
fontSize: pxToRem(12),
position: 'absolute',
right: pxToRem(8),
top: '90%',
transform: 'translateY(-90%)',
},
// --- AddForm Style ---
screenViewGridItem: {
marginTop: theme.spacing(1),
},
paperContainerBox: {
width: '100%',
},
countryCodeBox: {
padding: pxToRem(12),
paddingRight: pxToRem(20),
paddingLeft: pxToRem(16),
borderRadius: pxToRem(10),
backgroundColor: theme.palette.grey[20],
},
countryCodeLabel: {
marginBottom: theme.spacing(0),
opacity: 1,
fontFamily: 'Inter-Regular',
},
mobileNumberInput: {
fontFamily: 'Inter-Regular',
'& .MuiInputBase-root': {
paddingLeft: theme.spacing(0),
border: 0,
},
'& .MuiOutlinedInput-input': {
paddingLeft: theme.spacing(0),
},
},
passwordCheckListTitle: {
color: theme.palette.grey[20],
fontSize: pxToRem(12),
fontStyle: 'italic',
textAlign: 'left',
marginBottom: theme.spacing(0.4),
},
emailNote: {
color: theme.palette.grey[20],
fontSize: pxToRem(14),
fontStyle: 'italic',
textAlign: 'left',
marginBottom: theme.spacing(0.4),
},
passwordCheckList: {
fontSize: pxToRem(14),
paddingTop: theme.spacing(0.2),
fontStyle: 'italic',
color: theme.palette.grey[600],
},
stateTextField: {
'& .Mui-disabled': {
WebkitTextFillColor: theme.palette.grey[10], // Change to the appropriate color value
opacity: 1,
},
},
termsAndConditionErrorMessage: {
color: theme.palette.error.main,
fontStyle: 'italic',
fontSize: pxToRem(12),
marginTop: theme.spacing(0.5),
},
checkBox: {
marginTop: theme.spacing(3),
paddingRight: theme.spacing(1),
},
checkBoxLabel: {
marginTop: theme.spacing(3.2),
marginLeft: theme.spacing(1.2),
fontSize: pxToRem(16),
color: theme.palette.common.black,
},
linkText: {
color: theme.palette.grey[53],
},
placeholderText: {
fontStyle: 'italic',
fontSize: pxToRem(14),
opacity: 0.5,
color: theme.palette.grey[10],
},
// logo style
reactCrop: {
/* Set a minimum height and width */
minHeight: '200px',
minWidth: '200px',
},
dropZoneOuterBox: {
width: '100%',
},
inputTitle: {
'&.MuiTypography-body1': {
fontSize: pxToRem(12),
color: theme.palette.grey[10],
},
marginBottom: theme.spacing(0.6),
marginLeft: theme.spacing(0.6),
color: theme.palette.grey[10],
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
},
imgButton: {
color: theme?.palette?.primary?.main,
fontSize: pxToRem(12),
fontWeight: 600,
fontFamily: theme?.fontFamily?.Regular,
backgroundColor: 'transparent',
border: 'none',
cursor: 'pointer',
margin: `${theme.spacing(0)} ${theme.spacing(0.8)}`,
padding: theme.spacing(0),
outline: 'none',
},
logoUploadErrorBox: {
display: 'flex',
justifyContent: 'space-between',
},
errorText: {
color: theme.palette.error.main,
fontSize: pxToRem(12),
fontStyle: 'italic',
},
addButton: {
border: `1px dashed ${theme.palette.primary.main}`,
width: '100%',
height: '130px',
backgroundColor: theme.palette.common.white,
borderRadius: theme.spacing(1),
marginBottom: theme.spacing(1.48),
},
addButtonContent: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: '100%',
cursor: 'pointer',
width: '100%',
},
addText: {
opacity: 1,
fontFamily: theme?.fontFamily?.medium,
fontSize: pxToRem(14),
height: '16px',
fontWeight: 500,
lineHeight: 'normal',
letterSpacing: 'normal',
marginTop: theme?.spacing(1),
},
addIcon: {
width: '26px',
height: '24px',
},
addSubtext: {
fontSize: pxToRem(12),
color: theme.palette.grey[10],
opacity: '0.6',
textAlign: 'center',
padding: `${theme.spacing(0.5)} ${theme.spacing(7)}`,
[theme.breakpoints.down('md')]: {
padding: theme.spacing(1),
},
[theme.breakpoints.down('lg')]: {
padding: theme.spacing(1),
},
},
previewBox: {
display: 'flex',
justifyContent: 'end',
alignItems: 'start',
width: '153px',
height: '111px',
borderRadius: theme.spacing(0.4),
backgroundSize: 'cover',
},
formSectionHeadingMargin: {
marginTop: theme.spacing(1.5),
},
termsAndConditionDrawer: {
'& .MuiDrawer-paper': {
maxWidth: pxToRem(550),
boxShadow: '-10px 0px 10px -2px rgba(0,0,0,0.1)',
},
[theme.breakpoints.down('md')]: {
width: '100%',
},
},
verifyIcon: {
fontSize: pxToRem(20),
color: theme.palette.grey[54],
},
chipOuter: {
display: 'flex',
flexWrap: 'wrap',
paddingLeft: theme.spacing(3.2),
},
chip: {
minWidth: '170px',
height: '42px',
padding: theme.spacing(1.5),
margin: theme.spacing(1),
marginRight: theme.spacing(1.5),
justifyContent: 'space-between',
borderRadius: '24px',
fontSize: pxToRem(14),
},
billingUserOuterSection: {
marginTop: theme.spacing(3.0),
},
billingUserEmailSection: {
padding: `${theme.spacing(2.2)} ${theme.spacing(3.2)}`,
},
saveUserButton: {
marginLeft: theme.spacing(1.0),
width: '150px',
display: 'flex',
justifyContent: 'space-evenly',
borderRadius: '8px',
height: '47px',
// opacity: 0.5,
},
billingUserEmailWrapper: {
display: 'flex',
[theme.breakpoints.down('md')]: {
flexDirection: 'column',
gap: '15px',
},
},
}));

View File

@ -1,9 +1,70 @@
import React from 'react' import { Box } from "@mui/system";
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
import { useTheme } from "@mui/material/styles";
import PageHeader from "../../components/PageHeader";
import CustomFileUpload from "../../components/CustomFileUpload";
import { MAX_FILE_SIZE_IN_MB, MAX_FILES } from "../../constants";
import { useStyles } from "./contractManagementStyles";
import { Grid, Paper } from "@mui/material";
const ContractManagement = () => { const ContractManagement = () => {
return ( const classes = useStyles();
<div>ContractManagement</div> const theme = useTheme();
)
}
export default ContractManagement const formik = useFormik({
initialValues: {
companyPANImage: "",
},
validationSchema: Yup.object({
companyPANImage: Yup.string().required("Required"),
}),
onSubmit: (values) => {
console.log(values);
},
});
const setUploadedFileUrl = () => {};
return (
<Box>
<Paper
className={classes.formRoot}
sx={{ padding: theme.spacing(2), height: "70vh" }}
>
<PageHeader
pageTitle="Contract Management"
infiniteDropdown
hideAddButton={true}
/>
<Grid
container
spacing={theme.spacing(2.3)}
padding={`0 ${theme.spacing(3.2)}`}
className={classes.formRoot}
sx={{ display: "flex", flexDirection: "column" }}
>
<Grid item md={4} sm={12} xs={12}>
<CustomFileUpload
label="Contract*"
documentName="sd"
onUploadDone={setUploadedFileUrl}
maxFileSizeInMb={MAX_FILE_SIZE_IN_MB}
maxFiles={MAX_FILES}
uploadedFileUrl={formik.values.companyPANImage}
errorMessage={
formik.errors.companyPANImage && formik.touched.companyPANImage
? formik.errors.companyPANImage
: ""
}
/>
</Grid>
</Grid>
</Paper>
</Box>
);
};
export default ContractManagement;

View File

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

View File

@ -144,6 +144,9 @@ function YourDetailsForm() {
practiceManagementSystem: "", practiceManagementSystem: "",
practiceId: "", practiceId: "",
practiceName: "", practiceName: "",
// contract management
contract: "",
}); });
// Field references for focus and validation // Field references for focus and validation
@ -176,6 +179,9 @@ function YourDetailsForm() {
fullAddress: useRef(null), fullAddress: useRef(null),
companyPANImage: useRef(null), companyPANImage: useRef(null),
companyTANNumber: useRef(null), companyTANNumber: useRef(null),
// contract management
contract: useRef(null),
}; };
if (yourDetailsFormData) { if (yourDetailsFormData) {
@ -262,6 +268,7 @@ function YourDetailsForm() {
companyTANImage: Yup.string().required( companyTANImage: Yup.string().required(
"Clinic MEDICARE document is required" "Clinic MEDICARE document is required"
), ),
contract: Yup.string().required("Contract is required"),
termsAccepted: Yup.boolean() termsAccepted: Yup.boolean()
.oneOf([true], "You must accept the terms and conditions") .oneOf([true], "You must accept the terms and conditions")
.required("You must accept the terms and conditions"), .required("You must accept the terms and conditions"),
@ -551,6 +558,7 @@ function YourDetailsForm() {
documentType: "PAN", documentType: "PAN",
}, },
], ],
contract: inputData.contract || "",
}; };
return data; return data;
} }
@ -644,7 +652,9 @@ function YourDetailsForm() {
padding={`0 ${theme.spacing(3.2)}`} padding={`0 ${theme.spacing(3.2)}`}
className={classes.formRoot} className={classes.formRoot}
> >
<TextField disabled /> <Grid item md={4} sm={6} xs={12}>
<TextField value={1} disabled />
</Grid>
</Grid> </Grid>
{/* Personal Details Section */} {/* Personal Details Section */}
@ -1769,7 +1779,7 @@ function YourDetailsForm() {
<Grid item md={4} sm={12} xs={12}> <Grid item md={4} sm={12} xs={12}>
<CustomFileUpload <CustomFileUpload
label="Add ABN Image*" label="Add ABN Image*"
documentName="sd" documentName="companyPANImage"
onUploadDone={setUploadedFileUrl} onUploadDone={setUploadedFileUrl}
maxFileSizeInMb={MAX_FILE_SIZE_IN_MB} maxFileSizeInMb={MAX_FILE_SIZE_IN_MB}
maxFiles={MAX_FILES} maxFiles={MAX_FILES}
@ -1783,6 +1793,24 @@ function YourDetailsForm() {
/> />
</Grid> </Grid>
{/* contract grid */}
<Grid item md={4} sm={12} xs={12}>
<CustomFileUpload
label="Add Contract*"
documentName="contract"
onUploadDone={setUploadedFileUrl}
maxFileSizeInMb={MAX_FILE_SIZE_IN_MB}
maxFiles={MAX_FILES}
uploadedFileUrl={formik.values.contract}
errorMessage={
formik.errors.contract &&
formik.touched.contract
? formik.errors.contract
: ""
}
/>
</Grid>
{/* terms and condition grid */} {/* terms and condition grid */}
<Grid item md={12}> <Grid item md={12}>
<Box display="flex"> <Box display="flex">

View File

@ -1,9 +1,10 @@
import { LoadingButton } from "@mui/lab"; import { LoadingButton } from "@mui/lab";
import { Box, Button, Grid, MenuItem, Select, Switch, TextField } from "@mui/material"; import { Box, Button, Chip, Grid, MenuItem, Select, Switch, TextField } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import React, { useMemo, useRef, useState } from "react"; import React, { useMemo, useRef, useState } from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import * as Yup from "yup"; import * as Yup from "yup";
import EditIcon from '@mui/icons-material/Edit';
/* ----------------- Custom Imports ----------------- */ /* ----------------- Custom Imports ----------------- */
import PageHeader from "../../components/PageHeader"; import PageHeader from "../../components/PageHeader";
@ -13,11 +14,7 @@ import CustomModal from "../Modal/Modal";
import { useStyles } from "./userStyles"; import { useStyles } from "./userStyles";
/* ----------------- Assets ----------------- */ /* ----------------- Assets ----------------- */
import SendIcon from "@mui/icons-material/Send";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import RemoveIcon from "@mui/icons-material/Remove";
import { import {
getUsers, getUsers,
deleteUserById, deleteUserById,
@ -98,6 +95,36 @@ function Users() {
return { data: resp?.data?.records, rowCount: resp?.data?.totalCount }; return { data: resp?.data?.records, rowCount: resp?.data?.totalCount };
}; };
const editUser = async (row) => {
try {
const userData = await getUserById(row?.original?.id);
const formData = {
userName: userData?.name,
email: userData?.email,
mobile: userData?.mobile,
isEditUser: true,
};
// const updatedCheckboxes = roles.reduce(
// (acc, role) => ({
// ...acc,
// [role?.id]: userData?.roles.some(
// (roleData) => roleData?.id === role?.id
// ),
// }),
// {}
// );
// setSelectedCheckboxes(updatedCheckboxes);
// formik.setValues(formData);
// toggle();
// setIsEditUser(true);
// setUserId(row?.original?.id);
} catch ({ resp }) {
// eslint-disable-next-line no-console
console.error(resp?.data?.message);
}
};
const handleToggleButton = async (row) => { const handleToggleButton = async (row) => {
try { try {
// Replace this with your actual API call to update user status // Replace this with your actual API call to update user status
@ -204,14 +231,63 @@ function Users() {
isBold: true, isBold: true,
}, },
{ {
accessorFn: ({ mobile }) => mobile || NOT_AVAILABLE_TEXT, accessorFn: ({ userType }) => userType || NOT_AVAILABLE_TEXT,
accessorKey: "userType", accessorKey: "userType",
header: "User Type", header: "User Type",
}, },
{ {
accessorFn: ({ email }) => email || NOT_AVAILABLE_TEXT, accessorKey: "appointmentTypes",
accessorKey: "specialities", header: "Appointment Types",
header: "Specialties", Cell: ({ row }) => {
const appointmentTypes = row?.original?.appointmentTypes;
// Convert to array if it's a string (comma-separated values)
const typesArray = Array.isArray(appointmentTypes) && appointmentTypes.length > 0
? appointmentTypes
: typeof appointmentTypes === 'string' && appointmentTypes.trim() !== ''
? appointmentTypes.split(',').map(type => type.trim())
: [];
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{typesArray.length > 0 ? (
typesArray.map((type, index) => {
const label = typeof type === 'string'
? type
: type?.name || type?.type || String(type);
return (
<Chip
key={index}
label={label}
size="small"
variant="outlined"
sx={{
margin: '2px',
borderRadius: '16px',
backgroundColor: '#f5f5f5',
'& .MuiChip-label': { fontSize: '0.75rem' }
}}
/>
);
})
) : (
<Chip
label={NOT_AVAILABLE_TEXT}
size="small"
variant="outlined"
sx={{
margin: '2px',
borderRadius: '16px',
backgroundColor: '#f0f0f0',
color: '#757575',
'& .MuiChip-label': { fontSize: '0.75rem' }
}}
/>
)}
</Box>
);
},
}, },
{ {
accessorKey: "createdAt", accessorKey: "createdAt",
@ -362,26 +438,25 @@ function Users() {
columns={columns} columns={columns}
getData={getData} getData={getData}
options={{ enableRowSelection: false }} options={{ enableRowSelection: false }}
// showAction={true} showAction={true}
hideShowPerPage={true} hideShowPerPage={true}
showSearchBox={true} showSearchBox={true}
ref={ref} ref={ref}
searchText={"user"} searchText={"user"}
getRowStyle={getRowStyle} getRowStyle={getRowStyle}
// actions={[ actions={[
// { {
// title: "Action", onClick: editUser,
// field: "isActive", text: 'Edit',
// render: (rowData) => ( icon: (
// <Switch <EditIcon
// checked={rowData.isActive} // className={classes.tableActionIcons}
// onChange={() => handleToggleButton(rowData)} alt="Edit"
// inputProps={{ "aria-label": "Status toggle" }} />
// color="primary" ),
// /> // permissionName: "CREATE_USERS",
// ), },
// }, ]}
// ]}
/> />
</Box> </Box>