feat(booking): show services and duration on the user panel appointment

A patient who booked in service mode could not see which services they had
reserved or how long the appointment was. Both now appear on the list card and in
both detail layouts, reading service_items and service_total_minutes from
GET /api/v1/appointments/user.

Every field is behind an explicit guard. Slot-mode appointments carry none of
them, and an unguarded map would crash the card for every slot-mode appointment,
taking the whole panel with it. A reserve entry shows its services but not a
duration, because it has no time.

Covered by Card.test.jsx: slot-mode appointments render unchanged, absent fields
(older backend) do not crash, reserve hides the duration, and a zero duration
produces no row.

Task: clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-30 15:53:18 +03:30
co-authored by Claude Opus 5
parent d58eec1dba
commit ae958eee73
4 changed files with 152 additions and 0 deletions
@@ -55,6 +55,25 @@ function Card({ data, loading, setIsTurnsDetails }) {
</p>
</TextLoading>
</li>
{/* فیلدهای حالت نوبت‌دهی سرویسی. شرط اجباری است: نوبت اسلاتی این‌ها را ندارد و
بدون شرط، کارتِ همهٔ نوبت‌های اسلاتی می‌شکند — یعنی کل پنل کاربر. */}
{data.service_items?.length > 0 && (
<li className="flex items-start justify-between gap-[8px]">
<p className="text-[#7E7E7E] text-[14px] font-normal shrink-0">سرویس:</p>
<p className="text-[#525252] text-[14px] font-medium text-left">
{data.service_items.map((s) => s.name).join("، ")}
</p>
</li>
)}
{!data.is_reserve && data.service_total_minutes ? (
<li className="flex items-center justify-between">
<p className="text-[#7E7E7E] text-[14px] font-normal">مدت نوبت:</p>
<p className="text-[#525252] text-[14px] font-medium">
{data.service_total_minutes} دقیقه
</p>
</li>
) : null}
</ul>
<div className="mt-[12px] flex items-center justify-end px-[12px]">
<TextLoading loading={loading} width={80} height={25}>