From e820cc88ee3cdef586a46ffd0e4d67b175e43efd Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Mon, 15 Jun 2026 16:51:30 +0330 Subject: [PATCH] fix(appointment): client guard for slots that pass while page is open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend already returns is_available:false for past slots, but a slot can lapse after the list is loaded. Disable any slot whose start (unix seconds) is before now in the time grid, so a just-passed slot can't be clicked. The grid disabling makes a SendAppo-level guard redundant — a past slot can no longer be selected. Co-Authored-By: Claude Opus 4.8 --- .claude/prompt/verify-past-slots-disabled.md | 80 ++++++++++++++++++++ app/component/date/dateTime/hours/List.js | 6 +- 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 .claude/prompt/verify-past-slots-disabled.md diff --git a/.claude/prompt/verify-past-slots-disabled.md b/.claude/prompt/verify-past-slots-disabled.md new file mode 100644 index 0000000..854d4be --- /dev/null +++ b/.claude/prompt/verify-past-slots-disabled.md @@ -0,0 +1,80 @@ +# تأیید و سخت‌سازی غیرفعال‌بودن اسلات‌های گذشته در صفحه‌ی نوبت + +## پروژه + +`nobat724_front` — سایت عمومی. **بعد از پرامپت backend اجرا شود.** + +> **Cross-repo:** وابسته به اصلاح `is_available` در backend: +> `clinicpro/.claude/prompt/mark-past-slots-unavailable.md` +> بعد از آن، اسلات‌های گذشته‌ی روز جاری از API با `is_available: false` می‌آیند و UI خودکار آن‌ها را خاکستری می‌کند. این پرامپت آن را تأیید و یک گارد سبک سمت کلاینت اضافه می‌کند. + +## زمینه + +گرید ساعت (`List.js`) از قبل دکمه را با `disabled={!item.is_available}` غیرفعال و خاکستری می‌کند. پس وقتی backend اسلات گذشته را `is_available: false` بدهد، **بدون تغییر فرانت** خاکستری می‌شود. تنها ریسک باقی‌مانده: کاربر صفحه را ساعت ۹:۵۹ باز کرده، اسلات ۱۰:۰۰ آن لحظه `is_available: true` بوده و در state مانده؛ ساعت ۱۰:۰۱ همان آبجکت قدیمی هنوز کلیک‌پذیر است چون داده دوباره fetch نشده. backend موقع ثبت رد می‌کند (۴۲۲)، ولی بهتر است UI هم همان لحظه جلوی کلیک را بگیرد. + +## مشکل / هدف + +۱. تأیید کن گرید ساعت `is_available` را درست رعایت می‌کند (انتظار: نیازی به تغییر نیست). +۲. یک گارد سبک سمت کلاینت اضافه کن: اسلاتی که `start` (Unix ثانیه) آن از «اکنون» گذشته، حتی اگر `is_available: true` آمده باشد، در UI غیرفعال شود. + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `app/component/date/dateTime/hours/List.js` | گرید ساعت؛ `disabled={!item.is_available}` | +| `lib/appointmentSlots.js` | adapter اسلات‌ها (`sessions[]` → morning/evening) — اسلات‌ها `start`/`is_available` دارند | +| `app/component/date/dateTime/SendAppo.js` | دکمه‌ی «تایید نوبت»؛ `disabled={!hour}` | + +## وضعیت فعلی (کد واقعی) + +### `hours/List.js` + +```js + +``` + +### آبجکت اسلات (از `adaptSlots` / API) + +```js +{ start: 1781933400, end: 1781934600, start_time: "09:00", end_time: "09:20", location_id: 2581, is_available: true } +``` + +## وظایف + +### ۱. گارد زمان گذشته در گرید ساعت + +در `List.js` یک محاسبه‌ی کوچک اضافه کن که اسلات گذشته را غیرقابل‌انتخاب کند (مبنا: `item.start` بر حسب ثانیه): + +```js +const isPast = (item) => item?.start && item.start * 1000 < Date.now(); +const isSelectable = (item) => item.is_available && !isPast(item); +``` + +سپس در دکمه `disabled={!isSelectable(item)}` و در کلاس متن `isSelectable(item) ? ... : ...` را جایگزین `item.is_available` کن. + +> `start` ثانیه است؛ `Date.now()` میلی‌ثانیه — پس `start * 1000`. + +### ۲. (اختیاری) عدم انتخاب اسلات گذشته در SendAppo + +اگر منطقی بود، در `SendAppo` هم قبل از `setSelectedSlot(hour)` مطمئن شو `hour.start * 1000 >= Date.now()`؛ در غیر این صورت اجازه نده مرحله جلو برود. (اگر گارد گرید کافی است و کاربر اصلاً نمی‌تواند اسلات گذشته را انتخاب کند، این مرحله را رد کن و در گزارش ذکر کن.) + +### ۳. تأیید رفتار end-to-end + +- با `date=<امروز>` و یک ساعت که چند اسلات اول روز گذشته‌اند، تأیید کن اسلات‌های گذشته خاکستری و غیرقابل‌کلیک‌اند و اسلات‌های آینده فعال. + +## نکات مهم + +- **بدون تایمر/رندر مکرر اضافه نکن** مگر لازم باشد؛ یک محاسبه در زمان رندر کافی است (هر بار state تغییر کند یا کامپوننت رندر شود ارزیابی می‌شود). نیازی به `setInterval` برای به‌روزرسانی هر ثانیه نیست مگر کاربر بخواهد. +- **منبع اصلی همچنان backend است:** این گارد فقط لبه‌ی «اسلاتی که همین حالا گذشت» را می‌گیرد. اگر backend درست `is_available:false` بدهد، این گارد در عمل کم‌اثر ولی بی‌ضرر و درست است. +- **تایم‌زون:** `start` از API بر حسب Unix ثانیه (UTC مبنا) است؛ `Date.now()` هم Unix میلی‌ثانیه است — مقایسه درست است بدون تبدیل تایم‌زون. +- RTL/Jalali/multi-domain را خراب نکن؛ منطق `morning`/`evening` و adapter دست‌نخورده بماند. +- تست: `npm run build` و سپس بررسی دستی روی صفحه‌ی نوبت پزشک تست. سپس commit با پیام توصیفی. diff --git a/app/component/date/dateTime/hours/List.js b/app/component/date/dateTime/hours/List.js index 58e1d4e..4cfbda9 100644 --- a/app/component/date/dateTime/hours/List.js +++ b/app/component/date/dateTime/hours/List.js @@ -4,6 +4,8 @@ import React from "react"; function List({ appo, hour, setHour, value }) { const list = appo && appo[value ? "evening" : "morning"]; const checkHour = (item) => JSON.stringify(item) === JSON.stringify(hour); + const isSelectable = (item) => + item.is_available && !(item?.start && item.start * 1000 < Date.now()); if (list && list.length) { return ( @@ -12,7 +14,7 @@ function List({ appo, hour, setHour, value }) {