responsive pagination && handle free_turn value && fix set value in small size appo list in page doctors && set data rate in doctor item && fix set token in cookie in page login on domain nobat724.com

This commit is contained in:
ehsan
2025-10-04 17:16:47 +03:30
parent e1510b142b
commit 83e482507b
19 changed files with 282 additions and 171 deletions
+2 -3
View File
@@ -10,7 +10,6 @@ import ArrowLeftD from "@/components/icons/ArrowLeftD";
import ClockD from "@/components/icons/ClockD";
import LikeD from "@/components/icons/LikeD";
import PointD from "@/components/icons/PointD";
import { convertTimestampToPersianDateSimple } from "@/helper";
function ItemDoctor({ doctor, loading, setDoctors }) {
const handleLoading = () => {
@@ -90,8 +89,8 @@ function ItemDoctor({ doctor, loading, setDoctors }) {
: "bg-[rgba(211,_47,_47,_0.04)] text-[#D32F2F]"
}`}
>
{Number(doctor?.active)
? `اولین نوبت آزاد: ${convertTimestampToPersianDateSimple(doctor?.free_turn)}`
{doctor?.free_turn
? `اولین نوبت آزاد: ${doctor?.free_turn}`
: "نوبت ندارد"}
</p>
</CustomLoading>
+9 -6
View File
@@ -1,25 +1,28 @@
import { Pagination } from "@mui/material";
import { Pagination, useMediaQuery, useTheme } from "@mui/material";
function PaginationContent({ page, pageDetail, changePage, limit = 12 }) {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("sm"));
const count = Math.ceil(pageDetail.totalRecords / limit);
return (
<Pagination
count={count}
page={page || 1}
size={isSmall ? "small" : "medium"}
onChange={changePage}
color="primary"
className={(!count || count < 2) && "!hidden"}
sx={{
"& .mui-8q7g72-MuiButtonBase-root-MuiPaginationItem-root": {
"& .MuiPaginationItem-root": {
color: "#616161",
fontSize: "16px",
fontWeight: "500",
},
"& .mui-8q7g72-MuiButtonBase-root-MuiPaginationItem-root.Mui-selected":
{
color: "#F8F8FF",
},
"& .MuiPaginationItem-root.Mui-selected": {
color: "#F8F8FF",
},
"& .MuiSvgIcon-root": {
rotate: "180deg !important",
},
+5 -3
View File
@@ -16,7 +16,7 @@ function ProgressDetail({ data, idx }) {
{label[idx]}
</p>
<LinearProgress
value={data}
value={data || 0}
variant="determinate"
className="!w-full lg:!w-[265px] !rounded-[10px] !bg-[#D7D7D7] !h-[11px]"
sx={{
@@ -26,8 +26,10 @@ function ProgressDetail({ data, idx }) {
},
}}
/>
<p className="text-[#525252] text-[20px] font-medium">
{numberToArStyle(data)}%
<p
className={`text-[#525252] font-medium ${data ? "text-[20px]" : "text-[18px]"}`}
>
{data ? `${numberToArStyle(data)}%` : "بدون نمره"}
</p>
</li>
);
+1 -1
View File
@@ -2,7 +2,7 @@ import { Slider } from "@mui/material";
import { useState } from "react";
function ProgressChange({ data, changeData, parent }) {
const [value, setValue] = useState(70);
const [value, setValue] = useState(data.progress);
return (
<li className="flex items-center justify-start gap-3 w-full">
-1
View File
@@ -1,5 +1,4 @@
import ArrowLeftB from "@/components/icons/ArrowLeftB";
import { request } from "@/services/response";
import { Button } from "@mui/material";
import Cookies from "js-cookie";
import { useParams } from "next/navigation";
+2 -3
View File
@@ -37,9 +37,8 @@ function List({
.then((res) => {
if (res.data) setFilteredClinics(res.data);
})
.catch((err) => {
console.log(err);
})
.catch(() => {})
//
.finally(() => setLoading(false));
};
@@ -5,7 +5,6 @@ import GreenCalendarTurn from "@/components/icons/GreenCalendarTurn";
import { Button } from "@mui/material";
import Image from "next/image";
import Link from "next/link";
import { convertTimestampToPersianDateSimple } from "@/helper";
function ItemAppointment({ turn, loading }) {
return (
@@ -45,8 +44,8 @@ function ItemAppointment({ turn, loading }) {
<GreenCalendarTurn />
<TextLoading width={100} height={15} loading={loading}>
<p className="text-[#05BA58] text-[12px] font-medium">
{Number(turn?.free_turn)
? `اولین نوبت آزاد: ${convertTimestampToPersianDateSimple(turn?.free_turn)}`
{turn?.free_turn
? `اولین نوبت آزاد: ${turn?.free_turn}`
: "نوبت ندارد"}
</p>
</TextLoading>
@@ -7,6 +7,7 @@ import TextLoading from "@/app/component/loading/Text";
import CustomLoading from "@/app/component/loading/Custom";
function Chart({ comments, doctor }) {
const averages = doctor?.average_rate?.averages;
return (
<div
className="
@@ -53,12 +54,11 @@ function Chart({ comments, doctor }) {
</div>
<div className="flex flex-wrap items-center justify-center gap-[50px]">
<ul className="flex w-full flex-col items-start justify-start gap-1.5 lg:w-fit">
{doctor?.average_rate?.averages &&
Object.values(doctor?.average_rate?.averages).map((item, idx) => (
<TextLoading width="full" key={idx} height={20}>
<ProgressDetail data={item} idx={idx} />
</TextLoading>
))}
{Object.values(averages ? averages : Array(5).fill(false)).map(
(item, idx) => (
<ProgressDetail data={item} idx={idx} key={idx} />
)
)}
</ul>
<ProgressAll doctor={doctor} />
</div>
@@ -1,3 +1,5 @@
"use client";
import Chart from "./chart";
import Comment from "@/app/component/comment";
import ModalAnswer from "./modal/ModalAnswer";
@@ -1,15 +1,13 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import { styleDefault } from "@/mui";
import { Box, Button, Modal, Rating } from "@mui/material";
import CloseModalD from "@/components/icons/CloseModalD";
import Field from "@/app/component/fields/Field";
import ArrowLeftD from "@/components/icons/ArrowLeftD";
import ProgressChange from "@/app/component/ProgressChange";
import { Box, Modal } from "@mui/material";
import ProgressDetail from "@/data/progress_detail.json";
import { request } from "@/services/response";
import BtnNewComment from "./BtnNewComment";
import Content from "./content";
import { request } from "@/services/response";
import Cookies from "js-cookie";
function ModalAnswer({ doctor }) {
const [open, setOpen] = useState(false);
@@ -17,67 +15,41 @@ function ModalAnswer({ doctor }) {
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [comment, setComment] = useState({
defaultRate: false,
uuid: "",
rate: ProgressDetail,
detail: {
comment: "",
doctor_id: doctor?.id,
},
});
const changeData = (parent, child, value) => {
if (parent === "rate") {
setComment((prevComment) => ({
...prevComment,
rate: prevComment.rate.map((item) =>
item.name === child ? { ...item, progress: value } : item
),
}));
} else {
setComment((prevComment) => ({
...prevComment,
[parent]: {
...prevComment[parent],
[child]: value,
},
}));
useEffect(() => {
const isLogged = Cookies.get("access_token");
if (doctor && doctor.uuid && isLogged) {
let newRate = comment.rate;
request
.getDoctorRate(doctor.uuid)
.then((res) => {
if (res.doctor_id) {
newRate.map((item) => (item.progress = res[item.name]));
setComment({
...comment,
defaultRate: true,
uuid: res.uuid,
rate: newRate,
});
}
const data = res.response?.data;
// setComment();
})
.catch((err) => {
const data = err.response?.data;
// setComment();
});
}
};
const averageRate =
comment.rate.reduce((sum, item) => sum + item.progress, 0) /
comment.rate.length /
20;
const sendReq = () => {
setLoading(true);
request
.postDoctorComment(comment.detail)
.then(() => {
let newComment = comment.rate;
newComment = {
correct_diagnosis: newComment[1].progress,
doctor_skill: newComment[4].progress,
behavior_doctor: newComment[2].progress,
office_cleaning: newComment[3].progress,
time_in_office: newComment[0].progress,
doctor: doctor?.id,
rate: averageRate,
};
request
.postDoctorRate(newComment)
.then((res) => {
setLoading(false);
setOpen(false);
})
.catch((err) => {
setLoading(false);
setOpen(false);
});
})
.catch((err) => {
setLoading(false);
setOpen(false);
});
};
}, []);
return (
<>
@@ -87,56 +59,15 @@ function ModalAnswer({ doctor }) {
sx={styleDefault}
className="!w-full md:!min-w-[485px] !h-full md:!w-fit md:!h-fit !px-[24px] !pt-[22px] !pb-[30px] !rounded-none md:!rounded-lg"
>
<div className="flex items-center justify-between w-full">
<p className="text-[16px] text-[#525252] font-bold">
امتیاز شما به دکتر مهران امینی
</p>
<div className="cursor-pointer" onClick={handleClose}>
<CloseModalD />
</div>
</div>
<Rating
className="!my-[24px]"
value={averageRate}
readOnly
dir="ltr"
sx={{
"& .MuiSvgIcon-root": {
width: "16px",
height: "16px",
},
}}
<Content
doctor={doctor}
comment={comment}
loading={loading}
setOpen={setOpen}
setLoading={setLoading}
setComment={setComment}
handleClose={handleClose}
/>
<ul className="flex w-full lg:w-fit flex-col items-start justify-start gap-1.5">
{comment.rate?.map((item, idx) => (
<ProgressChange
key={idx}
data={item}
parent="rate"
changeData={changeData}
/>
))}
</ul>
<div className="flex flex-col gap-[16px] justify-center items-start mt-[24px]">
<p className="text-[#495057] text-[16px] font-bold">متن نظر</p>
<Field
type="number"
multiline={true}
handleChange={(_, val) => changeData("detail", "comment", val)}
title="نظر خود را بنویسید..."
/>
<Button
onClick={sendReq}
loading={loading}
variant="contained"
className="!py-2.5 !mr-auto !px-4 gap-1 !text-[14px] !font-medium !mt-[32px] !shadownon"
>
ثبت نظر
<div className="w-[20px] h-[20px]">
<ArrowLeftD />
</div>
</Button>
</div>
</Box>
</Modal>
</>
@@ -0,0 +1,69 @@
import React from "react";
import Field from "@/app/component/fields/Field";
import { Button } from "@mui/material";
import { request } from "@/services/response";
import ArrowLeftD from "@/components/icons/ArrowLeftD";
function Form({ doctor, changeData, setOpen, comment, loading, setLoading }) {
console.log(comment.rate);
const sendReq = () => {
setLoading(true);
request
.postDoctorComment(comment.detail)
.then((res) => {
let newComment = comment.rate;
newComment = {
doctor_id: doctor?.id,
accuracy_of_diagnosis: newComment[1].progress,
doctor_expertise: newComment[4].progress,
doctor_behavior: newComment[2].progress,
clinic_cleanliness: newComment[3].progress,
waiting_time_at_clinic: newComment[0].progress,
};
request[comment.defaultRate ? "patchDoctorRate" : "postDoctorRate"](
newComment,
comment.uuid
)
.then((res) => {
setLoading(false);
setOpen(false);
})
.catch((err) => {
setLoading(false);
setOpen(false);
});
})
.catch((err) => {
setLoading(false);
setOpen(false);
});
};
return (
<div className="flex flex-col gap-[16px] justify-center items-start mt-[24px]">
<p className="text-[#495057] text-[16px] font-bold">متن نظر</p>
<Field
type="number"
multiline={true}
handleChange={(_, val) => changeData("detail", "comment", val)}
title="نظر خود را بنویسید..."
/>
<Button
onClick={sendReq}
loading={loading}
variant="contained"
className="!py-2.5 !mr-auto !px-4 gap-1 !text-[14px] !font-medium !mt-[32px] !shadownon"
>
ثبت نظر
<div className="w-[20px] h-[20px]">
<ArrowLeftD />
</div>
</Button>
</div>
);
}
export default Form;
@@ -0,0 +1,19 @@
import ProgressChange from "@/app/component/ProgressChange";
import React from "react";
function Rates({ comment, changeData }) {
return (
<ul className="flex w-full lg:w-fit flex-col items-start justify-start gap-1.5">
{comment.rate?.map((item, idx) => (
<ProgressChange
key={idx}
data={item}
parent="rate"
changeData={changeData}
/>
))}
</ul>
);
}
export default Rates;
@@ -0,0 +1,73 @@
import Form from "./Form";
import Rates from "./Rates";
import { Rating } from "@mui/material";
import CloseModalD from "@/components/icons/CloseModalD";
function Content({
doctor,
comment,
setOpen,
loading,
setComment,
setLoading,
handleClose,
}) {
const averageRate =
comment.rate?.reduce((sum, item) => sum + item.progress, 0) /
comment.rate?.length /
20;
const changeData = (parent, child, value) => {
if (parent === "rate") {
setComment((prevComment) => ({
...prevComment,
rate: prevComment.rate.map((item) =>
item.name === child ? { ...item, progress: value } : item
),
}));
} else {
setComment((prevComment) => ({
...prevComment,
[parent]: {
...prevComment[parent],
[child]: value,
},
}));
}
};
return (
<>
<div className="flex items-center justify-between w-full">
<p className="text-[16px] text-[#525252] font-bold">
امتیاز شما به دکتر مهران امینی
</p>
<div className="cursor-pointer" onClick={handleClose}>
<CloseModalD />
</div>
</div>
<Rating
className="!my-[24px]"
value={averageRate}
readOnly
dir="ltr"
sx={{
"& .MuiSvgIcon-root": {
width: "16px",
height: "16px",
},
}}
/>
<Rates comment={comment} changeData={changeData} />
<Form
doctor={doctor}
comment={comment}
loading={loading}
setOpen={setOpen}
changeData={changeData}
setLoading={setLoading}
/>
</>
);
}
export default Content;
+2 -8
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import ItemDoctor from "@/app/component/ItemDoctor";
import PaginationContent from "@/app/component/PaginationContent";
import { QueryForDoctorsFilter, QueryForDoctorsReq } from "@/helper";
import { QueryForDoctorsReq } from "@/helper";
import { request } from "@/services/response";
import { useRouter } from "next/navigation";
@@ -37,18 +37,12 @@ function ListDoctors({
delete params.name;
}
console.log(params);
request
.getDoctors(params)
.then((res) => {
console.log(res);
if (res.data) setDoctors(res.data);
})
.catch((err) => {
console.log(err);
})
.catch((err) => {})
.finally(() => setLoading(false));
};
+35 -21
View File
@@ -27,36 +27,51 @@ function SendReq({
handleSetCookie(response);
}
})
.catch(() => {});
.catch(() => {
setLoading(false);
setIsError(true);
});
};
const handleSetCookie = (response) => {
const expiresTime = handleTimeExpiresToken(response.expires_in);
Cookies.set("access_token", response.access_token, {
expires: expiresTime.accessTokenExpires,
const hostname = window.location.hostname;
const isHttps = window.location.protocol === "https:";
const isLocalhost = hostname.includes("localhost");
// تنظیمات پایه
let cookieOptions = {
path: "/",
secure: true,
sameSite: "strict",
});
sameSite: "lax",
expires: expiresTime.accessTokenExpires,
};
// اگر روی دامنه اصلی و https بود
if (!isLocalhost && isHttps) {
cookieOptions = {
...cookieOptions,
secure: true,
domain: ".nobat724.com",
};
}
Cookies.set("access_token", response.access_token, cookieOptions);
Cookies.set("refresh_token", response.refresh_token, {
...cookieOptions,
expires: expiresTime.refreshTokenExpires,
path: "/",
secure: true,
sameSite: "strict",
});
Cookies.set("uuid", uuid, {
...cookieOptions,
expires: expiresTime.refreshTokenExpires,
path: "/",
secure: true,
sameSite: "strict",
});
getInfo(response.access_token);
getInfo(response.access_token, cookieOptions);
};
const getInfo = (token) => {
const getInfo = (token, cookieOptions) => {
request
.getUserInfo({
headers: {
@@ -66,21 +81,20 @@ function SendReq({
.then((res) => {
setLoading(false);
if (res && res.uuid) {
Cookies.set("userInfo", JSON.stringify(res), cookieOptions);
window.location.pathname = "/";
Cookies.set("userInfo", JSON.stringify(res), {
path: "/",
secure: true,
sameSite: "strict",
});
}
})
.catch(() => {});
.catch(() => {
setLoading(false);
setIsError(true);
});
};
return (
<Button
fullWidth
loading={loading}
disabled={loading}
variant="contained"
onClick={() => {
setIsSendMsg && setIsSendMsg(true);
+5 -5
View File
@@ -1,26 +1,26 @@
[
{
"name": "time_in_office",
"name": "clinic_cleanliness",
"label": "زمان انتظار در مطب",
"progress": 70
},
{
"name": "correct_diagnosis",
"name": "accuracy_of_diagnosis",
"label": "تشخیص درست",
"progress": 70
},
{
"name": "behavior_doctor",
"name": "doctor_behavior",
"label": "برخورد مناسب پزشک",
"progress": 70
},
{
"name": "office_cleaning",
"name": "clinic_cleanliness",
"label": "نظافت مطب",
"progress": 70
},
{
"name": "doctor_skill",
"name": "doctor_expertise",
"label": "مهارت پزشک",
"progress": 70
}
+1 -1
View File
@@ -540,7 +540,7 @@ export function timeAgo(timestamp) {
}
export function isUserLoggedIn() {
const userInfo = Cookies.get("userInfo");
const userInfo = Cookies.get("access_token");
return !!userInfo;
}
+5
View File
@@ -1,3 +1,4 @@
import { removeToken } from "@/utils";
import axios from "axios";
import Cookies from "js-cookie";
import "react-toastify/dist/ReactToastify.css";
@@ -20,6 +21,10 @@ api.interceptors.response.use(
return response.data;
},
(error) => {
if (error.status === 401) {
removeToken();
window.location.pathname = "login";
}
return Promise.reject(error);
}
);
+3
View File
@@ -84,7 +84,10 @@ export const request = {
getDoctorServices: () =>
api.get("api/v1/categorys/doctor_services", removeTokenHead),
postDoctorComment: (data) => api.post("api/v1/clinicpro/comment", data),
getDoctorRate: (uuid) => api.get(`api/v1/clinicpro/rate/${uuid}`),
postDoctorRate: (data) => api.post("api/v1/clinicpro/rate", data),
patchDoctorRate: (data, uuid) =>
api.patch(`api/v1/clinicpro/rate/${uuid}`, data),
postCommentsLike: (data) => api.post("/api/v1/clinicpro/like", data),
getClinicList: (params) =>
api.get("/api/v1/clinics", {