feat(booking): take service duration from the backend, not a parallel client sum
The service picker summed duration_minutes itself while the backend already returns total_duration_minutes. Two sources of truth: when the formula changes to solo/additional minutes, the site would keep showing the old number and the patient would see a duration that does not match their appointment. The picker runs before the date step and appointment-service-slots needs a date, so it is called with today. total_duration_minutes does not depend on the date — the backend computes it before touching that day's shifts, so the number is right even when today is closed and start_times comes back empty. The label reads "مدت تقریبی" until the server number arrives, then "مدت کل". A missing field or a failed request falls back to the client sum with a console.warn rather than blanking the step. Staleness is derived from the selection key instead of reset in the effect body, which also clears the set-state-in-effect lint warning. 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:
@@ -77,6 +77,8 @@ function Container({
|
||||
dateStep = (
|
||||
<ServiceSelect
|
||||
services={bookingServices}
|
||||
doctorUuid={doctor?.uuid}
|
||||
clinicUuid={clinicUuid}
|
||||
onContinue={(uuids) => setSelectedServiceUuids(uuids)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,24 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import moment from "jalali-moment";
|
||||
import { request } from "@/services/response";
|
||||
|
||||
function toToman(rials) {
|
||||
if (!rials) return null;
|
||||
return Math.round(rials / 10).toLocaleString("fa-IR");
|
||||
}
|
||||
|
||||
/**
|
||||
* جمعِ سمتِ کلاینت — فقط fallback برای بکاندی که `total_duration_minutes` نمیدهد.
|
||||
* مدت، دادهٔ سرور است: فرمولش قرار است به «زمان تنها / زمان اضافه» تغییر کند و هر
|
||||
* محاسبهٔ موازی در فرانت از آن روز عددِ غلط نشان میدهد.
|
||||
*/
|
||||
function fallbackSum(services, uuids) {
|
||||
return services
|
||||
.filter((s) => uuids.includes(s.uuid))
|
||||
.reduce((sum, s) => sum + (Number(s.duration_minutes) || 0), 0);
|
||||
}
|
||||
|
||||
// مرحلهٔ انتخاب سرویس (فقط حالت نوبتدهی سرویسی) — پیش از انتخاب روز.
|
||||
function ServiceSelect({ services = [], onContinue }) {
|
||||
function ServiceSelect({ services = [], onContinue, doctorUuid, clinicUuid = null }) {
|
||||
const [draft, setDraft] = useState([]);
|
||||
/** `{ key, minutes }` — کلید همان انتخابی است که عدد به آن تعلق دارد. */
|
||||
const [serverDuration, setServerDuration] = useState(null);
|
||||
|
||||
const toggle = (uuid) =>
|
||||
setDraft((prev) =>
|
||||
prev.includes(uuid) ? prev.filter((u) => u !== uuid) : [...prev, uuid]
|
||||
);
|
||||
|
||||
const totalMinutes = services
|
||||
.filter((s) => draft.includes(s.uuid))
|
||||
.reduce((sum, s) => sum + (Number(s.duration_minutes) || 0), 0);
|
||||
const draftKey = draft.join(",");
|
||||
|
||||
// مدت را سرور حساب میکند. این مرحله پیش از انتخاب روز است، ولی
|
||||
// `total_duration_minutes` به تاریخ وابسته نیست — بکاند آن را پیش از لمسِ
|
||||
// شیفتهای آن روز برمیگرداند، پس حتی اگر امروز تعطیل باشد و `start_times`
|
||||
// خالی بیاید، عدد درست است.
|
||||
useEffect(() => {
|
||||
if (!doctorUuid || draftKey === "") return;
|
||||
|
||||
let cancelled = false;
|
||||
const today = moment().format("YYYY-MM-DD");
|
||||
|
||||
request
|
||||
.getServiceSlots(doctorUuid, today, draftKey.split(","), clinicUuid)
|
||||
.then((res) => {
|
||||
if (cancelled) return;
|
||||
const minutes = (res?.data?.data ?? res?.data)?.total_duration_minutes;
|
||||
if (typeof minutes !== "number") {
|
||||
console.warn("[booking] total_duration_minutes missing — falling back to client sum");
|
||||
return;
|
||||
}
|
||||
setServerDuration({ key: draftKey, minutes });
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [doctorUuid, clinicUuid, draftKey]);
|
||||
|
||||
// کهنهبودن **مشتق** میشود، نه با reset: عددِ متعلق به انتخاب قبلی خودکار نادیده
|
||||
// گرفته میشود و effect لازم نیست در بدنهاش setState صدا بزند.
|
||||
const serverMinutes = serverDuration?.key === draftKey ? serverDuration.minutes : null;
|
||||
const totalMinutes = serverMinutes ?? fallbackSum(services, draft);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[520px] mx-auto">
|
||||
@@ -70,7 +116,10 @@ function ServiceSelect({ services = [], onContinue }) {
|
||||
|
||||
{draft.length > 0 && (
|
||||
<div className="mt-4 text-[13px] text-[#7A7A7A]">
|
||||
مدت کل: <b className="text-[#3B3B3B]">{totalMinutes} دقیقه</b>
|
||||
{/* تا وقتی عدد سرور نرسیده، «تقریبی» است — عددِ جمعِ کلاینت ممکن است با
|
||||
مدت واقعی نوبت یکی نباشد. */}
|
||||
{serverMinutes === null ? "مدت تقریبی" : "مدت کل"}:{" "}
|
||||
<b className="text-[#3B3B3B]">{totalMinutes} دقیقه</b>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user