Files
nobat724_front/app/component/comment/ItemUser.js
T
hamedandClaude Opus 4.8 7d65239615 feat(doctor): wire rich rating/review UI to multi-dimensional API
Connect the existing doctor-page review UI to the rebuilt backend contract
(see clinicpro feat/rating-multidimensional).

- page.js fetches the rating aggregate alongside comments and passes
  rateAggregate down to the chart (point / satisfaction / 5 dimensions).
- Submit form sends the five dimensions with doctor_uuid; comment/reply
  send {doctor_uuid, comment, parent}; 401/403 ERR_RATING_NOT_ELIGIBLE
  surface friendly guidance.
- Like/dislike call POST /like/{uuid} with value and update from the
  response; replies render nested.
- ModalAnswer gates the submit button on GET /rate/{uuid}/eligibility.
- services/response.js: getRateEligibility, postCommentsLike(uuid, value),
  drop unused patchDoctorRate. Fix hardcoded modal title; empty-state for
  no comments. Remove orphaned AnswerField.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 00:26:20 +03:30

150 lines
4.9 KiB
JavaScript

"use client";
import Image from "next/image";
import { imageUrl } from "@/helper";
import { Button, CircularProgress } from "@mui/material";
// Icons
import BoldDisLikeComment from "@/components/icons/BoldDisLikeComment";
import BoldLikeComment from "@/components/icons/BoldLikeComment";
import DisLikeComment from "@/components/icons/DisLikeComment";
import LikeComment from "@/components/icons/LikeComment";
import Replies from "./Replies";
import SendAnswer from "./SendAnswer";
import { alert, isUserLoggedIn, timeAgo } from "@/helper";
import { request } from "@/services/response";
import { useState } from "react";
function ItemUser({ data, doctor, isReply = false }) {
const [loadingLike, setLoadingLike] = useState(false);
const [loadingDislike, setLoadingDislike] = useState(false);
const [openAnswer, setOpenAnswer] = useState(false);
const [status, setStatus] = useState({
like_count: data.like_status?.like_count || 0,
dislike_count: data.like_status?.dislike_count || 0,
like: data.like_status?.current_user_like?.like || false,
dislike: data.like_status?.current_user_like?.dislike || false,
});
const sendReq = async (type) => {
if (!isUserLoggedIn()) {
alert("warning", "برای ثبت نظر باید ابتدا وارد حساب کاربری شوید.");
return;
}
if (type === "like") setLoadingLike(true);
else setLoadingDislike(true);
try {
const res = await request.postCommentsLike(data.uuid, type === "like" ? 1 : -1);
const d = res.data;
setStatus({
like_count: d.like_count,
dislike_count: d.dislike_count,
like: d.current_user_like.like,
dislike: d.current_user_like.dislike,
});
} catch (err) {
alert("error", "خطایی رخ داد! لطفاً دوباره تلاش کنید.");
} finally {
if (type === "like") setLoadingLike(false);
else setLoadingDislike(false);
}
};
const toggleAnswer = () => {
if (isUserLoggedIn()) {
setOpenAnswer((prev) => !prev);
} else {
alert("warning", "برای ارسال پاسخ باید ابتدا وارد حساب کاربری شوید.");
}
};
return (
<li className="w-full">
<div className="flex items-center justify-start gap-2">
<Image
className="!w-[32px] !h-[32px]"
src={imageUrl(data.author?.picture?.[0]?.url)}
width={40}
height={40}
alt="profile"
/>
<div className="flex flex-col items-start justify-center gap-[8px]">
<p className="text-[#343A40] text-[14px] md:text-[16px] font-medium">
{data.author?.real_name}
</p>
<p className="text-[#7E7E7E] text-[12px] md:text-[14px] font-medium">
{timeAgo(data.created)}
</p>
</div>
</div>
<div className="mt-2">
<p className="text-[#495057] text-[12px] md:text-[14px]">{data.comment}</p>
<div className="flex items-center justify-start mt-2 gap-[28px]">
{/* لایک */}
<div className="flex items-center justify-start gap-1">
<div className="cursor-pointer" onClick={() => sendReq("like")}>
{loadingLike ? (
<CircularProgress
className="!w-[24px] !h-[24px]"
sx={{ color: "#7E7E7E" }}
/>
) : status.like ? (
<BoldLikeComment />
) : (
<LikeComment />
)}
</div>
<p className="text-[#6C757D] text-[12px] select-none">
{status.like_count || ""}
</p>
</div>
{/* دیس‌لایک */}
<div className="flex items-center justify-start gap-1">
<div className="cursor-pointer" onClick={() => sendReq("dislike")}>
{loadingDislike ? (
<CircularProgress
className="!w-[24px] !h-[24px]"
sx={{ color: "#7E7E7E" }}
/>
) : status.dislike ? (
<BoldDisLikeComment />
) : (
<DisLikeComment />
)}
</div>
<p className="text-[#6C757D] text-[12px] select-none">
{status.dislike_count || ""}
</p>
</div>
{!isReply && (
<Button
className="!text-[#495057] !text-[12px] !font-medium"
onClick={toggleAnswer}
variant="text"
>
پاسخ
</Button>
)}
</div>
{!isReply && openAnswer && (
<SendAnswer
doctor={doctor}
parentUuid={data.uuid}
onSent={() => setOpenAnswer(false)}
/>
)}
{!isReply && data.replies?.length > 0 && (
<Replies data={data} doctor={doctor} />
)}
</div>
</li>
);
}
export default ItemUser;