feat: clinic offers

This commit is contained in:
deepvasoya 2025-05-22 11:43:48 +05:30
parent f949253b77
commit a17366037b
10 changed files with 827 additions and 632 deletions

View File

@ -24,7 +24,7 @@ function Header() {
{user?.userType == USER_ROLES.SUPER_ADMIN.toLowerCase() ? null : `Welcome to ${user?.created_clinics?.[0]?.name}`}
</Typography>
<Box className={classes.profilDiv}>
<NotificationSection />
{/* <NotificationSection /> */}
<Box
sx={{
zIndex:

View File

@ -84,3 +84,49 @@ export const getClinicsDashboardStatsById = () => {
.catch((err) => reject(err));
});
};
export const createClinicOffer = (data) => {
const url = `/admin/clinic/offer`;
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const getClinicOffer = (params) => {
let searchParams = new URLSearchParams();
searchParams.append("size", params?.pagination?.pageSize ?? 10);
searchParams.append("page", params?.pagination.pageIndex ?? 0);
searchParams.append("search", params?.globalFilter ?? "");
const url = `/admin/clinic/offers?${searchParams.toString()}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateClinicOffer = (data) => {
const url = `/admin/clinic/offer`;
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const deleteClinicOffer = (id) => {
const url = `/admin/clinic/offer/${id}`;
return new Promise((resolve, reject) => {
axiosInstance
.delete(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};

View File

@ -0,0 +1,35 @@
import { axiosInstance } from "../config/api";
export const getMasterData = (params) => {
let searchParams = new URLSearchParams();
searchParams.append("search", params?.globalFilter ?? "");
const url = `/admin/master-data?${searchParams.toString()}`;
return new Promise((resolve, reject) => {
axiosInstance
.get(url)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const setMasterData = (data) => {
const url = "/admin/master-data";
return new Promise((resolve, reject) => {
axiosInstance
.post(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};
export const updateMasterData = (data) => {
const url = "/admin/master-data";
return new Promise((resolve, reject) => {
axiosInstance
.put(url, data)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
};

View File

@ -9,3 +9,5 @@ export const passwordNumberRegex = /\d/;
// passwordSpacesRegex.js
export const passwordSpacesRegex = /^\S.*\S$/;
export const emailRegex = /^\S+@\S+\.\S+$/;

View File

@ -249,7 +249,7 @@ const ClinicsList = () => {
[
// ..............sro number column......................
{
size: 60,
size: 100,
header: "Sr. No.",
Cell: (props) => {
const tableState = props?.table?.getState();
@ -330,68 +330,6 @@ const ClinicsList = () => {
);
},
},
// ........................... sent email column.............................
// {
// enableSorting: true,
// accessorKey: 'hasActiveCustomPlan',
// minSize: 250,
// header: 'Sent Mail',
// Cell: ({ row }) => (
// <>
// {row?.original?.plans?.[0]?.status ===
// PLAN_STATUS_TYPE.ACTIVATED && (
// <div>
// <Link
// onClick={() => handleRegisterEmailSent(row)}
// className={classes.sendEmailStatus}
// >
// <u>Resend Subscription Mail</u>
// </Link>
// <div className={classes.sendEmailLastSentMailDate}>
// Last Sent On:{' '}
// {format(
// new Date(row?.original?.lastCustomPlanEmailSent),
// 'dd MMM yyyy'
// )}
// </div>
// </div>
// )}
// </>
// ),
// },
// ................add plan column..............................
// {
// enableSorting: true,
// isBold: true,
// size: 120,
// accessorKey: 'hasActiveCustomPlan',
// header: 'Custom Plan',
// Cell: ({ row }) => (
// <>
// {row?.original?.plans?.[0]?.status ===
// PLAN_STATUS_TYPE.ACTIVATED ? (
// <Box>
// <div className={classes.planAddedText}>
// Plan Added <VerifiedIcon className={classes.verifyIcon} />
// </div>
// </Box>
// ) : (
// <Tooltip>
// <Link
// to={{
// pathname: `/plans/${row.original.id}/custom-create`,
// search: `?tab=${type}`,
// }}
// className={classes.addDiscountCodeLink}
// >
// <u>ADD PLAN</u>
// </Link>
// </Tooltip>
// )}
// </>
// ),
// },
].filter(Boolean),
[type]
);

View File

@ -1,61 +1,38 @@
import AddIcon from "@mui/icons-material/Add";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import DescriptionIcon from '@mui/icons-material/Description';
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 React, { useMemo, useRef, useState } from "react";
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import PageHeader from "../../components/PageHeader";
import { pushNotification } from "../../utils/notification";
import { NOTIFICATION } from "../../constants";
import { useStyles } from "./masterDataStyles";
import Table from "../../components/Table";
import { getMasterData, setMasterData } from "../../services/masterData.services";
const MasterDataManagement = () => {
const classes = useStyles();
const ref = useRef(null);
// State for form fields
const [appointmentType, setAppointmentType] = useState("");
const queryParams = new URLSearchParams(location.search);
const [type, setType] = useState("");
// State for staff list
const [staffList, setStaffList] = useState([]);
// State for staff dialog
// 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 staff dialog open/close
// Handle dialog open/close
const handleOpenDialog = () => {
setOpenDialog(true);
};
@ -63,38 +40,50 @@ const MasterDataManagement = () => {
const handleCloseDialog = () => {
setOpenDialog(false);
// Clear form
setAppointmentType("");
setType("");
};
// Handle form submission
const handleSubmit = (e) => {
const handleSubmit = async (e) => {
e.preventDefault();
// Add new staff member
const newStaff = {
id: staffList.length + 1,
appointmentType,
const payload = {
type,
};
setStaffList([...staffList, newStaff]);
const response = await setMasterData(payload);
if (response?.data?.data) {
pushNotification("Master Data created successfully!", NOTIFICATION.SUCCESS);
} else {
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
}
// 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,
});
const getData = async (filters) => {
try {
// Remove the type parameter since it's not defined
let params = {
...filters
};
const resp = await getMasterData(params);
console.log('API Response:', resp);
return {
data: resp?.data?.data?.data,
rowCount: resp?.data?.data?.total || 0,
};
} catch (error) {
console.error('Error fetching admins:', error);
return {
data: [],
rowCount: 0,
};
}
};
// ...................breadcrumbs array........................
@ -109,161 +98,76 @@ const MasterDataManagement = () => {
},
];
const columns = useMemo(() => [
{
size: 100,
header: "Sr. No.",
Cell: (props) => {
const tableState = props?.table?.getState();
const serialNumber = (
props?.row?.index +
1 +
tableState?.pagination?.pageIndex * tableState?.pagination?.pageSize
)
?.toString()
?.padStart(2, "0");
return <span>{serialNumber}</span>;
},
enableSorting: false,
},
{
id: "type", // Add an id to match the accessorKey
size: 280,
accessorKey: "type",
header: "Type",
enableColumnFilter: false,
}
// Removed the empty object that was causing the error
]);
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<PageHeader pageTitle="Master Data Management" hideAddButton />
<Box>
<PageHeader
pageTitle="Master Data Management"
// hideAddButton
addButtonIcon={<AddIcon />}
onAddButtonClick={handleOpenDialog}
/>
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
<Box className={classes.tableMainDiv}>
<Box>
<Table
hideTopToolbar
columns={columns}
getData={getData}
options={{
enableRowSelection: true,
showTopBar: false,
showFilters: true,
}}
searchText="Master Data"
showSearchBox={true}
ref={ref}
// actions={[
// {
// onClick: (row) => {
<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,
},
}}
// },
// text: "Remove",
// icon: (
// <DescriptionIcon />
// // <img
// // src={TransactionHistoryIcon}
// // className={classes.tableActionIcons}
// // alt="transaction history"
// // />
// ),
// },
// ]}
/>
</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 */}
<Box className={classes.tableMainDiv}>
<Dialog
open={openDialog}
onClose={handleCloseDialog}
@ -285,7 +189,7 @@ const MasterDataManagement = () => {
component="span"
sx={{ fontWeight: "bold", color: "#0a2d6b" }}
>
Add New Appointment Type
Add Appointment Type
</Typography>
</Box>
<IconButton onClick={handleCloseDialog} size="small">
@ -301,8 +205,8 @@ const MasterDataManagement = () => {
label="Appointment Type"
fullWidth
margin="normal"
value={appointmentType}
onChange={(e) => setAppointmentType(e.target.value)}
value={type}
onChange={(e) => setType(e.target.value)}
placeholder="Appointment Type"
required
InputLabelProps={{
@ -330,23 +234,8 @@ const MasterDataManagement = () => {
</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>
</Box>
</Box>
);
};

View File

@ -0,0 +1,119 @@
import makeStyles from '@mui/styles/makeStyles';
import { pxToRem } from '../../theme/typography';
export const useStyles = makeStyles((theme) => ({
chipClass: {
height: 'fit-content',
minHeight: '30px',
padding: '2px',
alignItems: 'center',
},
statusColor: {
color: theme.palette.primary.main,
fontSize: pxToRem(10),
},
tabsBox: {
display: 'flex',
justifyContent: ' space-around',
// width: '55%',
marginTop: theme.spacing(0.5),
marginRight: theme.spacing(5.0),
alignItems: 'center',
},
secondaryButton: {
width: '200px',
height: '46px',
borderRadius: '8px',
justifyContent: 'space-evenly',
fontSize: pxToRem(16),
},
tableActionIcons: {
marginRight: theme.spacing(1.4),
width: '15px',
},
companyNameTableColumn: {
display: 'flex',
alignItems: 'center',
},
companyName: {
marginLeft: theme.spacing(1),
fontSize: pxToRem(14),
objectFit: 'contain',
width: '260px',
},
companyNameLink: {
textDecoration: 'none',
color: theme.palette.grey[10],
'&:hover': {
color: theme.palette.info.main,
textDecoration: 'underline',
},
},
companyWebsiteLabel: {
fontSize: pxToRem(12),
},
companyNameLogo: {
height: '40px',
width: '40px',
borderRadius: theme.shape.borderRadiusComponent,
objectFit: 'contain',
},
sendEmailStatus: {
fontSize: pxToRem(14),
color: theme.palette.primary.main,
},
sendEmailLastSentMailDate: {
fontSize: pxToRem(12),
},
addDiscountCodeLink: {
fontSize: pxToRem(12),
color: theme.palette.primary.main,
},
addDiscountCodeLabel: {
fontSize: pxToRem(14),
backgroundColor: theme.palette.common.white,
color: theme.palette.common.black,
},
customModel: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
},
customModelBox: {
paddingLeft: theme.spacing(5),
paddingRight: theme.spacing(5),
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
// height: '100%',
border: 'none',
},
newPlanTitleText: {
fontSize: pxToRem(28),
fontFamily: theme.fontFamily.bold,
},
newPlanSubTitleText: {
fontSize: pxToRem(18),
padding: theme.spacing(1),
},
addPlanSuccessIcon: {
padding: theme.spacing(2),
paddingTop: theme.spacing(1),
},
planAddedText: {
fontSize: pxToRem(12),
color: theme.palette.grey[54],
display: 'flex',
alignItems: 'center',
alignSelf: 'center',
},
verifyIcon: {
marginLeft: theme.spacing(0.3),
fontSize: pxToRem(12),
color: theme.palette.grey[54],
},
}));

View File

@ -1,10 +1,10 @@
import AddIcon from "@mui/icons-material/Add";
import EditIcon from "@mui/icons-material/Edit";
import CloseIcon from "@mui/icons-material/Close";
import {
Box,
Button,
Checkbox,
Container,
Dialog,
DialogActions,
DialogContent,
@ -13,85 +13,154 @@ import {
IconButton,
MenuItem,
Paper,
Snackbar,
Alert,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
Divider,
} from "@mui/material";
import React, { useState } from "react";
import React, { useMemo, useRef, useState } from "react";
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
import PageHeader from "../../components/PageHeader";
import { useStyles } from "./paymentStyles";
import Table from "../../components/Table";
import { getAdmins } from "../../services/auth.services";
import { pushNotification } from "../../utils/notification";
import { NOTIFICATION } from "../../constants";
import { emailRegex } from "../../utils/regex";
import {
createClinicOffer,
getClinicOffer,
} from "../../services/clinics.service";
const PaymentManagement = () => {
const classes = useStyles();
const ref = useRef(null);
const tableRef = useRef(null);
// State for payment dialog
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
const [paymentData, setPaymentData] = useState({
clinicName: "",
clinicEmail: "",
setupFeeWaived: false,
specialOffer: false,
configurationMonth: 3
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",
});
const handleSubmit = () => {};
const columns = useMemo(() => [
{
size: 100,
header: "Sr. No.",
Cell: (props) => {
const tableState = props?.table?.getState();
const serialNumber = (
props?.row?.index +
1 +
tableState?.pagination?.pageIndex * tableState?.pagination?.pageSize
)
?.toString()
?.padStart(2, "0");
return <span>{serialNumber}</span>;
},
enableSorting: false,
},
{
size: 260,
accessorKey: "clinic_email",
header: "Clinic Email",
enableColumnFilter: false,
},
{
size: 200,
accessorKey: "setup_fees_waived",
header: "Setup Fees Waived",
Cell: (props) => (props.renderedCellValue ? "Yes" : "No"),
enableColumnFilter: false,
enableSorting: false,
},
{
size: 200,
accessorKey: "special_offer_for_month",
header: "Special Offer For Month",
enableColumnFilter: false,
enableSorting: false,
},
]);
const getData = async (filters) => {
try {
let params = {
...filters,
};
const resp = await getClinicOffer(params);
console.log("API Response:", resp);
return {
data: resp?.data?.data?.data,
rowCount: resp?.data?.total || 0,
};
} catch (error) {
console.error("Error fetching admins:", error);
return {
data: [],
rowCount: 0,
};
}
};
// Handle payment dialog submission
const handleAddPayment = () => {
const handleAddPayment = async () => {
// Validate input
if (!paymentData.clinicName.trim()) {
setNotification({
open: true,
message: "Please enter a clinic name",
severity: "error",
});
if (!paymentData.clinicEmail.trim()) {
pushNotification("Please enter a clinic email", NOTIFICATION.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",
});
if (!emailRegex.test(paymentData.clinicEmail)) {
pushNotification("Please enter a valid clinic email", NOTIFICATION.ERROR);
return;
}
// either setup fees waived or special offer for month should be true
if (!paymentData.setupFeeWaived && !paymentData.specialOffer) {
pushNotification(
"Please select either setup fees waived or special offer for month",
NOTIFICATION.ERROR
);
return;
}
const payload = {
clinic_email: paymentData.clinicEmail,
setup_fees_waived: paymentData.setupFeeWaived,
special_offer_for_month: paymentData.specialOffer
? paymentData.configurationMonth.toString()
: "0",
};
const resp = await createClinicOffer(payload);
console.log("API Response:", resp);
// Reset form and close dialog
setPaymentData({
clinicName: "",
clinicEmail: "",
setupFeeWaived: false,
specialOffer: false,
configurationMonth: 3
configurationMonth: 3,
});
setEditPaymentIndex(null);
setPaymentDialogOpen(false);
// Refresh the table data
if (tableRef.current) {
tableRef.current.reFetchData();
pushNotification("Payment configuration added successfully", NOTIFICATION.SUCCESS);
}
};
// Handle edit payment
@ -107,19 +176,10 @@ const PaymentManagement = () => {
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,
});
pushNotification(
"Payment configuration deleted successfully",
NOTIFICATION.SUCCESS
);
};
// Breadcrumbs
@ -129,153 +189,167 @@ const PaymentManagement = () => {
];
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<PageHeader pageTitle="Payment Management" hideAddButton />
<>
<Box>
<PageHeader
pageTitle="Payment Management"
addButtonIcon={<AddIcon />}
onAddButtonClick={() => setPaymentDialogOpen(true)}
/>
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
<Paper elevation={3} sx={{ p: 0, mb: 4, overflow: "hidden" }}>
{/* Payment Management Header with Add Button */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
p: 3,
<Box className={classes.tableMainDiv}>
<Box>
<Table
ref={tableRef}
hideTopToolbar
columns={columns}
getData={getData}
options={{
enableRowSelection: true,
showTopBar: false,
showFilters: true,
}}
>
<Typography variant="h6" component="h2" sx={{ fontWeight: "bold" }}>
Payment Configurations
</Typography>
<Button
variant="contained"
color="error"
startIcon={<AddIcon />}
onClick={() => setPaymentDialogOpen(true)}
sx={{
borderRadius: 50,
textTransform: "none",
backgroundColor: "#ff3366",
"&:hover": {
backgroundColor: "#e61653",
searchText="Staff"
showSearchBox={true}
actions={[
{
onClick: (row) => handleEditPayment(row),
text: "Edit",
icon: <EditIcon />,
},
}}
>
Add Payment
</Button>
]}
/>
</Box>
</Box>
{/* Payment List Table */}
<TableContainer>
<Table sx={{ minWidth: 650 }} aria-label="payment table">
<TableHead sx={{ backgroundColor: "#f5f5f5" }}>
<TableRow>
<TableCell sx={{ fontWeight: "bold" }}>Clinic Name</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Setup Fee Waived</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Special Offer</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Configuration Month</TableCell>
<TableCell sx={{ fontWeight: "bold" }}>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paymentsList.length > 0 ? (
paymentsList.map((payment, index) => (
<TableRow key={index}>
<TableCell>{payment.clinicName}</TableCell>
<TableCell>{payment.setupFeeWaived ? "Yes" : "No"}</TableCell>
<TableCell>{payment.specialOffer ? "Yes" : "No"}</TableCell>
<TableCell>
{payment.specialOffer ? payment.configurationMonth : "-"}
</TableCell>
<TableCell>
<Button
size="small"
color="primary"
onClick={() => handleEditPayment(index)}
>
Edit
</Button>
<Button
size="small"
color="error"
onClick={() => handleDeletePayment(index)}
>
Delete
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={5} align="center">
No payment configurations found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
{/* Payment Dialog */}
{/* Improved Dialog Box */}
<Dialog
open={paymentDialogOpen}
onClose={() => setPaymentDialogOpen(false)}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
borderRadius: "12px",
overflow: "hidden",
},
}}
>
<DialogTitle>
{editPaymentIndex !== null ? "Edit Payment" : "Add New Payment"}
<DialogTitle
sx={{
padding: "16px 24px",
backgroundColor: (theme) => theme.palette.primary.main,
color: "white",
fontWeight: "bold",
position: "relative",
}}
>
{editPaymentIndex !== null
? "Edit Payment Configuration"
: "Add New Payment"}
<IconButton
aria-label="close"
onClick={() => setPaymentDialogOpen(false)}
sx={{
position: 'absolute',
position: "absolute",
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
color: "white",
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box sx={{ mt: 2 }}>
<DialogContent sx={{ padding: "24px 24px 16px" }}>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<Typography variant="subtitle2" gutterBottom sx={{ mb: 1 }}>
Clinic Information
</Typography>
<TextField
autoFocus
margin="dense"
id="clinicName"
label="Clinic Name"
id="clinicEmail"
label="Clinic Email"
type="text"
fullWidth
variant="outlined"
value={paymentData.clinicName}
onChange={(e) => setPaymentData({...paymentData, clinicName: e.target.value})}
sx={{ mb: 2 }}
value={paymentData.clinicEmail}
onChange={(e) =>
setPaymentData({
...paymentData,
clinicEmail: e.target.value,
})
}
sx={{ mb: 3 }}
/>
<Typography variant="subtitle2" gutterBottom sx={{ mb: 1 }}>
Payment Options
</Typography>
<Paper
elevation={0}
sx={{
backgroundColor: (theme) => theme.palette.grey[50],
p: 2,
mb: 2,
border: "1px solid",
borderColor: (theme) => theme.palette.grey[200],
borderRadius: "8px",
}}
>
<FormControlLabel
control={
<Checkbox
checked={paymentData.setupFeeWaived}
onChange={(e) => setPaymentData({...paymentData, setupFeeWaived: e.target.checked})}
onChange={(e) =>
setPaymentData({
...paymentData,
setupFeeWaived: e.target.checked,
})
}
color="primary"
/>
}
label="Setup fee waived"
sx={{ mb: 1 }}
label={
<Box>
<Typography variant="body1">Setup fee waived</Typography>
<Typography variant="caption" color="text.secondary">
No initial setup fee will be charged
</Typography>
</Box>
}
sx={{ mb: 1, display: "flex", alignItems: "flex-start" }}
/>
<Divider sx={{ my: 1.5 }} />
<FormControlLabel
control={
<Checkbox
checked={paymentData.specialOffer}
onChange={(e) => setPaymentData({...paymentData, specialOffer: e.target.checked})}
onChange={(e) =>
setPaymentData({
...paymentData,
specialOffer: e.target.checked,
})
}
color="primary"
/>
}
label="Special Offer: First 3 months free"
sx={{ mb: 1 }}
label={
<Box>
<Typography variant="body1">Special Offer</Typography>
<Typography variant="caption" color="text.secondary">
First 3 months free of charge
</Typography>
</Box>
}
sx={{ display: "flex", alignItems: "flex-start" }}
/>
</Paper>
{paymentData.specialOffer && (
<TextField
@ -286,44 +360,57 @@ const PaymentManagement = () => {
fullWidth
variant="outlined"
value={paymentData.configurationMonth}
onChange={(e) => setPaymentData({...paymentData, configurationMonth: e.target.value})}
onChange={(e) =>
setPaymentData({
...paymentData,
configurationMonth: parseInt(e.target.value),
})
}
sx={{ mt: 1 }}
helperText="Number of months for special configuration"
>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((month) => (
<MenuItem key={month} value={month}>
{month}
{month} {month === 1 ? "month" : "months"}
</MenuItem>
))}
</TextField>
)}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setPaymentDialogOpen(false)} color="primary">
<DialogActions
sx={{
padding: "16px 24px",
borderTop: "1px solid",
borderColor: (theme) => theme.palette.grey[200],
justifyContent: "space-between",
}}
>
<Button
onClick={() => setPaymentDialogOpen(false)}
sx={{
color: (theme) => theme.palette.grey[700],
fontWeight: "medium",
}}
>
Cancel
</Button>
<Button onClick={handleAddPayment} variant="contained" color="primary">
Save
<Button
onClick={handleAddPayment}
variant="contained"
color="primary"
sx={{
borderRadius: "8px",
px: 3,
}}
>
{editPaymentIndex !== null ? "Update" : "Save"}
</Button>
</DialogActions>
</Dialog>
{/* Notification Snackbar */}
<Snackbar
open={notification.open}
autoHideDuration={6000}
onClose={handleCloseNotification}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert
onClose={handleCloseNotification}
severity={notification.severity}
sx={{ width: "100%" }}
>
{notification.message}
</Alert>
</Snackbar>
</Container>
</Box>
</>
);
};

View File

@ -0,0 +1,119 @@
import makeStyles from '@mui/styles/makeStyles';
import { pxToRem } from '../../theme/typography';
export const useStyles = makeStyles((theme) => ({
chipClass: {
height: 'fit-content',
minHeight: '30px',
padding: '2px',
alignItems: 'center',
},
statusColor: {
color: theme.palette.primary.main,
fontSize: pxToRem(10),
},
tabsBox: {
display: 'flex',
justifyContent: ' space-around',
// width: '55%',
marginTop: theme.spacing(0.5),
marginRight: theme.spacing(5.0),
alignItems: 'center',
},
secondaryButton: {
width: '200px',
height: '46px',
borderRadius: '8px',
justifyContent: 'space-evenly',
fontSize: pxToRem(16),
},
tableActionIcons: {
marginRight: theme.spacing(1.4),
width: '15px',
},
companyNameTableColumn: {
display: 'flex',
alignItems: 'center',
},
companyName: {
marginLeft: theme.spacing(1),
fontSize: pxToRem(14),
objectFit: 'contain',
width: '260px',
},
companyNameLink: {
textDecoration: 'none',
color: theme.palette.grey[10],
'&:hover': {
color: theme.palette.info.main,
textDecoration: 'underline',
},
},
companyWebsiteLabel: {
fontSize: pxToRem(12),
},
companyNameLogo: {
height: '40px',
width: '40px',
borderRadius: theme.shape.borderRadiusComponent,
objectFit: 'contain',
},
sendEmailStatus: {
fontSize: pxToRem(14),
color: theme.palette.primary.main,
},
sendEmailLastSentMailDate: {
fontSize: pxToRem(12),
},
addDiscountCodeLink: {
fontSize: pxToRem(12),
color: theme.palette.primary.main,
},
addDiscountCodeLabel: {
fontSize: pxToRem(14),
backgroundColor: theme.palette.common.white,
color: theme.palette.common.black,
},
customModel: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
},
customModelBox: {
paddingLeft: theme.spacing(5),
paddingRight: theme.spacing(5),
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
// height: '100%',
border: 'none',
},
newPlanTitleText: {
fontSize: pxToRem(28),
fontFamily: theme.fontFamily.bold,
},
newPlanSubTitleText: {
fontSize: pxToRem(18),
padding: theme.spacing(1),
},
addPlanSuccessIcon: {
padding: theme.spacing(2),
paddingTop: theme.spacing(1),
},
planAddedText: {
fontSize: pxToRem(12),
color: theme.palette.grey[54],
display: 'flex',
alignItems: 'center',
alignSelf: 'center',
},
verifyIcon: {
marginLeft: theme.spacing(0.3),
fontSize: pxToRem(12),
color: theme.palette.grey[54],
},
}));

View File

@ -1,28 +1,15 @@
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,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
} from "@mui/material";
@ -32,12 +19,10 @@ import PageHeader from "../../components/PageHeader";
import { createAdmin, getAdmins } from "../../services/auth.services";
import { pushNotification } from "../../utils/notification";
import { NOTIFICATION } from "../../constants";
import { useTheme } from "@emotion/react";
import { useStyles } from "./staffStyles";
import Table from "../../components/Table";
const StaffManagement = () => {
const theme = useTheme();
const classes = useStyles();
const ref = useRef(null);
// State for form fields
@ -46,26 +31,9 @@ const StaffManagement = () => {
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState("");
// 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",
});
// Email validation function
const validateEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@ -113,14 +81,6 @@ const StaffManagement = () => {
handleCloseDialog();
};
// Handle notification close
const handleCloseNotification = () => {
setNotification({
...notification,
open: false,
});
};
const getData = async (filters) => {
try {
// Remove the type parameter since it's not defined