handle like comments data, req for update comments and add loading for like comment && search specialty with searchbar in doctors page && set category and specialty data

This commit is contained in:
ehsan
2025-09-20 17:04:36 +03:30
parent 2f078eba3c
commit dfc8492afa
14 changed files with 366 additions and 121 deletions
+27
View File
@@ -0,0 +1,27 @@
import ArrowUpComment from "@/components/icons/ArrowUpComment";
import { Button } from "@mui/material";
function AnswerField({ data, update, setUpdate }) {
return (
<div className="flex items-center justify-start gap-1 my-2">
<Button
className="!text-[#0FA1B3] !text-[12px] !font-medium"
onClick={() => {
data.is_open_reply = !data.is_open_reply;
setUpdate(!update);
}}
>
مشاهده پاسخ ها ({data.replies.length})
<div
className={`!mr-1 ${
data.is_open_reply ? "!rotate-0" : "!rotate-180"
}`}
>
<ArrowUpComment />
</div>
</Button>
</div>
);
}
export default AnswerField;
+2 -1
View File
@@ -1,12 +1,13 @@
import { TextField } from "@mui/material"; import { TextField } from "@mui/material";
function Field({ title, type, multiline, isPlaceHolder }) { function Field({ title, type, updateValue, value, multiline, isPlaceHolder }) {
return ( return (
<TextField <TextField
placeholder={isPlaceHolder && title} placeholder={isPlaceHolder && title}
label={!isPlaceHolder ? title : ""} label={!isPlaceHolder ? title : ""}
type={type || "text"} type={type || "text"}
className="!w-full !mx-auto !bg-transparent" className="!w-full !mx-auto !bg-transparent"
onChange={(e) => updateValue(e.target.value)}
sx={{ sx={{
"& .MuiFormLabel-root": { "& .MuiFormLabel-root": {
top: "-4px !important", top: "-4px !important",
+93 -88
View File
@@ -1,28 +1,73 @@
import Image from "next/image"; import Image from "next/image";
import { Button, Skeleton } from "@mui/material"; import { Button, CircularProgress } from "@mui/material";
import Field from "./Field";
// Icons // Icons
import BoldDisLikeComment from "@/components/icons/BoldDisLikeComment"; import BoldDisLikeComment from "@/components/icons/BoldDisLikeComment";
import BoldLikeComment from "@/components/icons/BoldLikeComment"; import BoldLikeComment from "@/components/icons/BoldLikeComment";
import DisLikeComment from "@/components/icons/DisLikeComment"; import DisLikeComment from "@/components/icons/DisLikeComment";
import ArrowUpComment from "@/components/icons/ArrowUpComment";
import LikeComment from "@/components/icons/LikeComment"; import LikeComment from "@/components/icons/LikeComment";
import Replies from "./Replies";
import AnswerField from "./AnswerField";
import SendAnswer from "./SendAnswer";
import { timeAgo } from "@/helper";
import { request } from "@/services/response";
import { useState } from "react";
function ItemUser({ update, setUpdate, isReply, list, setList, data }) { function ItemUser({ update, setUpdate, data }) {
const handleLike = (lengthLike, data, nameLike, nameIsLike) => { const [loadingLike, setLoadingLike] = useState(false);
if (nameIsLike === "is_like" && data.is_dislike) { const [loadingDislike, setLoadingDislike] = useState(false);
data.is_dislike = false;
data.like_status.dislike_count--; const SendReq = async (type) => {
} else if ( if (type === "like") setLoadingLike(true);
nameIsLike === "is_dislike" && else setLoadingDislike(true);
data.like_status.current_user_like.like
) { const body = {
data.like_status.current_user_like.like = false; comment_id: data.id,
data.like_status.like_count--; like: type === "like" ? 1 : 0,
};
try {
const res = await request.postCommentsLike(body);
console.log(res);
} catch (err) {
console.log(err);
} finally {
if (type === "like") setLoadingLike(false);
else setLoadingDislike(false);
} }
data[nameLike] = lengthLike; };
data[nameIsLike] = !data[nameIsLike];
const handleLike = (data, type) => {
SendReq(type);
const { current_user_like } = data.like_status;
if (type === "like") {
if (current_user_like.like) {
data.like_status.like_count -= 1;
current_user_like.like = false;
} else {
data.like_status.like_count += 1;
current_user_like.like = true;
if (current_user_like.dislike) {
data.like_status.dislike_count -= 1;
current_user_like.dislike = false;
}
}
} else {
if (current_user_like.dislike) {
data.like_status.dislike_count -= 1;
current_user_like.dislike = false;
} else {
data.like_status.dislike_count += 1;
current_user_like.dislike = true;
if (current_user_like.like) {
data.like_status.like_count -= 1;
current_user_like.like = false;
}
}
}
setUpdate(!update); setUpdate(!update);
}; };
@@ -30,7 +75,7 @@ function ItemUser({ update, setUpdate, isReply, list, setList, data }) {
<li className="w-full"> <li className="w-full">
<div className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<Image <Image
className="!w-[32px] !h-[32px] sm:!w-[35px] sm:!h-[35px] md:!h-[37px] md:!w-[37px] lg:!w-[40px] lg:!h-[40px]" className="!w-[32px] !h-[32px]"
src={data.author?.picture[0]?.url} src={data.author?.picture[0]?.url}
width={40} width={40}
height={40} height={40}
@@ -41,59 +86,60 @@ function ItemUser({ update, setUpdate, isReply, list, setList, data }) {
{data.author.real_name} {data.author.real_name}
</p> </p>
<p className="text-[#7E7E7E] text-[12px] md:text-[14px] font-medium"> <p className="text-[#7E7E7E] text-[12px] md:text-[14px] font-medium">
3 روز پیش {timeAgo(data.created)}
</p> </p>
</div> </div>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<p className="text-[#495057] text-[12px] md:text-[14px] font-normal leading-[20px] sm:leading-[24px] md:leading-[28px] lg:leading-[30px]"> <p className="text-[#495057] text-[12px] md:text-[14px]">
{data.comment} {data.comment}
</p> </p>
<div className="flex items-center justify-start mt-2 gap-[28px]"> <div className="flex items-center justify-start mt-2 gap-[28px]">
{/* لایک */}
<div className="flex items-center justify-start gap-1"> <div className="flex items-center justify-start gap-1">
<div <div
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() => handleLike(data, "like")}
handleLike(
data.like_status.current_user_like.like
? data.like_status.like_count - 1
: data.like_status.like_count + 1,
data,
"like",
"is_like"
)
}
> >
{data.like_status.current_user_like.like ? ( {loadingLike ? (
<CircularProgress
className="!w-[24px] !h-[24px]"
sx={{ color: "#7E7E7E" }}
/>
) : data.like_status.current_user_like.like ? (
<BoldLikeComment /> <BoldLikeComment />
) : ( ) : (
<LikeComment /> <LikeComment />
)} )}
</div> </div>
<p className="text-[#6C757D] text-[12px] font-normal select-none"> <p className="text-[#6C757D] text-[12px] select-none">
{data.like_status.like_count || ""} {data.like_status.like_count || ""}
</p> </p>
</div> </div>
{/* دیس‌لایک */}
<div className="flex items-center justify-start gap-1"> <div className="flex items-center justify-start gap-1">
<div <div
className="cursor-pointer" className="cursor-pointer"
onClick={() => onClick={() => handleLike(data, "dislike")}
handleLike(
data.is_dislike
? data.like_status.dislike_count - 1
: data.like_status.dislike_count + 1,
data,
"dislike",
"is_dislike"
)
}
> >
{data.is_dislike ? <BoldDisLikeComment /> : <DisLikeComment />} {loadingDislike ? (
<CircularProgress
className="!w-[24px] !h-[24px]"
sx={{ color: "#7E7E7E" }}
/>
) : data.like_status.current_user_like.dislike ? (
<BoldDisLikeComment />
) : (
<DisLikeComment />
)}
</div> </div>
<p className="text-[#6C757D] text-[12px] font-normal select-none"> <p className="text-[#6C757D] text-[12px] select-none">
{data.like_status.dislike_count || ""} {data.like_status.dislike_count || ""}
</p> </p>
</div> </div>
<Button <Button
className="!text-[#495057] !text-[12px] !font-medium" className="!text-[#495057] !text-[12px] !font-medium"
onClick={() => { onClick={() => {
@@ -105,53 +151,12 @@ function ItemUser({ update, setUpdate, isReply, list, setList, data }) {
پاسخ پاسخ
</Button> </Button>
</div> </div>
<div
className={`flex flex-col mt-2 justify-center overflow-hidden transition duration-500 items-start gap-2 mb-2 <SendAnswer data={data} />
${data.is_open_answer ? "max-h-fit" : "max-h-0"}`}
>
<Field type="text" title="پاسخ شما..." isPlaceHolder={true} />
<Button
variant="contained"
className="!p-2 !h-10 !text-[12px] !font-medium !text-[#FFF] sm:!py-[10px] md:!px-[12px] !rounded-[8px] !bg-[#5559CE]
!shadow-none sm:!shadow-[0px_4px_30px_0px_rgba(56,_160,_162,_0.30)]"
>
ارسال پاسخ
</Button>
</div>
{!!data.replies.length && ( {!!data.replies.length && (
<div className="flex items-center justify-start gap-1 my-2"> <AnswerField data={data} update={update} setUpdate={setUpdate} />
<Button
className="!text-[#0FA1B3] !text-[12px] !font-medium"
onClick={() => {
data.is_open_reply = !data.is_open_reply;
setUpdate(!update);
}}
>
مشاهده پاسخ ها ({data.replies.length})
<div
className={`!mr-1 ${
data.is_open_reply ? "!rotate-0" : "!rotate-180"
}`}
>
<ArrowUpComment />
</div>
</Button>
</div>
)} )}
<ul <Replies data={data} update={update} setUpdate={setUpdate} />
className={`flex pr-12 flex-col justify-center transition-all duration-500 overflow-hidden items-start gap-12 w-full
${data.is_open_reply ? "max-h-fit mt-12" : "max-h-0"}`}
>
{data.replies.map((item, idx) => (
<ItemUser
key={idx}
data={item}
isReply={true}
update={update}
setUpdate={setUpdate}
/>
))}
</ul>
</div> </div>
</li> </li>
); );
+23
View File
@@ -0,0 +1,23 @@
import React from "react";
import ItemUser from "./ItemUser";
function Replies({ data, update, setUpdate }) {
return (
<ul
className={`flex pr-12 flex-col justify-center transition-all duration-500 overflow-hidden items-start gap-12 w-full
${data.is_open_reply ? "max-h-fit mt-12" : "max-h-0"}`}
>
{data.replies.map((item, idx) => (
<ItemUser
key={idx}
data={item}
isReply={true}
update={update}
setUpdate={setUpdate}
/>
))}
</ul>
);
}
export default Replies;
+59
View File
@@ -0,0 +1,59 @@
import { Button } from "@mui/material";
import Field from "./Field";
import { useState } from "react";
import { request } from "@/services/response";
function SendAnswer({ data }) {
const [comment, setComment] = useState("");
const [loading, setLoading] = useState(false);
const resetData = () => {
setComment("");
setLoading(false);
data.is_open_answer = false;
};
const sendReq = () => {
setLoading(true);
const body = {
comment,
doctor_id: data?.doctor?.id,
parent: data?.parent,
};
request
.postDoctorComment(body)
.then(() => {
resetData();
})
.catch(() => {
resetData();
});
};
return (
<div
className={`flex flex-col mt-2 justify-center overflow-hidden transition duration-500 items-start gap-2 mb-2
${data.is_open_answer ? "max-h-fit" : "max-h-0"}`}
>
<Field
type="text"
value={comment}
title="پاسخ شما..."
isPlaceHolder={true}
updateValue={(val) => setComment(val)}
/>
<Button
loading={loading}
variant="contained"
onClick={sendReq}
className="!p-2 !h-10 !text-[12px] !font-medium sm:!py-[10px] md:!px-[12px] !rounded-[8px]
!shadow-none sm:!shadow-[0px_4px_30px_0px_rgba(56,_160,_162,_0.30)]"
>
ارسال پاسخ
</Button>
</div>
);
}
export default SendAnswer;
-2
View File
@@ -17,10 +17,8 @@ function Comment({ data, comments }) {
comments.map((item, idx) => ( comments.map((item, idx) => (
<ItemUser <ItemUser
key={idx} key={idx}
list={list}
data={item} data={item}
update={update} update={update}
setList={setList}
setUpdate={setUpdate} setUpdate={setUpdate}
/> />
))} ))}
@@ -7,9 +7,6 @@ import TextLoading from "@/app/component/loading/Text";
import CustomLoading from "@/app/component/loading/Custom"; import CustomLoading from "@/app/component/loading/Custom";
function Chart({ comments, doctor }) { function Chart({ comments, doctor }) {
console.log(doctor);
console.log(comments);
return ( return (
<div <div
className=" className="
@@ -50,7 +50,7 @@ function ModalAnswer({ doctor }) {
setLoading(true); setLoading(true);
request request
.postDoctorComment(comment.detail) .postDoctorComment(comment.detail)
.then((res) => { .then(() => {
let newComment = comment.rate; let newComment = comment.rate;
newComment = { newComment = {
correct_diagnosis: newComment[1].progress, correct_diagnosis: newComment[1].progress,
@@ -105,6 +105,7 @@ function ModalAnswer({ doctor }) {
<Rating <Rating
className="!my-[24px]" className="!my-[24px]"
value={averageRate} value={averageRate}
readOnly
dir="ltr" dir="ltr"
sx={{ sx={{
"& .MuiSvgIcon-root": { "& .MuiSvgIcon-root": {
+79
View File
@@ -0,0 +1,79 @@
import { Autocomplete, TextField } from "@mui/material";
function AutoComplete({
data,
noneBg,
clearText,
inputValue,
placeholder,
handleSearch,
setInputValue,
setSelectedOption,
}) {
const clearIndicatorProps = clearText
? {
clearIndicator: {
onClick: () => {
clearText();
},
},
}
: {};
console.log(inputValue);
return (
<Autocomplete
freeSolo
options={data}
getOptionLabel={(option) => option.name}
disableClearable
inputValue={inputValue || ""}
className="!w-full"
onInputChange={(_, newInputValue) => {
setInputValue && setInputValue(newInputValue);
handleSearch(newInputValue);
}}
onChange={(_, newValue) => {
setSelectedOption && setSelectedOption(newValue);
handleSearch(newValue ? newValue.name : "");
}}
// componentsProps={clearIndicatorProps}
renderOption={(props, option) => (
<li {...props} key={option.id}>
{option.name}
</li>
)}
sx={{
"& .MuiAutocomplete-input": {
background: noneBg ? "" : "#ffffff !important",
},
}}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder={placeholder}
dir="rtl"
InputProps={{
...params.InputProps,
disableUnderline: true,
style: { height: "100%" },
}}
sx={{
background: "transparent !important",
"& .MuiInputBase-input": {
background: "#FAFAFA",
color: "#6C757D",
padding: "0 16px",
fontSize: "14px",
fontFamily: "inherit",
},
}}
className="!shadow-none !w-full !px-5 !h-full !text-[14px] md:!text-[16px] !min-w-[50%] !rounded-0 !font-normal"
/>
)}
/>
);
}
export default AutoComplete;
+34 -4
View File
@@ -1,7 +1,7 @@
import AutoCompleteSearch from "@/components/searchHead/smSearch/AutoCompleteSearch";
import specialties from "@/data/specialties.json";
import { clearFilterParams } from "@/helper"; import { clearFilterParams } from "@/helper";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import specialties from "@/data/specialties.json";
import AutoComplete from "./AutoComplete";
function SearchField({ filter, setFilter, updateData, setDataInURL }) { function SearchField({ filter, setFilter, updateData, setDataInURL }) {
const router = useRouter(); const router = useRouter();
@@ -19,13 +19,43 @@ function SearchField({ filter, setFilter, updateData, setDataInURL }) {
setFilter(newFilter); setFilter(newFilter);
// setDataInURL(newFilter); // setDataInURL(newFilter);
}; };
console.log(filter);
const handleSearch = (e) => {
const findName = specialties.find((item) => item.name === e);
console.log(findName);
if (findName) {
let newFilter = { ...filter };
if (findName.parent) {
const itemParent = specialties.find(
(item) => item.id === findName.parent
);
newFilter.category = itemParent;
newFilter.specialty = findName;
// updateData("name", e);
} else {
newFilter.category = findName;
newFilter.specialty = "";
}
newFilter.name = e
setFilter(newFilter);
// const newFilter = { ...filter, [name]: e };
// setFilter(newFilter);
} else {
updateData("name", e);
}
};
return ( return (
<div className="!min-w-[50%] !w-[524px]"> <div className="!min-w-[50%] !w-[524px]">
<AutoCompleteSearch <AutoComplete
data={specialties} data={specialties}
clearText={clearText} clearText={clearText}
inputValue={filter.name} inputValue={filter.name}
handleSearch={(e) => updateData("name", e)} handleSearch={handleSearch}
// handleSearch={(e) => updateData("name", e)}
placeholder="جستجوی نام پزشک، تخصص، بیماری ..." placeholder="جستجوی نام پزشک، تخصص، بیماری ..."
/> />
</div> </div>
+21 -21
View File
@@ -5,33 +5,33 @@ import specialties from "@/data/specialties.json";
function SendReq({ filter, setFilter, setDataInURL, sendReq }) { function SendReq({ filter, setFilter, setDataInURL, sendReq }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const checkName = () => { // const checkName = () => {
let newFilter = filter; // let newFilter = filter;
const specialtyItem = // const specialtyItem =
filter.name && // filter.name &&
specialties.find((item) => item.name.includes(filter.name)); // specialties.find((item) => item.name.includes(filter.name));
if (specialtyItem) { // if (specialtyItem) {
if (specialtyItem.parent) { // if (specialtyItem.parent) {
newFilter.specialty = specialtyItem; // newFilter.specialty = specialtyItem;
newFilter.category = specialties.find( // newFilter.category = specialties.find(
(item) => item.id === specialtyItem.parent // (item) => item.id === specialtyItem.parent
); // );
} else { // } else {
newFilter.specialty = specialtyItem; // newFilter.specialty = specialtyItem;
} // }
} // }
return newFilter; // return newFilter;
}; // };
const filterData = () => { const filterData = () => {
setLoading(true); setLoading(true);
const newFilter = checkName(); // const newFilter = checkName();
setFilter(newFilter); setFilter(filter);
setDataInURL(newFilter); setDataInURL(filter);
sendReq(newFilter).finally(() => { sendReq(filter).finally(() => {
setLoading(false); setLoading(false);
}); });
}; };
@@ -10,7 +10,6 @@ function AutoCompleteSearch({
setInputValue, setInputValue,
setSelectedOption, setSelectedOption,
}) { }) {
// ایجاد props شرطی برای clearIndicator
const clearIndicatorProps = clearText const clearIndicatorProps = clearText
? { ? {
clearIndicator: { clearIndicator: {
+25
View File
@@ -435,3 +435,28 @@ export const buildDoctorParams = (searchParams) => {
return params; return params;
}; };
export function timeAgo(timestamp) {
const now = moment();
const then = moment.unix(timestamp);
const diffSeconds = now.diff(then, "seconds");
if (diffSeconds < 60) {
return diffSeconds === 0 ? "همین الان" : `${diffSeconds} ثانیه پیش`;
} else if (diffSeconds < 3600) {
const minutes = Math.floor(diffSeconds / 60);
return `${minutes} دقیقه پیش`;
} else if (diffSeconds < 86400) {
const hours = Math.floor(diffSeconds / 3600);
return `${hours} ساعت پیش`;
} else if (diffSeconds < 2592000) {
const days = Math.floor(diffSeconds / 86400);
return `${days} روز پیش`;
} else if (diffSeconds < 31104000) {
const months = Math.floor(diffSeconds / 2592000);
return `${months} ماه پیش`;
} else {
const years = Math.floor(diffSeconds / 31104000);
return `${years} سال پیش`;
}
}
+1
View File
@@ -85,4 +85,5 @@ export const request = {
api.get("api/v1/categorys/doctor_services", removeTokenHead), api.get("api/v1/categorys/doctor_services", removeTokenHead),
postDoctorComment: (data) => api.post("api/v1/clinicpro/comment", data), postDoctorComment: (data) => api.post("api/v1/clinicpro/comment", data),
postDoctorRate: (data) => api.post("api/v1/clinicpro/rate", data), postDoctorRate: (data) => api.post("api/v1/clinicpro/rate", data),
postCommentsLike: (data) => api.post("/api/v1/clinicpro/like", data),
}; };