diff --git a/.claude/prompt/appointment-required-account-fields.md b/.claude/prompt/appointment-required-account-fields.md new file mode 100644 index 0000000..0cc5378 --- /dev/null +++ b/.claude/prompt/appointment-required-account-fields.md @@ -0,0 +1,116 @@ +# اجباری‌کردن فیلدهای اطلاعات حساب کاربری در صفحه نوبت‌گیری + +## پروژه + +`nobat724_front` (سایت عمومی). + +> کاملاً frontend است. هیچ تغییری در بک‌اند لازم نیست — فقط اعتبارسنجی سمت کلاینت قبل از ادامه به مرحله‌ی پرداخت. + +## زمینه + +در صفحه‌ی نوبت‌گیری (`/appointment/[doctorId]`)، بخش «اطلاعات حساب کاربری» (و در حالت «شخص دیگر»، «اطلاعات کاربر جدید») قبل از رفتن به مرحله‌ی پرداخت هیچ اعتبارسنجی اجباری ندارد؛ `SubmitData.handleSubmit` فقط `setErrors({})` می‌کند و مستقیم به API می‌فرستد، و خطاها فقط از پاسخ سرور برمی‌گردند. در نتیجه کاربر می‌تواند بدون نام/کد ملی/جنسیت/بیمه نوبت بگیرد و رکورد ناقص ثبت شود (همان مشکلی که باعث شد نام کاربر خالی بماند). + +## مشکل / هدف + +این پنج فیلد در **هر دو حالت** (نوبت برای خود کاربر و برای شخص دیگر) **اجباری** شوند و تا تکمیل نشدن، فرم به مرحله‌ی بعد نرود و خطای فیلدی نمایش دهد: +- کد ملی (`national_code`) — ۱۰ رقم +- نام (`name`) +- نام خانوادگی (`family`) +- جنسیت (`gender`) +- نوع بیمه (`basic_insurance`) + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `components/appointment/detail/SubmitData.js` | `handleSubmit` — جای افزودن اعتبارسنجی اجباری قبل از API | +| `components/appointment/detail/Form.js` | فیلدها از قبل `error={errors?.}` می‌گیرند (نیازی به تغییر ساختار نیست) | +| `components/appointment/detail/index.js` | `errors` state و `emptyPatientData()` (شکل فیلدها) | + +## وضعیت فعلی (کد واقعی) + +`SubmitData.handleSubmit` — بدون اعتبارسنجی اجباری: +```js +const handleSubmit = async () => { + const isChanged = JSON.stringify(prevData) !== JSON.stringify(data); + setErrors({}); + setLoading(true); + try { + if (!isForAnother && isChanged) { /* PATCH/POST profile */ } + if (doctor && selectedSlot) { /* postAppointment */ } + ... + } catch (error) { + if (error?.response?.status === 409) { setStep(0); ... } + setErrors(error.response.data); // فقط خطای سرور + } +}; +``` + +شکل داده‌ی هر فیلد (از `detail/index.js`): +```js +// data. = { value: <...>, isEdit: } +// gender.value: رشته "male"/"female" (یا "" ) +// basic_insurance.value: آبجکت { id } یا "" (در Form با findBasicInsuranceVal مپ می‌شود) +// national_code/name/family.value: رشته +``` + +`Form.js` از قبل `error` را به هر فیلد پاس می‌دهد: +```jsx + + + + + +``` +> یعنی فقط کافی است `SubmitData` یک آبجکت `errors` با کلیدهای همین فیلدها بسازد و `setErrors` کند؛ نمایش خطا خودکار کار می‌کند. + +## وظایف + +### ۱. تابع اعتبارسنجی اجباری در `SubmitData` + +یک `validate(data)` بساز که آبجکت خطا (کلید فیلد → پیام فارسی) برگرداند. در ابتدای `handleSubmit`، قبل از `setLoading(true)` و قبل از هر فراخوانی API، اجرا شود؛ اگر خطا داشت `setErrors(...)` و `return` (توقف). + +```js +const validate = () => { + const errs = {}; + const nationalCode = (data?.national_code?.value || "").trim(); + if (!data?.name?.value?.trim()) errs.name = "نام الزامی است"; + if (!data?.family?.value?.trim()) errs.family = "نام خانوادگی الزامی است"; + if (!nationalCode) errs.national_code = "کد ملی الزامی است"; + else if (!/^\d{10}$/.test(nationalCode)) errs.national_code = "کد ملی باید ۱۰ رقم باشد"; + // gender.value رشته است + if (!data?.gender?.value) errs.gender = "جنسیت الزامی است"; + // basic_insurance.value آبجکت {id} یا "" است + if (!data?.basic_insurance?.value?.id) errs.basic_insurance = "نوع بیمه الزامی است"; + return errs; +}; + +const handleSubmit = async () => { + const errs = validate(); + if (Object.keys(errs).length > 0) { + setErrors(errs); + return; + } + setErrors({}); + setLoading(true); + // ... بقیه بدون تغییر +}; +``` + +- این اعتبارسنجی در **هر دو حالت** اجرا شود (هم `isForAnother=false` هم `true`) — یعنی خارج از شرط `if (!isForAnother && isChanged)` و قبل از آن. +- `DefaultSelect` (جنسیت/بیمه) باید پیام خطا را نمایش دهد؛ تأیید کن `error` prop در `DefaultSelect` به helperText/حالت خطای MUI وصل است (اگر نیست، اضافه کن — ولی `EditField` این را دارد). + +### ۲. هماهنگی شکل داده + +- `gender.value` رشته‌ی `"male"`/`"female"` است (از `buildProfileData`/`DefaultSelect`)، پس بررسی `!data.gender.value` درست است. +- `basic_insurance.value` یا آبجکت `{ id }` است (از پروفایل) یا کل آیتم بیمه (از انتخاب کاربر در `DefaultSelect`) — هر دو `id` دارند، پس `?.value?.id` درست کار می‌کند. +- کد ملی ممکن است از پروفایلِ تأییدشده `isEdit:false` (قفل) ولی پر باشد — این حالت معتبر است (مقدار دارد، خطا نمی‌دهد). + +## نکات مهم + +- **بک‌اند تغییر نمی‌کند**؛ این صرفاً gate سمت کلاینت است. خطاهای سرور (`setErrors(error.response.data)`) در catch دست‌نخورده بماند. +- اعتبارسنجی باید **قبل از** ساخت/ارسال نوبت و پروفایل اجرا شود تا رکورد ناقص ثبت نشود. +- پیام‌ها فارسی، فیلدها همان کلیدهایی که `Form.js` انتظار دارد (`name`, `family`, `national_code`, `gender`, `basic_insurance`). +- در حالت «شخص دیگر»، فیلد `phone` هم در فرم هست؛ اگر می‌خواهی آن را هم اجباری کنی به validate اضافه کن — ولی طبق درخواست فقط این پنج فیلد لازم‌اند. +- App Router، RTL، MUI v5؛ از `EditField`/`DefaultSelect` موجود استفاده کن، کامپوننت جدید نساز. +- بعد از تغییر: `npm run build` بدون خطا؛ دستی تست کن — با فیلدهای خالی دکمه‌ی ادامه نباید جلو برود و باید زیر هر فیلد خطا نشان دهد؛ با تکمیل همه، باید به مرحله‌ی پرداخت برود. diff --git a/.claude/prompt/fix-my-appointments-status-filter.md b/.claude/prompt/fix-my-appointments-status-filter.md new file mode 100644 index 0000000..adf20b4 --- /dev/null +++ b/.claude/prompt/fix-my-appointments-status-filter.md @@ -0,0 +1,93 @@ +# رفع فیلتر وضعیت در «نوبت‌های من» (نوبت ثبت‌شده نمایش داده نمی‌شود) + +## پروژه + +`nobat724_front` (سایت عمومی، داشبورد کاربر). + +> این باگ کاملاً frontend است. بک‌اند درست کار می‌کند: `GET /api/v1/appointments/user?status=` نوبت‌های کاربر فعلی را برمی‌گرداند و فیلتر `status` را روی مقادیر واقعی نوبت اعمال می‌کند. + +## زمینه + +کاربر در nobat724 نوبت گرفته (وضعیت `confirmed`) ولی در «نوبت‌های من» (تب‌های تاریخچه نوبت) چیزی نمایش داده نمی‌شود. ریشه‌یابی نشان داد بک‌اند نوبت را درست برمی‌گرداند؛ مشکل این است که تب‌های فیلتر در frontend مقادیر `status` نادرستی به API می‌فرستند که با وضعیت‌های واقعی بک‌اند مطابقت ندارند، پس هر تب جز «همه» نتیجه‌ی خالی می‌دهد. + +تأیید با داده‌ی واقعی: +- `GET /api/v1/appointments/user?status=reserved` → **۰ نوبت** (مقدار `reserved` در بک‌اند وجود ندارد). +- `GET /api/v1/appointments/user?status=confirmed` → **۴ نوبت** (مقدار درست). + +## مشکل / هدف + +تب‌های `Head.js` این مقادیر را می‌فرستند: +`waiting_for_payment`, `reserved`, `visited`, `auto_cancel_unpaid`, `completed` + +اما وضعیت‌های واقعی نوبت در بک‌اند (همان‌هایی که `List.js` در `STATUS_LABELS` می‌شناسد و `Appointment` entity تعریف می‌کند): +`pending`, `confirmed`, `completed`, `cancelled_by_user`, `cancelled_by_doctor`, `expired`, `no_show` + +هدف: نگاشت تب‌ها به مقادیر واقعی + هماهنگ‌کردن برچسب‌ها، تا فیلتر درست کار کند. + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `components/dashboard/userAccount/sidebars/turns/Head.js` | تب‌های فیلتر — `listTab` و `statusMap` نادرست | +| `components/dashboard/userAccount/sidebars/turns/List.js` | `STATUS_LABELS` (مرجع مقادیر واقعی — درست است) | +| `components/dashboard/userAccount/sidebars/turns/index.js` | `Turns` — `status` را به `getMyAppointments` پاس می‌دهد (درست است، تغییر لازم ندارد) | + +## وضعیت فعلی (کد واقعی) + +`Head.js` — نگاشت نادرست: +```jsx +const listTab = ["همه", "در انتظار پرداخت", "رزرو شده", "ویزیت شده", "لغو خودکار", "تکمیل شده"]; + +const statusMap = { + 0: undefined, // all + 1: "waiting_for_payment", // ❌ بک‌اند: pending + 2: "reserved", // ❌ بک‌اند: confirmed + 3: "visited", // ❌ بک‌اند: completed (یا no_show) + 4: "auto_cancel_unpaid", // ❌ بک‌اند: expired + 5: "completed", // فقط این یکی تصادفاً درست است +}; +``` + +`List.js` — مقادیر واقعی (مرجع درست): +```jsx +const STATUS_LABELS = { + pending: "در انتظار پرداخت", + confirmed: "تأیید شده", + completed: "انجام شده", + cancelled_by_user: "لغو شده", + cancelled_by_doctor: "لغو توسط پزشک", + expired: "منقضی شده", + no_show: "عدم مراجعه", +}; +``` + +## وظایف + +### ۱. اصلاح نگاشت تب‌ها در `Head.js` +`listTab` و `statusMap` را با وضعیت‌های واقعی بک‌اند هماهنگ کن. تب‌های پیشنهادی (برچسب فارسی ← مقدار واقعی API): +```jsx +const listTab = ["همه", "در انتظار پرداخت", "تأیید شده", "انجام شده", "لغو شده", "منقضی شده"]; + +const statusMap = { + 0: undefined, // همه + 1: "pending", + 2: "confirmed", + 3: "completed", + 4: "cancelled_by_user", + 5: "expired", +}; +``` +- برچسب‌ها باید با `STATUS_LABELS` در `List.js` یک‌دست باشند (مثلاً `confirmed` → «تأیید شده»). +- اگر می‌خواهی «لغو توسط پزشک» و «عدم مراجعه» هم فیلتر شوند، می‌توانی تب اضافه کنی؛ ولی چون فیلتر بک‌اند فقط یک مقدار `status` می‌گیرد، هر تب باید دقیقاً یک مقدار معتبر بفرستد. + +### ۲. تأیید جریان +- `index.js`: `if (status) params.status = status;` — وقتی `undefined` باشد (تب «همه») پارامتر ارسال نمی‌شود و همه‌ی نوبت‌ها می‌آیند. این درست است؛ تغییر نده. +- مطمئن شو تب پیش‌فرض «همه» (index 0 → `undefined`) است تا در اولین بارگذاری همه‌ی نوبت‌ها (از جمله `confirmed`) نمایش داده شوند. + +## نکات مهم + +- **بک‌اند تغییر نمی‌کند.** مقادیر معتبر `status` دقیقاً ثابت‌های `Appointment` هستند: `pending`, `confirmed`, `completed`, `cancelled_by_user`, `cancelled_by_doctor`, `expired`, `no_show`. هر مقدار دیگری → لیست خالی. +- پاسخ `appointments/user` دابل‌نِست است (`success(['data'=>...])`)؛ `index.js` درست با `response.data.data` می‌خواند — دست نزن. +- تاریخ/ساعت در `List.js` با `convertTimestampToJalali`/`convertTimestampToTime` از `slot_start` (Unix ثانیه) ساخته می‌شود — درست است. +- App Router، RTL، فونت Vazir، MUI v5؛ از کامپوننت `Tabs` موجود استفاده کن. +- بعد از تغییر: `npm run build` بدون خطا؛ در داشبورد همه‌ی تب‌ها را تست کن (به‌ویژه «تأیید شده» که باید نوبت confirmed را نشان دهد، و «همه» که همه را نشان دهد). diff --git a/components/appointment/detail/SubmitData.js b/components/appointment/detail/SubmitData.js index 46aa655..20a1153 100644 --- a/components/appointment/detail/SubmitData.js +++ b/components/appointment/detail/SubmitData.js @@ -34,7 +34,26 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, isForAnother, } }; + const validate = () => { + const errs = {}; + const nationalCode = (data?.national_code?.value || "").trim(); + if (!data?.name?.value?.trim()) errs.name = "نام الزامی است"; + if (!data?.family?.value?.trim()) errs.family = "نام خانوادگی الزامی است"; + if (!nationalCode) errs.national_code = "کد ملی الزامی است"; + else if (!/^\d{10}$/.test(nationalCode)) + errs.national_code = "کد ملی باید ۱۰ رقم باشد"; + if (!data?.gender?.value) errs.gender = "جنسیت الزامی است"; + if (!data?.basic_insurance?.value?.id) errs.basic_insurance = "نوع بیمه الزامی است"; + return errs; + }; + const handleSubmit = async () => { + const errs = validate(); + if (Object.keys(errs).length > 0) { + setErrors(errs); + return; + } + const isChanged = JSON.stringify(prevData) !== JSON.stringify(data); setErrors({}); diff --git a/components/dashboard/userAccount/sidebars/turns/Head.js b/components/dashboard/userAccount/sidebars/turns/Head.js index e2494be..541a47b 100644 --- a/components/dashboard/userAccount/sidebars/turns/Head.js +++ b/components/dashboard/userAccount/sidebars/turns/Head.js @@ -3,20 +3,20 @@ import Tabs from "@/app/component/Tabs"; const listTab = [ "همه", "در انتظار پرداخت", - "رزرو شده", - "ویزیت شده", - "لغو خودکار", - "تکمیل شده", + "تأیید شده", + "انجام شده", + "لغو شده", + "منقضی شده", ]; function Head({ status, setStatus }) { const statusMap = { 0: undefined, // all - 1: "waiting_for_payment", - 2: "reserved", - 3: "visited", - 4: "auto_cancel_unpaid", - 5: "completed", + 1: "pending", + 2: "confirmed", + 3: "completed", + 4: "cancelled_by_user", + 5: "expired", }; const handleChange = (e, value) => {