27 KiB
تسک ۰۰ب — سازگارسازی سایت عمومی با نوبتدهی سرویسی
پروژه
nobat724_front (سایت عمومی)
پرامپت همتا (بکاند، باید اول تمام شده باشد):
clinicpro/.claude/prompt/booking-engine-task-runner.md → تسک ۰۰
منبع کامل این تسک — چهار فایل، همه را بخوان:
clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/task.md
clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/architecture.md
clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/implementation_notes.md
clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/checklist.md ← وضعیت
و سه سند حاکم:
clinicpro/docs/new_feture/taskes/_shared/red-lines.md
clinicpro/docs/new_feture/taskes/_shared/ui-conventions.md
clinicpro/docs/new_feture/taskes/_shared/definition-of-done.md
زمینه
سایت حالت نوبتدهی سرویسی را میشناسد — components/appointment/index.js روش را
per محل تشخیص میدهد، components/appointment/service/index.js مرحلهٔ انتخاب سرویس را
نشان میدهد، و lib/appointmentSlots.js پاسخ appointment-service-slots را به قالب
اسلات تبدیل میکند. ولی سه دسته مشکل دارد: انحراف از تم، محاسبهٔ موازی مدت در فرانت، و
نبود سرویس/مدت در پنل کاربر.
تسک ۰۰ در clinicpro سه endpoint جدید ساخته (service-reschedule، convert-reserve،
پارامتر exclude_appointment_uuid) و دو ستون service_total_minutes/service_buffer_minutes
اضافه کرده. این تسک سایت را با آنها همگام میکند.
مشکل / هدف
پنج شکاف مشخص:
| # | شکاف | فایل |
|---|---|---|
| ۱ | چهار رنگ hard-code در مرحلهٔ انتخاب سرویس → دارکمود میشکند | components/appointment/service/index.js |
| ۲ | مدت با reduce در فرانت حساب میشود، موازی با total_duration_minutes بکاند |
همان فایل |
| ۳ | adaptServiceSlots همهٔ زمانها را در یک تب با برچسب ثابت «زمانهای خالی» میریزد و end_time را اشتباه میدهد |
lib/appointmentSlots.js |
| ۴ | پنل کاربر نام سرویس و مدت نوبت را نشان نمیدهد | components/dashboard/userAccount/sidebars/turns/* |
| ۵ | جابهجایی نوبت سرویسی از پنل کاربر وجود ندارد | .../turns/isTurnsDetails/ButtonData.js |
شکاف ۲ مهمترین است: وقتی تسک ۰۴ فرمول را به «زمان تنها / زمان اضافه» عوض کند، سایت عدد قدیمی نشان میدهد و بیمار مدتی میبیند که با مدت واقعی نوبتش نمیخواند.
معیار پذیرش
معیار کامل با همهٔ حالتهای مرزی در task.md همان تسک است. خلاصهٔ اجباری:
- ✅ موفق: مرحلهٔ انتخاب سرویس در دارکمود درست رندر میشود — هیچ متن تیره روی زمینهٔ تیره، هیچ کارت سفید.
- ✅ موفق: مدت نمایشدادهشده از
total_duration_minutesپاسخ بکاند میآید؛ اگر بکاند عدد متفاوتی بدهد، UI همان را نشان میدهد. - ✅ موفق: پزشکی با دو شیفت (صبح ۹-۱۳، عصر ۱۶-۲۰) در حالت سرویسی → دو تب زمانی با برچسب واقعی هر شیفت.
- ✅ موفق: کارت نوبت در پنل کاربر نام سرویسها و مدت را نشان میدهد.
- ✅ موفق: بیمار از پنل نوبت سرویسیاش را جابهجا میکند و عددی وارد نمیکند — مدت را بکاند حساب میکند.
- ✅ موفق (⛔ خط سرخ): رزرو اسلاتی سرتاسر بیتبهبیت مثل قبل کار میکند و کارت نوبت اسلاتی در پنل هیچ تغییری نمیکند.
- ❌ خطا: جابهجایی به زمان اشغالشده → پیام فارسی خودِ بکاند نمایش داده میشود
(نه «خطای نامشخص») و فهرست زمانها خودکار
refetchمیشود. - ❌ خطا: انتخاب صفر سرویس → دکمهٔ ادامه غیرفعال با راهنمای فارسی.
- ⚠️ مرزی: پنل کاربری که فقط نوبت اسلاتی دارد → بدون هیچ تغییری رندر میشود
(شرط
&&روی فیلدهای سرویسی). - ⚠️ مرزی: محل سرویسی بدون سرویس
bookable→ پیام روشن + پیشنهاد محل دیگر اگر باشد. - ⚠️ مرزی:
total_duration_minutesدر پاسخ نبود (بکاند قدیمی) →fallbackSumباconsole.warn، نه صفحهٔ خالی. - ⚠️ مرزی: نوبت سرویسیِ قدیمی بدون
service_items→ نام «—»، بدون کرش. - ⚠️ مرزی: نوبت رزرو (
is_reserve) در پنل → فقط سرویسها، بدون مدت (زمان ندارد). - ⚠️ مرزی: نوبت ۹۰ دقیقهای با شکاف ۹۰ دقیقه → یک تب، نه تبهای تکعضوی
(آستانه =
max(60, durationMin)).
فایلهای مرتبط
| فایل | نقش |
|---|---|
components/appointment/service/index.js |
مرحلهٔ انتخاب سرویس — بازنویسی با توکن تم |
components/appointment/index.js |
ارکستراتور مراحل — تشخیص booking_mode per محل |
components/appointment/date/index.js |
مصرفکنندهٔ adaptServiceSlots |
components/appointment/detail/SubmitData.js |
ارسال service_item_uuids در ثبت |
lib/appointmentSlots.js |
adaptSlots (⛔ قفل) و adaptServiceSlots (بازنویسی) |
services/response.js |
افزودن serviceReschedule و getServiceSlotsForReschedule |
components/dashboard/userAccount/sidebars/turns/Card.js |
+ نام سرویس و مدت |
.../turns/isTurnsDetails/DetailLg.js · DetailSm.js |
همان |
.../turns/isTurnsDetails/ButtonData.js |
+ دکمهٔ جابهجایی |
.../turns/isTurnsDetails/modal/index.js |
مودال موجود — بازاستفاده |
mui/index.js · tailwind.config.js · app/globals.css |
منبع تم و توکن |
CLAUDE.md |
+ بخش «حالتهای نوبتدهی» |
وضعیت فعلی
۱. رنگهای hard-code — components/appointment/service/index.js
<h2 className="text-[16px] font-bold text-[#3B3B3B] mb-4">۱. انتخاب سرویس</h2>
{services.length === 0 ? (
<p className="text-[14px] text-[#7A7A7A]">
در حال حاضر سرویسی برای نوبتدهی آنلاین تعریف نشده است.
</p>
) : (
…
className={`flex items-center justify-between gap-3 p-3 rounded-xl border text-right transition-colors ${
active
? "border-[#5559CE] bg-[#5559CE]/5"
: "border-gray-200 bg-white hover:border-[#5559CE]"
}`}
چهار رنگ ثابت. سایت darkMode: "class" دارد و صفحات عمومی با data-theme تم عوض
میکنند (app/Providers.js با next-themes) — این کامپوننت در دارکمود میشکند، در حالی
که بقیهٔ مراحل رزرو نمیشکنند.
۲. محاسبهٔ موازی مدت — همان فایل
const totalMinutes = services
.filter((s) => draft.includes(s.uuid))
.reduce((sum, s) => sum + (Number(s.duration_minutes) || 0), 0);
بکاند همان عدد را در total_duration_minutes پاسخ appointment-service-slots میدهد.
دو منبع حقیقت.
۳. adaptServiceSlots — lib/appointmentSlots.js
// حالت سرویسی: پاسخ appointment-service-slots فقط start_times دارد (همه کافی).
// آنها را در یک session قالببندی میکنیم تا مثل حالت اسلاتی رندر شوند.
export function adaptServiceSlots(slotsResponse) {
const starts =
slotsResponse?.data?.start_times ?? slotsResponse?.start_times ?? [];
if (!starts.length) return [];
return [
{
start_time: starts[0].start_time,
end_time: starts[starts.length - 1].start_time,
label: "زمانهای خالی",
slots: starts.map((s) => ({ ...s, is_available: true })),
},
];
}
سه مشکل: همه در یک تب · برچسب ثابت · end_time برابر شروعِ آخرین اسلات، نه پایان نوبت.
⛔ تابع adaptSlots() بالای همین فایل (حالت اسلاتی) قفل است و یک خط هم عوض نمیشود:
export function adaptSlots(slotsResponse) {
const sessions = slotsResponse?.data?.sessions ?? slotsResponse?.sessions ?? [];
return sessions
.filter((session) => Array.isArray(session.slots) && session.slots.length > 0)
.map((session) => ({ … }));
}
۴ و ۵. پنل کاربر
components/dashboard/userAccount/sidebars/turns/Card.js و
isTurnsDetails/DetailLg.js/DetailSm.js هیچ ارجاعی به service یا مدت ندارند.
ButtonData.js هیچ مسیر جابهجایی ندارد.
تشخیص حالت — components/appointment/index.js:125 (درست، دستنخورده بماند)
// روش نوبتدهی و سرویسها per-location هستند: یک پزشک میتواند در مطب شخصی
// اسلاتی و در کلینیک سرویسی باشد.
const bookingMode = selectedLocation?.booking_mode === "service" ? "service" : "slot";
const bookingServices = selectedLocation?.services ?? [];
و changeLocation() که انتخابهای وابسته را باطل میکند — این رفتار درست است و حفظ میشود.
وظایف
todo را از ردیفهای checklist.md همان تسک بساز (بخشهای ۰ تا ۸)، نه از این هشت وظیفه.
هر ردیف چکلیست را همان لحظه ⏳ → 🔄 → ✅ کن.
۱. پیشبررسی قرارداد API — پیش از هر خط کد
سه چیز را تأیید کن. اگر فیلدی نیست، کد نزن؛ به تسک ۰۰ برگردان.
TOKEN=<access_token یک کاربر تست>
# ۱. appointments/user چه فیلدهایی دارد؟
curl -s -H "Authorization: Bearer $TOKEN" \
https://clinic-pro.ddev.site/api/v1/appointments/user | jq '.data[0] | keys'
# باید service_items و service_total_minutes داشته باشد
# ۲. هر start_times[i] فیلد end_time دارد؟
curl -s "https://clinic-pro.ddev.site/api/v1/appointment-service-slots?doctor_uuid=…&date=…&service_item_uuids[]=…" \
| jq '.data.start_times[0], .data.total_duration_minutes, .data.buffer_minutes'
# ۳. exclude_appointment_uuid کار میکند؟
curl -s "…&exclude_appointment_uuid=<uuid نوبت موجود>" | jq '.data.start_times | length'
نحوه تست: خروجی واقعی هر سه دستور را در گزارش بگذار. ردیفهای ۱.۱ تا ۱.۵ چکلیست با همینها ✅ میشوند.
۲. بازنویسی service/index.js با توکن تم
اول رنگهای همسایه را ببین — هدف این است که این کامپوننت از بقیهٔ مراحل رزرو قابل تشخیص نباشد:
grep -n "className" components/appointment/location/index.js | head -30
grep -n "className" components/appointment/date/index.js | head -30
بعد همان الگو را اعمال کن. ساختار DOM و رفتار toggle عوض نمیشود — فقط منبع رنگ.
// نمونه؛ نام دقیق کلاس را از tailwind.config.js و mui/index.js همین پروژه بردار
<h2 className="text-base font-bold text-foreground mb-4">۱. انتخاب سرویس</h2>
className={active
? "border-primary bg-primary/5"
: "border-border bg-surface hover:border-primary"}
اگر پروژه توکن معادل ندارد، توکن جدید نساز — همان کاری را بکن که location/index.js
میکند. اگر دارکمود در آن هم شکسته است، این یک مسئلهٔ جداست: ردیف ⚠️ در چکلیست ثبت کن
و دامنه را گسترش نده. صفحهٔ رزرو در این تسک بازطراحی نمیشود.
نحوه تست: سناریوهای ۶.۱ و ۶.۲ چکلیست — رزرو سرویسی کامل، یک بار در لایت و یک بار
در دارک (data-theme). اسکرینشات هر دو حالت در گزارش.
۳. حذف محاسبهٔ موازی مدت
مدت از total_duration_minutes میآید. مسئلهٔ ترتیبی: مرحلهٔ انتخاب سرویس پیش از
انتخاب روز است و آن endpoint تاریخ میخواهد. راهحل انتخابشده (دلیل در
architecture.md بخش ۲): فراخوانی با تاریخ امروز فقط برای گرفتن مدت — پاسخ ممکن است
start_times خالی داشته باشد ولی total_duration_minutes میآید.
const { data } = useServiceDuration(doctorUuid, clinicUuid, draft); // hook جدید
const minutes = data?.total_duration_minutes ?? (() => {
console.warn('[booking] total_duration_minutes missing — falling back to client sum');
return fallbackSum(draft, services);
})();
// پیش از انتخاب روز
<span>مدت تقریبی: {minutes} دقیقه</span>
// پس از انتخاب روز — از همان پاسخ appointment-service-slots
<span>مدت نوبت: {minutes} دقیقه</span>
fallbackSum موقت است و فقط برای بکاند قدیمی. حذفش را در چکلیست بهعنوان ردیف ⏳ با
دلیل و تسک مقصد ثبت کن.
نحوه تست: دو سرویس انتخاب کن و عدد UI را با
curl … | jq '.data.total_duration_minutes' مقایسه کن — باید یکی باشند. بعد یک بار
total_duration_minutes را از پاسخ حذف کن (mock) و ببین console.warn میزند و صفحه
خالی نمیشود.
۴. adaptServiceSlots شیفتآگاه
export function adaptServiceSlots(slotsResponse) {
const payload = slotsResponse?.data ?? slotsResponse ?? {};
const starts = payload.start_times ?? [];
if (!starts.length) return [];
const durationMin = Number(payload.total_duration_minutes) || 0;
// آستانه هرگز کمتر از مدت نوبت — وگرنه نوبت بلند به تبهای تکعضوی میشکند
const threshold = Math.max(60, durationMin);
const groups = [];
let current = null;
for (const s of starts) {
const gapMin = current
? (s.start - current.slots[current.slots.length - 1].start) / 60
: Infinity;
if (!current || gapMin > threshold) {
current = { slots: [] };
groups.push(current);
}
current.slots.push({ ...s, is_available: true });
}
return groups.map((g) => {
const first = g.slots[0];
const last = g.slots[g.slots.length - 1];
const endTime = last.end_time ?? addMinutes(last.start_time, durationMin);
return {
start_time: first.start_time,
end_time: endTime,
label: `${first.start_time} - ${endTime}`, // همان قالب حالت اسلاتی
slots: g.slots,
};
});
}
کامنت بنویس که گروهبندی هیوریستیک است و راه دقیقش endpoint تسک ۰۶ است — تصمیم و
دلیل رد گزینهٔ «گروهبندی از بکاند» در architecture.md بخش ۳ ثبت شده (تغییر قرارداد
endpoint که سه کلاینت مصرفش میکنند).
⛔ adaptSlots() را لمس نکن.
نحوه تست: adaptServiceSlots تابع خالص است — بهترین کاندید تست واحد. پنج حالت:
یک شیفت → یک گروه
دو شیفت با شکاف ۳ ساعت → دو گروه با برچسب درست
نوبت ۹۰ دقیقهای با شکاف ۹۰ دقیقه → یک گروه
start_times خالی → []
end_time از پاسخ میآید، نه محاسبه
و سناریوی دستی ۶.۴: پزشک دو-شیفته در حالت سرویسی → دو تب در UI.
۵. سرویس و مدت در پنل کاربر
// Card.js — شرط && اجباری است
{turn.service_items?.length > 0 && (
<span className="…">{turn.service_items.map((s) => s.name).join("، ")}</span>
)}
{turn.service_total_minutes && !turn.is_reserve && (
<span className="…">{turn.service_total_minutes} دقیقه</span>
)}
بدون &&، کارت همهٔ نوبتهای اسلاتی کرش میکند — یعنی کل پنل کاربر میشکند، نه یک
خط. نوبت رزرو مدت نمیگیرد چون زمان ندارد. نوبت سرویسیِ قدیمی بدون service_items →
نام «—».
همین را در DetailLg.js و DetailSm.js هم اعمال کن.
نحوه تست: سناریوهای ۶.۵ تا ۶.۷ — پنل کاربری که فقط نوبت اسلاتی دارد باید بدون
تغییر رندر شود؛ پنل با نوبت سرویسی سرویس و مدت را نشان دهد؛ هر دو در دارکمود پنل
(class، نه data-theme — دو مکانیزم متفاوتاند).
۶. جابهجایی سرویسآگاه از پنل
// services/response.js
serviceReschedule: (uuid, body) =>
request.post(`api/v1/appointment/${uuid}/service-reschedule`, body, { requireAuth: true }),
getServiceSlotsForReschedule: (doctor_uuid, date, service_uuids, exclude_uuid, clinic_uuid) =>
request.get(
`api/v1/appointment-service-slots?doctor_uuid=${doctor_uuid}&date=${date}` +
service_uuids.map((u) => `&service_item_uuids[]=${encodeURIComponent(u)}`).join("") +
`&exclude_appointment_uuid=${exclude_uuid}` + clinicQuery(clinic_uuid),
{ requireAuth: true }
),
ButtonData.js دکمهٔ «جابهجایی» میگیرد که مودال موجود
(isTurnsDetails/modal/index.js) را با components/appointment/date/ باز میکند —
انتخابگر زمان جدید نساز؛ آن کامپوننت هر دو حالت را از قبل میشناسد.
بیمار مدت وارد نمیکند: service-reschedule فقط start میگیرد.
خطا را با پیام خودِ بکاند نشان بده:
catch (err) {
const msg = err?.response?.data?.errors?.[0]?.message ?? 'خطایی رخ داد';
toast.error(msg);
refetchSlots(); // بعد از خطای تداخل اجباری
}
ERR_SLOT_TAKEN پیام فارسی دقیق دارد؛ «خطای نامشخص» یعنی بیمار همان دکمه را ده بار میزند.
نحوه تست: سناریوهای ۶.۸ و ۶.۹ — جابهجایی موفق (مدت حفظ میشود، بیمار عددی وارد
نکرده) و جابهجایی به زمانی که همزمان توسط شخص دیگری گرفته شده (پیام فارسی + refetch).
برای دومی، یک نوبت از پنل ادمین روی همان زمان بساز و بعد دکمه را بزن.
۷. بهروزرسانی checklist.md — دو مخزن جدا
⚠️ این تسک دو مخزن git را لمس میکند:
| مخزن | چه چیزی |
|---|---|
nobat724_front |
همهٔ تغییرات کد |
clinicpro |
فقط docs/new_feture/taskes/task-00b-nobat724-service-mode/checklist.md |
پس دو commit لازم است. فراموش کردن دومی یعنی راهبر تسک بعدی فکر میکند ۰۰ب تمام نشده و دوباره تحویلش میدهد.
# ۱. کد سایت
cd nobat724_front
git checkout -b feat/booking-service-mode-frontend # اگر روی main هستی
git add -A
git commit -m "$(cat <<'EOF'
feat(booking): align public site with service booking mode
Theme tokens in service picker, backend-driven duration, shift-aware
slot grouping, service/duration in user panel, service-aware reschedule.
Slot-mode path untouched.
Task: clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EOF
)"
# ۲. چکلیست در مخزن clinicpro
cd ../clinicpro
git add docs/new_feture/taskes/task-00b-nobat724-service-mode/checklist.md
git commit -m "$(cat <<'EOF'
docs(booking): mark task 00b checklist complete
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EOF
)"
نحوه تست: git log --oneline -1 در هر دو مخزن، و
grep -c '| ⏳ |' clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/checklist.md
که باید ۰ بدهد (یا فقط ردیفهای ⏳ با یادداشت دلیل).
۸. مستندات و گزارش پایانی
nobat724_front/CLAUDE.md ← بخش «حالتهای نوبتدهی»: slot و service،
per محل تعیین میشوند، adaptSlots/adaptServiceSlots
نقطهٔ تفکیکاند
clinicpro/docs/api/appointment.md ← یادداشت: گروهبندی شیفت در حالت سرویسی
هیوریستیک سمت فرانت است؛ راه دقیقش endpoint تسک ۰۶
گزارش پایانی:
✅ تسک ۰۰ب تمام شد
پیادهسازی
• <فایلها با مسیر>
تست
• npm run build : بدون خطا
• npm run lint : بدون خطای جدید
• تست واحد adaptServiceSlots: <N> حالت، سبز
• ۱۱ سناریوی دستی : <فهرست با نتیجه>
خط سرخ
• مسیر اسلاتی دستنخورده — adaptSlots عوض نشد؛ سناریوی ۶.۳ سرتاسر تست شد
کلاینت دیگر
• clinic-pro-tauri: <چه بررسی شد — قرارداد service_item تکی>
به تعویق افتاد
• fallbackSum — دلیل: تا deploy تسک ۰۰ · تسک مقصد: <XX>
commit سایت: <hash> · commit چکلیست: <hash>
────────────────────────────────
🎯 بعدی: تسک ۰۱ — شعبه و اتاق
اجرا کن: /run-prompt clinicpro/.claude/prompt/booking-engine-task-runner.md
نکات مهم
⛔ خط سرخ — مسیر اسلاتی سایت
_shared/red-lines.md حاکم است. در این پروژه یعنی:
adaptSlots()یک خط هم عوض نمیشود- رندر تبهای شیفت در حالت اسلاتی دستنخورده
- کارت نوبت اسلاتی در پنل بدون تغییر (شرط
&&روی فیلدهای سرویسی) - سناریوی ۶.۳ (رزرو اسلاتی سرتاسر) اجباری است، نه اختیاری
اگر بعد از تغییرات، رزرو اسلاتی یک پیکسل هم فرق کرد، تغییر برمیگردد.
دامنه را گسترش نده
فقط service/index.js که خودمان اضافه کردیم و از بقیهٔ مراحل منحرف است بازنویسی میشود.
اگر location/index.js یا date/index.js هم رنگ hard-code دارند و دارکمودشان شکسته
است، این یک مسئلهٔ جداست: ردیف ⚠️ در چکلیست ثبت کن و بگذار. بازطراحی کل صفحهٔ رزرو
در این تسک نیست.
قواعد سایت که رعایت میشوند
از _shared/ui-conventions.md بخش nobat724_front و CLAUDE.md پروژه:
- تم MUI از
mui/index.jsباdirection: rtl— تم جدید نساز - Tailwind با
darkMode: "class"؛ صفحات عمومیdata-theme، پنلclass— هر دو تست شوند - فونت فقط Vazir از
app/globals.cssبا@font-face— فونت دیگر اضافه نکن - فراخوانی API از
services/response.js→request.*؛ برای auth{ requireAuth: true }تا کوکیaccess_tokenبهصورت Bearer ضمیمه شود - داده server-side با
lib/req.js→fetchReq(url) - کامپوننتهای موجود
components/appointment/*توسعه داده میشوند، مسیر موازی نه - تاریخ شمسی با
jalali-moment - RTL —
ms-*/me-*نهml-*/mr-* - هر صفحهای که دست خورد،
generateMetadataوawait paramsسالم بماند - slug پزشک/کلینیک =
uuid - شهر از subdomain: server-side
lib/getStateInfo.js· client-sideuseProvince()
وابستگی سخت به تسک ۰۰
سه endpoint و دو ستون این تسک را تسک ۰۰ میسازد. اگر
clinicpro/docs/new_feture/taskes/task-00-service-mode-completion/checklist.md کامل ✅
نیست، این تسک شروع نمیشود:
grep -c '| ⏳ |\|| 🔄 |' clinicpro/docs/new_feture/taskes/task-00-service-mode-completion/checklist.md
# باید ۰ بدهد
اگر نداد، پیام بده و بایست:
/run-prompt clinicpro/.claude/prompt/booking-engine-task-runner.md
cross-repo — بررسی دستی اجباری
clinic-pro-tauri/src/service/response.js هم مصرفکنندهٔ همان /api/v1/... است. این
تسک قرارداد بکاند را عوض نمیکند (فقط مصرف میکند)، ولی اگر تسک ۰۰ فیلدی را جابهجا
کرده باشد، اپ دسکتاپ هم متأثر است. بررسی کن که service_item تکی هنوز در پاسخ هست و
گزارش بده — «بررسی شد» بیارزش است، نام فایل و فیلد را بنویس (guidelines §۳).
تست خودکار محدود است
پروژه تست خودکار کمی دارد، پس یازده سناریوی دستی بخش ۶ چکلیست اجباریاند، نه توصیه. هر ده مورد اول روی موبایل هم تکرار میشوند (سناریو ۶.۱۰) — بدون اسکرول افقی.
adaptServiceSlots تابع خالص است و بهترین کاندید تست واحد؛ اگر پروژه runner تست ندارد،
یک فایل ساده در tests/appointmentSlots.test.js بساز و در چکلیست ثبت کن.