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>
This commit is contained in:
hamed
2026-06-16 00:26:20 +03:30
co-authored by Claude Opus 4.8
parent 12917cd900
commit 7d65239615
18 changed files with 320 additions and 287 deletions
+97
View File
@@ -0,0 +1,97 @@
# اتصال UI غنیِ نظرات/امتیاز صفحه پزشک به قرارداد جدید بک‌اند
## پروژه
`nobat724_front` (سایت عمومی).
> **Cross-repo:** این پرامپت به قرارداد غنیِ بک‌اند وابسته است که در `clinicpro/.claude/prompt/rating-multidimensional-and-rich-comments.md` تعریف شده. **آن پرامپت بک‌اند باید اول اجرا شود.**
## زمینه
UI غنیِ نظرات/امتیاز صفحه پزشک (چارت ۵‌بُعدی + دایره‌ی رضایت + ستاره‌ی کلی + نظرات با نام/عکس/لایک/دیسلایک/پاسخ) از قبل در کد وجود دارد و باید **حفظ** شود. این کامپوننت‌ها فیلدهایی می‌خوانند که بک‌اندِ ساده‌ی قبلی نمی‌داد؛ حالا بک‌اند قرارداد غنی را تأمین می‌کند و فرانت فقط باید درست به آن وصل شود (و چند نقص جزئی رفع شود).
> هشدار: یک تلاش قبلی این UI را به‌اشتباه «ساده‌سازی» کرد و سپس به HEAD برگردانده شد. UI غنی نباید ساده شود؛ فقط به قرارداد جدید وصل شود.
## قرارداد بک‌اند (مرجع — از پرامپت همتا)
- `GET /api/v1/rate/{uuid}` (عمومی) → `{ data: { point, satisfaction, averages:[{name,label,progress}] } }`
- `POST /api/v1/rate` (AUTH) ← `{ doctor_uuid, waiting_time_at_clinic, accuracy_of_diagnosis, doctor_behavior, clinic_cleanliness, doctor_expertise }` (هر کدام ۰–۱۰۰)
- `GET /api/v1/comments/{uuid}` (عمومی) → `{ data: { data: [ { uuid, comment, created, parent, author:{real_name,picture:[{url}]}, like_status:{like_count,dislike_count,current_user_like:{like,dislike}}, replies:[…] } ] } }`
- `POST /api/v1/comment` (AUTH) ← `{ doctor_uuid, comment, parent }`
- `POST /api/v1/like/{commentUuid}` (AUTH) ← `{ value: 1|-1 }``{ data: { like_count, dislike_count, current_user_like } }`
- `GET /api/v1/rate/{uuid}/eligibility` (AUTH) → `{ data: { eligible: bool } }` (قبلاً اضافه شده)
- گارد بک‌اند: ثبت نظر/امتیاز فقط برای کاربرِ دارای نوبت confirmed در ۳۰ روز گذشته → در غیر این صورت `403 ERR_RATING_NOT_ELIGIBLE`.
## فایل‌های مرتبط (UI موجود که باید وصل شود)
| فایل | نقش | اصلاح موردنیاز |
|---|---|---|
| `app/doctor/[slug]/page.js` | fetch server-side | علاوه بر comments، تجمیع امتیاز از `GET /rate/{uuid}` را fetch کن و به‌صورت prop بده |
| `components/doctor/index.js``detailDoctor/index.js``cards/comments/index.js` | زنجیره prop | `rateAggregate` (point/satisfaction/averages) را عبور بده |
| `cards/comments/chart/index.js` | چارت ۵‌بُعدی + ستاره | به‌جای `doctor.point/satisfaction/average_rate.averages` از prop `rateAggregate` بخوان |
| `cards/comments/chart/ProgressAll.js` | دایره‌ی رضایت | `satisfaction` را از `rateAggregate` بگیرد |
| `cards/comments/modal/ModalAnswer.js` | مودال ثبت | پیش‌بارگذاری امتیاز قبلی را حذف/سازگار کن؛ eligibility بررسی شود |
| `cards/comments/modal/content/index.js` | هدر مودال | عنوان hardcoded «دکتر مهران امینی» → `doctor?.name` |
| `cards/comments/modal/content/Form.js` | ثبت امتیاز+نظر | body امتیاز را با `doctor_uuid` بفرست (نه `doctor_id`)؛ مدیریت ۴۰۳/۴۰۱ |
| `app/component/comment/ItemUser.js` | رندر نظر | `data.created`→تاریخ، author/like_status/replies حفظ؛ لایک/دیسلایک به `postCommentsLike(uuid, value)` وصل شود |
| `app/component/comment/SendAnswer.js` | ثبت پاسخ | body را با `doctor_uuid` و `parent` بفرست |
| `services/response.js` | wrappers | `postDoctorRate` بدنه‌ی چندبُعدی؛ `postCommentsLike(uuid, value)`؛ `getRateEligibility` (موجود) |
## وظایف
### ۱. fetch تجمیع امتیاز در صفحه و عبور prop
در `app/doctor/[slug]/page.js` کنار fetch فعلیِ doctor/comments، تجمیع امتیاز را بگیر:
```jsx
const resRate = await axiosInstance.get(`${API_URL}/api/v1/rate/${doctor.uuid}`);
const rateAggregate = resRate?.data?.data ?? { point: 0, satisfaction: 0, averages: [] };
```
سپس `rateAggregate` را از `DoctorPage``DetailDoctor``Comments``Chart`/`ProgressAll` عبور بده.
### ۲. اتصال چارت به قرارداد جدید (`chart/index.js` + `ProgressAll.js`)
- `Chart` به‌جای `doctor?.average_rate?.averages` از `rateAggregate.averages` استفاده کند (همان شکل `{name,label,progress}` که `ProgressDetail` انتظار دارد).
- `امتیاز کلی کاربران: {rateAggregate.point} از ۵` و ستاره `value={rateAggregate.point}`.
- `{rateAggregate.satisfaction}% رضایت کاربران` و `ProgressAll` با `satisfaction={rateAggregate.satisfaction}`.
- ساختار/استایل و کامپوننت‌های `ProgressDetail`/`ProgressAll`/loadingها حفظ شوند.
### ۳. فرم ثبت امتیاز چندبُعدی (`Form.js` + `ModalAnswer.js` + `content/index.js`)
- ساختار ۵‌بُعدیِ موجود (`Rates` + `ProgressChange` + `comment.rate`) حفظ شود.
- body امتیاز با **`doctor_uuid`** ارسال شود (نه `doctor_id`):
```jsx
request.postDoctorRate({
doctor_uuid: doctor.uuid,
waiting_time_at_clinic: rate[0].progress,
accuracy_of_diagnosis: rate[1].progress,
doctor_behavior: rate[2].progress,
clinic_cleanliness: rate[3].progress,
doctor_expertise: rate[4].progress,
});
```
(ترتیب ایندکس‌ها را با `progress_detail.json` تطبیق بده — همان مپ فعلیِ Form.)
- نظر با `doctor_uuid` ارسال شود: `postDoctorComment({ doctor_uuid: doctor.uuid, comment, parent: null })`.
- چون بک‌اند upsert است، `patchDoctorRate`/`defaultRate` لازم نیست؛ همیشه `postDoctorRate`.
- عنوان مودال hardcoded → `doctor?.name`.
- در `ModalAnswer`، اگر pre-load امتیاز قبلی مشکل‌ساز است حذفش کن (بک‌اند endpoint «امتیاز کاربر فعلی» ندارد؛ `getDoctorRate` حالا تجمیع برمی‌گرداند نه رأی کاربر) — فرم با مقدار اولیه‌ی صفر باز شود.
- مدیریت خطا: ۴۰۱ → «برای ثبت باید وارد شوید»؛ ۴۰۳ با `ERR_RATING_NOT_ELIGIBLE` → پیام واجد بودن؛ سایر → خطای عمومی. (با `alert`)
### ۴. لایک/دیسلایک واقعی (`ItemUser.js` + `services/response.js`)
- `postCommentsLike` را به `(commentUuid, value)` تغییر بده: `api.post(`api/v1/like/${uuid}`, { value }, { requireAuth: true })`.
- در `ItemUser`، هنگام کلیک لایک `value=1` و دیسلایک `value=-1` بفرست؛ از پاسخ `{like_count,dislike_count,current_user_like}` state محلی را به‌روز کن (به‌جای دستکاری دستیِ فعلی روی `data.like_status`).
- نمایش author/created/replies حفظ شود.
### ۵. ثبت پاسخ (`SendAnswer.js`)
- body را `{ doctor_uuid: doctor.uuid /* یا از data */, comment, parent: data.uuid }` کن (نه `doctor_id`).
- منبع `doctor.uuid` را در این کامپوننت بررسی کن؛ اگر در دسترس نیست، از prop عبور بده.
### ۶. wrapperها (`services/response.js`)
- `postDoctorRate(data)` → `POST api/v1/rate` با `requireAuth` (بدنه‌ی چندبُعدی؛ بدون تغییر امضا، فقط دیتای جدید).
- `getRateEligibility(uuid)` (موجود) حفظ شود.
- `getDoctorRate(uuid)` حالا تجمیع برمی‌گرداند — مصرف‌کننده‌ها (chart) باید `data.point/...` بخوانند.
## نکات مهم
- **UI غنی حفظ شود؛ ساده نکن.** فقط به قرارداد جدید وصل کن و نقص‌های جزئی (hardcode، `doctor_id`→`doctor_uuid`، toggle لایک) را رفع کن.
- `app/component/comment/*` بین صفحه پزشک و بلاگ مشترک است، اما فراخوانی آن در بلاگ (`components/blog/detail/index.js`) کامنت‌اوت است؛ پس تغییرات صفحه پزشک بلاگ را نمی‌شکند — ولی هنگام تغییر `ItemUser`/`Comment` این اشتراک را در نظر بگیر و چیزی که build بلاگ را بشکند وارد نکن.
- `current_user_like` در لیست عمومی همیشه false است (طبق تصمیم بک‌اند)؛ وضعیت واقعی پس از کلیک از پاسخ `toggleLike` می‌آید — UI را optimistic نگه‌دار.
- نظرات نیازمند تأیید ادمین‌اند؛ لیست لوکال ممکن است خالی باشد → empty-state، نه خطا.
- App Router + RTL + Vazir؛ کتابخانه‌ی جدید اضافه نکن.
- بعد از تغییر: `npm run build` بدون خطا؛ `/doctor/[slug]` و `/blog/[slug]` هر دو باید کامپایل شوند.