page add panel/doctor role agent && save data crop img && upload img && just roles agent can access panel && save data doctor && add field phone number && remove two tab

This commit is contained in:
Ehsan
2025-07-16 08:13:29 +03:30
parent 1b2573e460
commit c88a371eda
37 changed files with 196 additions and 1193 deletions
+7 -3
View File
@@ -1,15 +1,19 @@
import { Button } from "@mui/material";
import { useState } from "react";
import Cropper from "react-easy-crop";
import { cropImage } from "./cropImg";
import { cropImage, dataURLtoFile } from "./cropImg";
function Crop({ data, uploadImg, handleClose }) {
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
const onComplete = (imagePromisse) =>
imagePromisse.then((image) => uploadImg(image));
const onComplete = async (imagePromise) => {
const dataUrl = await imagePromise;
const file = dataURLtoFile(dataUrl, "cropped-image.jpg");
uploadImg({ blob: dataUrl, file });
};
return (
<div className="mt-[16px]">
+16 -2
View File
@@ -21,7 +21,7 @@ async function getCroppedImg(imageSrc, pixelCrop) {
ctx.drawImage(
image,
safeArea / 2 - image.width * 0.5,
safeArea / 2 - image.height * 0.5,
safeArea / 2 - image.height * 0.5
);
const data = ctx.getImageData(0, 0, safeArea, safeArea);
@@ -31,12 +31,26 @@ async function getCroppedImg(imageSrc, pixelCrop) {
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
);
return canvas.toDataURL("image/jpeg");
}
export const dataURLtoFile = (dataurl, filename) => {
const arr = dataurl.split(",");
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
};
export const cropImage = async (image, croppedAreaPixels, onError) => {
try {
const croppedImage = await getCroppedImg(image, croppedAreaPixels);
+1 -1
View File
@@ -54,7 +54,7 @@ function UploadFile({ image, setImage }) {
<Image
width={24}
height={24}
src={image}
src={image.blob}
alt="img-uploaded"
className="w-[74px] h-[74px] rounded-full overflow-hidden object-cover"
/>
+1
View File
@@ -9,6 +9,7 @@ import { redirect } from "next/navigation";
export default async function Dashboard({ searchParams }) {
const { matchedCity } = getStateInfo();
const user = await getUser();
const ability = defineAbilitiesFor(user);
const cookieStore = cookies();
const token = cookieStore.get("access_token");
+11 -1
View File
@@ -1,6 +1,16 @@
import Content from "@/components/panel/Content";
import { defineAbilitiesFor } from "@/lib/ability";
import { getUser } from "@/lib/auth";
import { redirect } from "next/navigation";
async function LayoutPanel({ children }) {
const user = await getUser();
const ability = defineAbilitiesFor(user);
if (!ability.can("access", "Panel")) {
return redirect("/");
}
function LayoutPanel({ children }) {
return <Content children={children} />;
}
-57
View File
@@ -1,57 +0,0 @@
import CalendarTick from "@/components/icons/CalendarTick";
import LocationAdd from "@/components/icons/LocationAdd";
import UserEdit from "@/components/icons/UserEdit";
function Step({ value }) {
const list = [
{ icon: <UserEdit active={value >= 0} />, name: "اطلاعات عمومی" },
{ icon: <LocationAdd active={value >= 1} />, name: "اطلاعات مطب" },
{ icon: <CalendarTick active={value >= 2} />, name: "روزهای کاری" },
];
return (
<ul className="flex items-center justify-center">
{list.map((item, idx) => (
<li className="flex items-center justify-center" key={idx}>
<div className="flex flex-col items-center justify-center gap-2">
<div
className={`flex items-center bg-[#F6F6F6] justify-center p-[18px]
w-[32px] h-[32px] sm:w-[46px] sm:h-[46px] md:w-[60px] md:h-[60px] lg:w-[75px] lg:h-[75px] rounded-full
relative overflow-hidden before:delay-500 before:transition-all before:duration-500
before:absolute before:w-full before:h-full before:bg-[#5559CE] dark:bg-[#D7D8ED]
${
value >= idx
? "before:right-0 !bg-transparent delay-700"
: "before:-right-full"
}`}
>
<div className="z-10 delay-500">{item.icon}</div>
</div>
<p
className={`text-[10px] sm:text-[12px] md:text-[14px] lg:text-[16px] text-nowrap delay-500
${
value >= idx
? "font-bold text-[#5559CE]"
: "font-normal text-[#616161] dark:text-[#a1a1a1]"
}`}
>
{item.name}
</p>
</div>
{list.length > idx + 1 && (
<span
className={`block rounded-[16px] transition-all duration-500 h-[2px]
bg-[#EFEFEF] dark:bg-[#EFEFEF] w-[35px] sm:w-[56px] md:w-[77px] lg:w-[100px]
relative overflow-hidden before:transition-all before:duration-500
before:absolute before:w-full before:h-full before:bg-[#5559CE] before:ease-linear
${value > idx ? "before:right-0" : "before:-right-full"}`}
></span>
)}
</li>
))}
</ul>
);
}
export default Step;
+12 -69
View File
@@ -1,72 +1,15 @@
export const defaultState = {
general: {
name: { value: "", placeholder: "", isEdit: true },
medical_system_number: {
value: "",
placeholder: 1741025645,
isEdit: true,
},
expertise: { value: "", placeholder: "دندان پزشکی", isEdit: true },
description: { value: "", isEdit: true },
skills: { value: "", isEdit: true },
time_work: { value: "", isEdit: true },
degree: { value: "", isEdit: true },
name: { value: "", placeholder: "", isEdit: true },
medical_system_number: {
value: "",
placeholder: 1741025645,
isEdit: true,
},
office: {
address: { value: "مپ", isEdit: true },
phone: { value: "", placeholder: 1741025645, isEdit: true },
office_number1: { value: "", isEdit: true },
state: { value: "", isEdit: true },
city: { value: "", isEdit: true },
},
work_day: [
{
name: "شنبه",
number_turns: { value: 1, isEdit: true },
turn_time: "",
workplace: "",
morning_time: ["", ""],
evening_time: ["", ""],
},
{
name: "یکشنبه",
number_turns: { value: 1, isEdit: true },
turn_time: "",
workplace: "",
morning_time: ["", ""],
evening_time: ["", ""],
},
{
name: "دوشنبه",
number_turns: { value: 1, isEdit: true },
turn_time: "",
workplace: "",
morning_time: ["", ""],
evening_time: ["", ""],
},
{
name: "سه شنبه",
number_turns: { value: 1, isEdit: true },
turn_time: "",
workplace: "",
morning_time: ["", ""],
evening_time: ["", ""],
},
{
name: "چهار شنبه",
number_turns: { value: 1, isEdit: true },
turn_time: "",
workplace: "",
morning_time: ["", ""],
evening_time: ["", ""],
},
{
name: "پنج شنبه",
number_turns: { value: 1, isEdit: true },
turn_time: "",
workplace: "",
morning_time: ["", ""],
evening_time: ["", ""],
},
],
expertise: { value: "", placeholder: "دندان پزشکی", isEdit: true },
description: { value: "", isEdit: true },
skills: { value: "", isEdit: true },
time_work: { value: "", isEdit: true },
phone: { value: "", placeholder: "", isEdit: true },
degree: { value: "", isEdit: true },
gender: { value: "woman", isEdit: true },
};
@@ -1,11 +1,12 @@
import { FormControlLabel, Radio, RadioGroup } from "@mui/material";
function RadioGender() {
function RadioGender({ changeData, name }) {
return (
<RadioGroup
dir="ltr"
className="!flex radio-mui !flex-row !gap-[36px] !items-center !justify-start"
defaultValue="female"
defaultValue="woman"
onChange={(e) => changeData(e.target.value, "value", name)}
sx={{
"& .muirtl-j204z7-MuiFormControlLabel-root": {
marginLeft: "0 !important",
@@ -14,13 +15,13 @@ function RadioGender() {
>
<FormControlLabel
label="مرد"
value="male"
value="man"
control={<Radio />}
className="!text-[#A1A1A1]"
/>
<FormControlLabel
label="زن"
value="female"
value="woman"
control={<Radio />}
className="!text-[#A1A1A1]"
/>
@@ -1,7 +1,7 @@
import FieldDate from "./FieldDate";
import RadioGender from "./RadioGender";
import SendReq from "./SendReq";
import Selector from "./Selector";
import FieldDate from "./fields/FieldDate";
import RadioGender from "./fields/RadioGender";
import SendReq from "./sendReq";
import Selector from "./fields/Selector";
// Components
import EditField from "@/app/component/EditField";
@@ -16,19 +16,15 @@ const validate_medical_number = {
max: 9999999999,
};
function Form({ setValue, parent, data, setData }) {
function Form({ image, data, setData, resetData }) {
const changeData = (value, type, name) => {
type !== "isEdit" &&
setData((prev) => {
return {
...prev,
[parent]: {
...prev[parent],
[name]: {
...prev[parent][name],
[type]: value,
},
[name]: {
...prev[name],
[type]: value,
},
};
});
@@ -73,6 +69,7 @@ function Form({ setValue, parent, data, setData }) {
<ContentText text="تخصص">
<Selector
updateData={changeData}
data={data?.expertise.value}
label="انتخاب کنید..."
name="expertise"
list={filteredSpe}
@@ -82,9 +79,9 @@ function Form({ setValue, parent, data, setData }) {
<ContentText text="مدرک">
<Selector
updateData={changeData}
// type="number"
label="انتخاب کنید..."
name="degree"
data={data?.degree.value}
list={education}
nameKey="label"
/>
@@ -111,12 +108,24 @@ function Form({ setValue, parent, data, setData }) {
name="skills"
/>
</ContentText>
<ContentText text="جنسیت">
<RadioGender />
<ContentText text="شماره موبایل">
<EditField
isTransparent={true}
iconGray={true}
changeData={changeData}
data={data?.phone}
type="number"
placeholder={data?.phone.placeholder}
name="phone"
/>
</ContentText>
<FieldDate changeListData={changeData} data={data?.time_work} />
<ContentText text="جنسیت">
<RadioGender changeData={changeData} name="gender" />
</ContentText>
</div>
<SendReq setValue={setValue} data={data} />
<SendReq img={image} data={data} resetData={resetData} />
</>
);
}
@@ -0,0 +1,18 @@
import moment from "jalali-moment";
export const dateToTimestamp = (date) => {
const gregorianMoment = moment(date, "jYYYY/jM/jD").locale("fa");
const unixTimestamp = gregorianMoment.valueOf();
return unixTimestamp / 1000;
};
export const transformData = (data) => {
return {
name: data.name.value,
activity_time: dateToTimestamp(data.time_work.value),
doctor_mobile_number: data.phone.value,
degree: data.degree.value,
specialty: [Number(data.expertise.value)],
info: data.description.value,
};
};
@@ -0,0 +1,66 @@
import ArrowLeftPA from "@/components/icons/ArrowLeftPA";
import { checkAllValues } from "@/helper";
import { request } from "@/services/response";
import { Button } from "@mui/material";
import { useState } from "react";
import { transformData } from "./function";
import { defaultState } from "../../defaultState";
function SendReq({ img, data, resetData }) {
const [loading, setLoading] = useState(false);
const saveData = (img) => {
let transformed = transformData(data);
if (img) {
transformed.image = [img];
}
request
.postDoctor(transformed)
.then(() => {
resetData();
setLoading(false);
})
.catch(() => {
setLoading(false);
});
};
const saveImg = () => {
setLoading(true);
if (img) {
request
.postImageUpload(img.file)
.then((res) => {
const imgId = res.fid[0].value;
saveData(imgId);
})
.catch((err) => {
setLoading(false);
});
} else {
saveData();
}
};
return (
<div className="mt-[40px] w-full flex justify-end">
<Button
className="!w-full md:!w-fit !shadow-none !rounded-[4px] !gap-[4px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium"
// onClick={() => setValue((prev) => prev + 1)}
disabled={!checkAllValues(data)}
variant="contained"
onClick={saveImg}
loading={loading}
>
<p className={loading ? "opacity-0" : ""}>ثبت اطلاعات</p>
<div className={!checkAllValues(data) ? "opacity-40" : "opacity-100"}>
<ArrowLeftPA />
</div>
</Button>
</div>
);
}
export default SendReq;
+14 -44
View File
@@ -1,59 +1,29 @@
"use client";
import Form from "./form";
import { useState } from "react";
// Tabs
import WorkingDays from "./listMenu/workingDays";
import OfficeInformation from "./listMenu/officeInformation";
import GeneralInformation from "./listMenu/generalInformation";
import Step from "./Step";
import { defaultState } from "./defaultState";
import UploadFile from "@/app/component/uploadFile";
function AddDoctorPage() {
const [value, setValue] = useState(0);
const [data, setData] = useState(defaultState);
const [image, setImage] = useState("");
console.log(data);
const resetData = () => {
setData(defaultState);
setValue(0);
setImage();
};
const components = [
<GeneralInformation
data={data}
parent="general"
setData={setData}
setValue={setValue}
/>,
<OfficeInformation
data={data}
parent="office"
setData={setData}
setValue={setValue}
/>,
<WorkingDays
data={data}
parent="work_day"
setData={setData}
resetData={resetData}
/>,
];
return (
<div className="opacity-page">
<h1 className="text-[#525252] dark:text-[#D7D8ED] text-[20px] font-bold hidden md:flex">
پروفایل من
</h1>
<div
className="rounded-[8px] mt-0 md:mt-[24px] bg-transparent md:bg-[#FFF]
md:dark:bg-[#222433] shadow-none md:shadow-[0px_1px_24.8px_0px_rgba(204,204,204,0.18)] dark:shadow-none pt-0
md:pt-[30px] pb-0 md:pb-[12px] lg:pb-[24px] px-0 md:px-[14px] lg:px-[26px] xl:px-[56px]"
>
<Step value={value} />
<div className="mt-[24px] md:mt-[28px] lg:mt-[32px]">
{components[value]}
</div>
</div>
<div className="opacity-page bg-transparent dark:shadow-[0px_4px_25px_10px_#1F1D2B] md:dark:bg-[#222433] md:bg-[#FFF] opacity-page rounded-[8px] shadow-none md:shadow-[0px_1px_24.8px_0px_rgba(204,204,204,0.18)] pt-0 md:pt-[12px] lg:pt-[24px] px-0 md:px-[24px] p-[24px]">
<UploadFile image={image} setImage={setImage} />
<Form
data={data}
image={image}
parent={parent}
setData={setData}
resetData={resetData}
/>
</div>
);
}
@@ -1,45 +0,0 @@
import ArrowLeftPH from "@/components/icons/ArrowLeftPH";
import { Button } from "@mui/material";
function Menu({ value, open, setOpen, listTab, handleChange }) {
return (
<ul
className={`
flex bg-[#FAFAFA] flex-col justify-start items-start dark:bg-[#222433]
fixed h-screen w-full z-50 top-[60px] pt-[20px] transition-all duration-500
${open ? "right-0" : "-right-full"}
`}
>
{listTab.map((item, idx) => (
<li key={idx} className="w-[calc(100%-32px)] mx-[16px]">
<Button
onClick={() => {
handleChange(_, idx);
setOpen(false);
}}
className="!flex !p-2 !items-center !justify-between !w-full"
>
<p
className={`
text-[16px] transition-all duration-500
${
value === idx
? "text-[#5559CE] font-bold"
: "text-[#7E7E7E] font-normal"
}
`}
>
{item}
</p>
<ArrowLeftPH active={value === idx} />
</Button>
{listTab.length > idx + 1 && (
<span className="my-[12px] block h-px w-full bg-[#EFEFEF] dark:bg-[#343645]"></span>
)}
</li>
))}
</ul>
);
}
export default Menu;
@@ -1,35 +0,0 @@
import Tabs from "@/app/component/Tabs";
function Tab({ value, listTab, handleChange }) {
return (
<Tabs
isSm={false}
style={{
".MuiTabs-indicator": {
height: "2px",
borderRadius: "16px",
background: "#5559CE",
},
"& .MuiTabs-flexContainer": {
borderBottom: "2px solid #EFEFEF",
},
"& .mui-11pdt05-MuiButtonBase-root-MuiTab-root.Mui-selected": {
color: "#5559CE !important",
},
"& .MuiButtonBase-root": {
fontWeight: "400 !important",
},
"& .MuiButtonBase-root.Mui-selected": {
color: "#5559CE !important",
fontWeight: "700 !important",
transition: "0.3s all !important",
},
}}
value={value}
listTab={listTab}
handleChange={handleChange}
/>
);
}
export default Tab;
@@ -1,10 +0,0 @@
import UploadFile from "@/app/component/uploadFile";
import { useState } from "react";
function Head() {
const [image, setImage] = useState("");
return <UploadFile image={image} setImage={setImage} />;
}
export default Head;
@@ -1,23 +0,0 @@
import ArrowLeftPA from "@/components/icons/ArrowLeftPA";
import { checkAllValues } from "@/helper";
import { Button } from "@mui/material";
function SendReq({ setValue, data }) {
return (
<div className="mt-[40px] w-full flex justify-end">
<Button
className="!w-full md:!w-fit !shadow-none !rounded-[4px] !gap-[4px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium"
onClick={() => setValue((prev) => prev + 1)}
disabled={!checkAllValues(data)}
variant="contained"
>
ثبت اطلاعات
<div className={!checkAllValues(data) ? "opacity-40" : "opacity-100"}>
<ArrowLeftPA />
</div>
</Button>
</div>
);
}
export default SendReq;
@@ -1,18 +0,0 @@
import Form from "./form";
import Head from "./Head";
function GeneralInformation({ setValue, parent, data, setData }) {
return (
<div className="opacity-page pb-[24px] px-0 md:px-[24px] lg:px-[56px]">
<Head />
<Form
parent={parent}
setData={setData}
setValue={setValue}
data={data.general}
/>
</div>
);
}
export default GeneralInformation;
@@ -1,23 +0,0 @@
import Menu from "./Menu";
import Tab from "./Tab";
function ListMenu({ value, open, setOpen, handleChange, listTab }) {
return (
<>
<div className="hidden md:flex">
<Tab value={value} listTab={listTab} handleChange={handleChange} />
</div>
<div className="flex md:hidden">
<Menu
open={open}
value={value}
listTab={listTab}
setOpen={setOpen}
handleChange={handleChange}
/>
</div>
</>
);
}
export default ListMenu;
@@ -1,136 +0,0 @@
"use client";
import { city, state } from "./listSelector";
import EditField from "@/app/component/EditField";
import ContentText from "@/app/component/ContentText";
import AddLocationP from "@/components/icons/AddLocationP";
import AutoCompleteSelect from "@/app/component/element/AutoCompleteSelect";
import { useState } from "react";
import ModalMap from "./modal";
// Data
import statesData from "@/data/state.json";
import citiesData from "@/data/city.json";
import Selector from "./Selector";
const validate_phone_number = {
min: 1,
max: 9999999999,
};
function Form({ parent, data, setData }) {
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
const changeData = (value, type, name) =>
type !== "isEdit" &&
setData((prev) => {
return {
...prev,
[parent]: {
...prev[parent],
[name]: {
...prev[parent][name],
[type]: value,
},
},
};
});
const updateNum = (value, type, name) => {
const parseValue = +value;
const { max } = validate_phone_number;
const validateValue = parseValue > max ? data[name].value : value;
changeData(validateValue, type, name);
};
return (
<>
<div className="mt-[24px] grid grid-cols-1 md:grid-cols-2 gap-x-[48px] gap-y-[24px]">
<ContentText text="استان">
<Selector
updateData={changeData}
label="انتخاب کنید..."
name="state"
list={statesData}
/>
</ContentText>
<ContentText text="شهر">
<Selector
updateData={changeData}
label="انتخاب کنید..."
name="city"
list={citiesData}
/>
</ContentText>
<ContentText text="آدرس">
<div className="relative w-full">
<EditField
icon={<AddLocationP />}
isTransparent={true}
placeholder="از روی نقشه انتخاب کنید..."
iconGray={true}
changeData={changeData}
data={data?.address}
name="address"
/>
<div
className="absolute left-0 top-0 w-full h-full opacity-0 z-10 cursor-pointer"
onClick={handleOpen}
></div>
</div>
</ContentText>
<ContentText text="شماره موبایل">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.phone}
noDisable={true}
type="number"
placeholder="شماره موبایل"
name="phone"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
<ContentText text="شماره مطب">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.office_number1}
noDisable={true}
type="number"
placeholder="شماره مطب"
name="office_number1"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
{/* <ContentText text="شماره مطب">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.office_number2}
noDisable={true}
type="number"
placeholder="شماره مطب"
name="office_number2"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText> */}
</div>
<ModalMap open={open} handleClose={handleClose} />
</>
);
}
export default Form;
@@ -1,83 +0,0 @@
import BottomSelect from "@/components/icons/BottomSelect";
import { Autocomplete, Button, TextField } from "@mui/material";
import { styleTextSelectRight } from "@/mui";
function Selector({ updateData, label, name, list }) {
return (
<Autocomplete
disablePortal
options={list}
getOptionLabel={(option) => option.name || ""}
// isOptionEqualToValue={(option, value) => option.id === value.id}
className="!w-full !rounded-lg auto-complete-selector "
onChange={(e, newValue) => {
if (newValue) {
updateData && updateData(newValue.id, "value", name);
}
}}
sx={{
...styleTextSelectRight,
// Hide Icon Number
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button":
{
display: "none",
},
"& input[type=number]": {
MozAppearance: "textfield",
},
"& .MuiInputBase-input": { padding: "12.5px 14px !important" },
"& .MuiInputBase-root": {
borderRadius: 2,
padding: "0 !important",
height: {
xs: "43px !important",
sm: "43px !important",
md: "46px !important",
lg: "48px !important",
},
},
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid #D7D7D7",
},
"& .MuiInput-underline:hover:not(.Mui-disabled):before, .MuiOutlinedInput-notchedOutline":
{
borderColor: "#D7D7D7 !important",
},
"& .muirtl-1kiro5a-MuiInputBase-root-MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":
{
border: "1px solid #5559CE !important",
},
}}
renderOption={(props, option) => (
<Button
fullWidth
{...props}
key={option.id || props.id}
className="!flex !justify-start !p-[8px] !text-start !text-[#A1A1A1] hover:dark:!bg-[#35343D] !bg-[#FFF] dark:!bg-[#1F1D2B]"
>
{option.name || option.label}
</Button>
)}
renderInput={(params) => (
<TextField
fullWidth
{...params}
InputProps={{
...params.InputProps,
endAdornment: (
<div className="absolute left-4">
<BottomSelect />
</div>
),
}}
placeholder={label}
sx={{
borderRadius: "8px",
}}
/>
)}
/>
);
}
export default Selector;
@@ -1,34 +0,0 @@
import ArrowLeftPA from "@/components/icons/ArrowLeftPA";
import { Button } from "@mui/material";
import Form from "./Form";
import { checkAllValues } from "@/helper";
import ScrollToTop from "@/app/component/ScrollToTop";
function OfficeInformation({ setValue, parent, data, setData }) {
return (
<div className="opacity-page pb-[24px] px-0 md:px-[24px] lg:px-[56px]">
<ScrollToTop />
<Form parent={parent} setData={setData} data={data.office} />
<div className="mt-[40px] w-full flex justify-end">
<Button
className={`!w-full md:!w-fit !shadow-none !rounded-[4px] !gap-[4px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium
${!checkAllValues(data.office) && "opacity-60"}`}
disabled={!checkAllValues(data.office)}
onClick={() => setValue((prev) => prev + 1)}
variant="contained"
>
ثبت اطلاعات
<div
className={
!checkAllValues(data.office) ? "opacity-40" : "opacity-100"
}
>
<ArrowLeftPA />
</div>
</Button>
</div>
</div>
);
}
export default OfficeInformation;
@@ -1,13 +0,0 @@
export const state = [
{ label: "استان 1", id: 10 },
{ label: "استان 2", id: 20 },
{ label: "استان 3", id: 30 },
{ label: "استان 4", id: 40 },
];
export const city = [
{ label: "شهر 1", id: 10 },
{ label: "شهر 2", id: 20 },
{ label: "شهر 3", id: 30 },
{ label: "شهر 4", id: 40 },
];
@@ -1,53 +0,0 @@
import { styleDefault } from "@/mui";
import { Modal } from "@mui/material";
import { useState } from "react";
import MapPicker from "react-google-map-picker";
const DefaultLocation = {
lat: 9.912345921562734,
lng: 105.97776559918576,
};
const DefaultZoom = 10;
function ModalMap({ open, handleClose }) {
const [defaultLocation, setDefaultLocation] = useState(DefaultLocation);
const [location, setLocation] = useState(defaultLocation);
const [zoom, setZoom] = useState(DefaultZoom);
function handleChangeLocation(lat, lng) {
setLocation({ lat: lat, lng: lng });
}
function handleChangeZoom(newZoom) {
setZoom(newZoom);
}
function handleResetLocation() {
setDefaultLocation({ ...DefaultLocation });
setZoom(DefaultZoom);
}
return (
<Modal open={open} onClose={handleClose} className="w-full">
<div
style={styleDefault}
className="!px-[16px] !overflow-auto md:!min-w-[580px] md:!px-[24px] lg:!px-[32px] !py-[24px] !bg-[#FFF] !rounded-[8px] !shadow-[0px_1px_24.8px_0px_rgba(204,_204,_204,_0.18)]
!w-full md:!w-fit !h-full md:!h-fit !flex !flex-col !justify-between !max-h-screen md:!max-h-[calc(100%_-_32px)]"
>
<MapPicker
zoom={zoom}
mapTypeId="roadmap"
style={{ height: "500px" }}
onChangeZoom={handleChangeZoom}
defaultLocation={defaultLocation}
onChangeLocation={handleChangeLocation}
apiKey="AIzaSyD07E1VvpsN_0FvsmKAj4nK9GnLq-9jtj8"
/>
</div>
</Modal>
);
}
export default ModalMap;
@@ -1,151 +0,0 @@
import { useState } from "react";
import ModalAddTime from "./modal/ModalAddTime";
import {
Button,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import ContentModal from "./modal";
import EditOrangeAD from "@/components/icons/EditOrangeAD";
function List({ parent, data, setData, setIsDisable }) {
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [dataChange, setDataChange] = useState({
is_morgen: false,
idx: undefined,
});
return (
<div className="w-full flex justify-center table-profile-page">
<TableContainer
className="!mt-[40px] md:!mt-[36px] lg:!mt-[32px] !w-fit"
sx={{
"&.MuiTableContainer-root": {
boxShadow: "none !important",
border: "none !important",
},
}}
>
<Table>
<TableHead className="!py-2">
<TableRow className="!bg-[#EFEFEF] !py-2 dark:!bg-[#343645]">
<TableCell
className="!py-[10px] !text-[#616161] dark:!text-[#D7D8ED] !text-[14px] !font-normal"
align="left"
>
روز های هفته
</TableCell>
<TableCell
className="!py-[10px] !text-[#616161] dark:!text-[#D7D8ED] !text-[14px] !font-normal"
align="left"
>
تایم صبح
</TableCell>
<TableCell
className="!py-[10px] !text-[#616161] dark:!text-[#D7D8ED] !text-[14px] !font-normal"
align="left"
>
تایم عصر
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data[parent].map((row, idx) => (
<TableRow
key={row.name}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell
className="!border-[1px] !border-[#DBDBDB] dark:!text-[#A1A1A1]"
component="th"
scope="row"
>
{row.name}
</TableCell>
<TableCell
className="!border-[1px] !text-[#616161] dark:!text-[#D7D8ED] !text-center !text-[16px] !font-medium !border-[#DBDBDB]"
align="right"
>
{row.morning_time[0] || row.morning_time[1] ? (
<Button
onClick={() => {
handleOpen();
setDataChange({
is_morgen: true,
idx,
});
}}
className="!gap-[6px] !min-w-[146px] !py-[4px] dark:!text-[#D7D8ED] !px-[8px] !text-[#616161] !text-[16px] !font-bold"
>
{row.morning_time[0] === "تعطیل"
? row.morning_time[0]
: `${row.morning_time[0]} - ${row.morning_time[1]}`}
<EditOrangeAD />
</Button>
) : (
<ModalAddTime
setDataChange={setDataChange}
handleOpen={handleOpen}
setData={setData}
isMorgen={true}
idx={idx}
/>
)}
</TableCell>
<TableCell
className="!border-[1px] !text-[#616161] dark:!text-[#D7D8ED] !text-center !text-[16px] !font-medium !border-[#DBDBDB]"
align="right"
>
{row.evening_time[0] || row.evening_time[1] ? (
<Button
onClick={() => {
handleOpen();
setDataChange({
is_morgen: false,
idx,
});
}}
className="!gap-[6px] !min-w-[146px] !py-[4px] dark:!text-[#D7D8ED] !px-[8px] !text-[#616161] !text-[16px] !font-bold"
>
{row.morning_time[0] === "تعطیل"
? row.morning_time[0]
: `${row.evening_time[0]} - ${row.evening_time[1]}`}
<EditOrangeAD />
</Button>
) : (
<ModalAddTime
setDataChange={setDataChange}
handleOpen={handleOpen}
setData={setData}
isMorgen={false}
idx={idx}
/>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<ContentModal
open={open}
data={data}
parent={parent}
setOpen={setOpen}
setData={setData}
dataChange={dataChange}
handleClose={handleClose}
setIsDisable={setIsDisable}
/>
</div>
);
}
export default List;
@@ -1,64 +0,0 @@
import { Button } from "@mui/material";
import ArrowLeftWhiteD from "@/components/icons/ArrowLeftWhiteD";
import { request } from "@/services/response";
import { useState } from "react";
import moment from "jalali-moment";
import { defaultState } from "../../defaultState";
const dateToTimestamp = (date) => {
const gregorianMoment = moment(date, "jYYYY/jM/jD").locale("fa");
const unixTimestamp = gregorianMoment.valueOf();
return unixTimestamp / 1000;
};
const transformData = (data) => {
return {
name: data.general.name.value,
activity_time: dateToTimestamp(data.general.time_work.value),
doctor_mobile_number: data.office.phone.value,
state: [Number(data.office.state.value)],
city: [Number(data.office.city.value)],
degree: data.general.degree.value,
specialty: [Number(data.general.expertise.value)],
info: data.general.description.value,
};
};
function SendReq({ data, isDisable, resetData }) {
const [loading, setLoading] = useState(false);
const saveData = () => {
setLoading(true);
const transformed = transformData(data);
request
.postDoctor(transformed)
.then(() => {
resetData();
setLoading(false);
})
.catch(() => {
setLoading(false);
});
};
return (
<Button
className="!gap-[4px] !w-full md:!w-fit !mr-auto !text-nowrap !flex !flex-nowrap !py-[8px]
!rounded-[4px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium
!mt-[48px] md:!mt-[44px] lg:!mt-[40px] !px-[16px] sm:!px-[19px] md:!px-[22px] lg:!px-[24px]"
disabled={isDisable}
variant="contained"
onClick={saveData}
loading={loading}
>
<p className={loading ? "opacity-0" : ""}>ثبت اطلاعات</p>
<div className={isDisable ? "opacity-40" : "opacity-100"}>
<ArrowLeftWhiteD />
</div>
</Button>
);
}
export default SendReq;
@@ -1,26 +0,0 @@
import List from "./List";
import { useState } from "react";
import ScrollToTop from "@/app/component/ScrollToTop";
import SendReq from "./SendReq";
function WorkingDays({ parent, data, setData, resetData }) {
const [isDisable, setIsDisable] = useState(false);
return (
<div className="opacity-page pb-[24px]">
<ScrollToTop />
<p className="text-[#525252] dark:text-[#D7D8ED] text-[14px] md:text-[16px] text-center md:text-start font-bold">
لیست روزهای کاری
</p>
<List
data={data}
parent={parent}
setData={setData}
setIsDisable={setIsDisable}
/>
<SendReq resetData={resetData} isDisable={isDisable} data={data} />
</div>
);
}
export default WorkingDays;
@@ -1,122 +0,0 @@
import { Button } from "@mui/material";
import ContentText from "@/app/component/ContentText";
import TimePickerField from "@/app/component/TimePickerField";
import AutoCompleteSelect from "@/app/component/element/AutoCompleteSelect";
import { turnTime, workplace } from "./listSelector";
import EditField from "@/app/component/EditField";
import { useState } from "react";
const validate_number_turns = {
min: 1,
max: 10,
};
function Form({
handleClose,
isVacation,
parent,
setData,
dataChange,
data,
value,
}) {
const [update, setUpdate] = useState(false);
const updateData = (value, type, name) =>
setData((prev) => {
const newData = prev;
newData[parent][dataChange.idx][name] = value;
return newData;
});
const updateNum = (value, type, name) => {
const prevData = data;
const prevVal = data[parent][dataChange.idx][name].value;
const parseValue = +value;
const { max } = validate_number_turns;
const validateValue = parseValue > max ? prevVal : value;
data[parent][dataChange.idx][name].value = validateValue;
setUpdate(!update);
setData(prevData);
};
return (
<div className="flex flex-col gap-[32px] md:gap-[36px] lg:gap-[40px] min-w-fit md:min-w-[420px]">
<div className="flex relative flex-col gap-[16px]">
<ContentText text="تعداد نوبت">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data[parent][dataChange.idx].number_turns}
noDisable={true}
type="number"
placeholder="انتخاب کنید..."
name="number_turns"
props={{
inputProps: validate_number_turns,
}}
/>
</ContentText>
<ContentText text="زمان نوبت">
<AutoCompleteSelect
updateData={updateData}
label="زمان نوبت"
name="turn_time"
list={turnTime}
/>
</ContentText>
<ContentText text="محل کار">
<AutoCompleteSelect
updateData={updateData}
label="محل کار"
name="workplace"
list={workplace}
/>
</ContentText>
<ContentText text="زمان شروع">
<TimePickerField
dataChange={dataChange}
isDisable={isVacation}
disable={value === 1}
setData={setData}
timeStart={true}
data={data[parent]}
/>
</ContentText>
<ContentText text="زمان پایان">
<TimePickerField
dataChange={dataChange}
isDisable={isVacation}
disable={value === 1}
setData={setData}
timeStart={false}
data={data[parent]}
/>
</ContentText>
<div
className={`
absolute transition-all duration-500 right-0 top-0 w-full h-full bg-[rgba(255,255,255,.85)] dark:bg-[rgba(34,36,51,.85)]
${isVacation ? "max-w-full" : "max-w-0"}`}
></div>
</div>
<div className="flex flex-wrap items-center justify-center gap-[16px]">
<Button
variant="outlined"
onClick={handleClose}
className="!text-[16px] !max-w-[calc(50%_-_8px)] dark:!bg-[#C7CEF4] dark:!text-[#5559CE] !font-medium !border-[#5559CE] !py-[5px] md:!py-[7px] lg:!py-[9px] !shadow-none !px-[46px]"
>
انصراف
</Button>
<Button
variant="contained"
onClick={handleClose}
className="!text-[16px] !max-w-[calc(50%_-_8px)] !font-medium !shadow-none !py-[5px] md:!py-[7px] lg:!py-[9px] !px-[46px]"
>
ذخیره
</Button>
</div>
</div>
);
}
export default Form;
@@ -1,26 +0,0 @@
import AddTimeP from "@/components/icons/AddTimeP";
import { Button } from "@mui/material";
import React from "react";
function ModalAddTime({ handleOpen, isMorgen, idx, setDataChange }) {
return (
<div>
<Button
variant="text"
onClick={() => {
handleOpen();
setDataChange({
is_morgen: isMorgen,
idx,
});
}}
className="!gap-[2px] md:!min-w-[146px] sm:!gap-[4px] md:!gap-[6px] lg:!gap-[8px] !p-1 !text-[#5559CE] !text-[14px] md:!text-[16px] !font-medium"
>
<AddTimeP />
انتخاب کنید
</Button>
</div>
);
}
export default ModalAddTime;
@@ -1,32 +0,0 @@
import { FormControlLabel, Radio, RadioGroup } from "@mui/material";
function Radios({ value, handleChange }) {
return (
<RadioGroup
dir="ltr"
defaultValue={value}
onChange={handleChange}
className="!flex radio-mui !flex-row !mb-[24px] !my-[40px] !gap-[36px] !items-center !justify-end"
sx={{
"& .muirtl-j204z7-MuiFormControlLabel-root": {
marginLeft: "0 !important",
},
}}
>
<FormControlLabel
className="!text-[#A1A1A1]"
value={0}
control={<Radio />}
label="روز کاری"
/>
<FormControlLabel
className="!text-[#A1A1A1]"
value={1}
control={<Radio />}
label="تعطیل"
/>
</RadioGroup>
);
}
export default Radios;
@@ -1,71 +0,0 @@
import CloseModalD from "@/components/icons/CloseModalD";
import { styleDefault } from "@/mui";
import { Box, Button, Modal } from "@mui/material";
import Radios from "./Radios";
import Form from "./Form";
import { useState } from "react";
import { checkTimes } from "@/helper";
function ContentModal({
open,
data,
setData,
parent,
dataChange,
handleClose,
setIsDisable,
}) {
const [value, setValue] = useState(0);
const [isVacation, setIsVacation] = useState(false);
const handleChange = (event) => {
const valueRadio = +event.target.value;
const newData = data[parent];
const { idx, is_morgen } = dataChange;
newData[idx][is_morgen ? "morning_time" : "evening_time"][0] = "تعطیل";
setIsVacation(valueRadio);
setValue(valueRadio);
const disable = checkTimes(newData).includes(false);
!disable && setIsDisable(disable);
};
const closeModal = () => {
handleClose();
setValue(0);
};
return (
<Modal open={open} onClose={closeModal}>
<Box
sx={styleDefault}
className="!w-full !rounded-none md:!rounded-[8px] dark:!bg-[#222433] dark:!shadow-[0px_4px_25px_10px_#1F1D2B]
md:!w-fit !h-full md:!h-fit !py-[20px] !px-[16px] !pb-[56px]"
>
<div className="flex items-center justify-between">
<p className="text-[#525252] dark:text-[#D7D8ED] text-[16px] md:text-[18px] lg:text-[20px] font-bold">
اضافه کردن روز کاری
</p>
<Button variant="text" className="!p-1" onClick={closeModal}>
<CloseModalD />
</Button>
</div>
<div className="mx-0 md:mx-[52px]">
<Radios value={value} handleChange={handleChange} />
<Form
handleClose={closeModal}
dataChange={dataChange}
isVacation={value}
setData={setData}
parent={parent}
value={value}
data={data}
/>
</div>
</Box>
</Modal>
);
}
export default ContentModal;
@@ -1,27 +0,0 @@
const numberTurns = [
{ id: "1", label: 1 },
{ id: "2", label: 2 },
{ id: "3", label: 3 },
];
const turnTime = [
{ id: 1, label: "00:05" },
{ id: 2, label: "00:10" },
{ id: 3, label: "00:15" },
{ id: 4, label: "00:20" },
{ id: 5, label: "00:25" },
{ id: 6, label: "00:30" },
{ id: 7, label: "00:40" },
{ id: 8, label: "00:50" },
{ id: 9, label: "01:00" },
{ id: 10, label: "01:15" },
{ id: 11, label: "01:30" },
];
const workplace = [
{ id: "1", label: "تهران" },
{ id: "2", label: "رشت" },
{ id: "3", label: "اصفهان" },
];
export { numberTurns, turnTime, workplace };
+7 -4
View File
@@ -23,18 +23,21 @@ export const handleTwoLength = (value) =>
export const checkAllValues = (data) => {
for (let key in data) {
if (typeof data[key] === "object" && data[key] !== null) {
if ("value" in data[key]) {
if (!data[key].value) {
const item = data[key];
if (typeof item === "object" && item !== null) {
if ("value" in item) {
if (!item.value) {
return false;
}
} else {
if (!checkAllValues(data[key])) {
if (!checkAllValues(item)) {
return false;
}
}
}
}
return true;
};
+5
View File
@@ -6,6 +6,11 @@ export function defineAbilitiesFor(user) {
if (user) {
can("access", "Dashboard");
cannot("access", "Login");
const parsedData = JSON.parse(user.value);
if (parsedData.roles.find((item) => item === "representation")) {
can("access", "Panel");
}
} else {
can("access", "Login");
cannot("access", "Dashboard");
+8
View File
@@ -60,4 +60,12 @@ export const request = {
),
postAppointment: (data) => api.post(`api/v1/appointment`, data),
postDoctor: (data) => api.post("api/v1/doctor", data),
postImageUpload: (data) =>
api.post("file/upload/clinic_pro/doctor/field_image", data, {
headers: {
"Content-Type": "application/octet-stream",
"Content-Disposition": 'file; filename="filename.jpg"',
"X-CSRF-Token": "",
},
}),
};