- JalaliDatePicker gains an opt-in `stepwise` mode (year → month → day): the year step is a scrollable list from the current Jalali year down to 1332, used by the birthday field. Default-off so appointment/panel pickers keep the month-grid behavior. Parse incoming Jalali string values safely (fixes NaN keys). - Birthday create path now converts Jalali→Unix on send (changeDateType true), matching the update path and the Unix-based backend contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.5 KiB
انتخاب تاریخ تولد بهصورت مرحلهای: سال ← ماه ← روز
پروژه
nobat724_front (سایت عمومی، داشبورد کاربر).
کاملاً frontend است. هیچ تغییری در بکاند یا قرارداد API لازم نیست؛ خروجی همان رشتهی جلالی فعلی است.
زمینه
در داشبورد (/dashboard → اطلاعات کاربری)، فیلد «تاریخ تولد» از components/common/JalaliDatePicker استفاده میکند که فقط یک نمای ماهانه دارد: هدر فقط «ماه سال» را نشان میدهد و با فلشها فقط ماهبهماه جلو/عقب میرود. برای انتخاب سال تولد (مثلاً ۱۳۷۰) کاربر باید دهها بار فلش بزند — تجربهی بدی برای تاریخ تولد است.
خواسته: تقویمِ تاریخ تولد باید مرحلهای باشد — اول سال، بعد ماه، بعد روز.
مشکل / هدف
افزودن یک حالتِ انتخابِ مرحلهای (year → month → day) به JalaliDatePicker بهصورت opt-in با prop، و فعالکردن آن فقط برای فیلد تاریخ تولد. سایر استفادهها (انتخاب تاریخ نوبت و پنل) باید دستنخورده بمانند.
فایلهای مرتبط
| فایل | نقش |
|---|---|
components/common/JalaliDatePicker.js |
date picker جلالیِ مشترک — افزودن حالت مرحلهای |
components/dashboard/userAccount/detailUser/information/form/DateBirthday.js |
فیلد تاریخ تولد — فعالکردن حالت جدید با prop |
هشدار سازگاری:
JalaliDatePickerدر این فایلها هم استفاده میشود و نباید رفتارشان تغییر کند:app/component/DatePickerContent.js,app/component/date/datePicker/date/FirstDatePicker.js,.../SecondDatePicker.js,components/layoutPanel/userDetail/Date.js. پس حالت جدید باید پیشفرض خاموش باشد.
وضعیت فعلی (کد واقعی)
JalaliDatePicker.js — فقط نمای ماهانه. هدر و ناوبری:
const handlePrevMonth = () => setDisplayDate(moment(displayDate).subtract(1, "jMonth"));
const handleNextMonth = () => setDisplayDate(moment(displayDate).add(1, "jMonth"));
// ...
// هدر: فقط ماه و سال + دو فلش ماه
<Box sx={{ fontWeight: 600, fontSize: "14px" }}>
{MONTHS[displayDate.jMonth()]} {displayDate.jYear()}
</Box>
// ... سپس گرید روزها
MONTHS (۱۲ ماه جلالی) از قبل در همین فایل تعریف شده. moment = moment-jalaali؛ متدها: jYear(), jMonth(), jDate(), moment.jDaysInMonth(y, m).
DateBirthday.js:
<JalaliDatePicker
value={data}
placeholder="تاریخ تولد"
error={error}
onChange={(e) => changeData(convertToJalali(e._d), "birthday")}
textFieldProps={{ /* CalendarEdit icon */ }}
/>
وظایف
۱. افزودن حالت مرحلهای به JalaliDatePicker
- یک prop جدید بگیر:
stepwise = false(پیشفرض خاموش تا سازگاری حفظ شود). - یک state داخلی
viewModeبا مقادیر"year" | "month" | "day". وقتی Popover باstepwise=trueباز میشود،viewModeروی"year"شروع شود؛ در غیر این صورت رفتار فعلی (مستقیم نمای روز) حفظ شود. - نمای سال: یک گرید از سالها (مثلاً ۱۲ سال در هر صفحه، با دو فلش برای صفحهی قبل/بعدِ بازهی سالها). بازهی منطقی برای تولد: از
currentJalaliYear - 100تاcurrentJalaliYear. انتخاب سال →displayDateبا آن سال بهروز شود وviewMode = "month". - نمای ماه: گرید ۱۲ ماه از
MONTHS. انتخاب ماه →displayDateبا آن ماه، وviewMode = "day". - نمای روز: همان گرید روزهای فعلی (بدون تغییر). انتخاب روز →
onChangeصدا زده شود و Popover بسته شود (مثل حالا). - در هدرِ حالت مرحلهای، عنوان قابلکلیک باشد تا کاربر بتواند به مرحلهی بالاتر برگردد: در نمای ماه، کلیک روی سال → برگشت به نمای سال؛ در نمای روز، کلیک روی «ماه سال» → برگشت به نمای ماه. (ناوبری رو به عقب)
- وقتی Popover بسته شد،
viewModeبرای بار بعد دوباره"year"شود.
طرح کلی:
function JalaliDatePicker({ /* ...props */, stepwise = false }) {
const [viewMode, setViewMode] = useState(stepwise ? "year" : "day");
// باز شدن popover (stepwise): setViewMode("year")
// بسته شدن: setViewMode(stepwise ? "year" : "day")
// نمای سال
const renderYears = () => { /* گرید سالها currentYear-100..currentYear */ };
// نمای ماه
const renderMonths = () => { /* گرید MONTHS، onClick → setDisplayDate(jMonth) + setViewMode("day") */ };
// نمای روز = همان رندر فعلی
// در بدنهی Popover:
// stepwise ? (viewMode==="year" ? renderYears() : viewMode==="month" ? renderMonths() : renderDays())
// : renderDays()
}
- منطق
value/selectedDate/onChangeو خروجی (همانmomentobject کهDateBirthdayباe._dوconvertToJalaliمصرف میکند) بدون تغییر بماند.
۲. فعالکردن در DateBirthday.js
فقط prop اضافه کن:
<JalaliDatePicker
value={data}
placeholder="تاریخ تولد"
error={error}
stepwise
onChange={(e) => changeData(convertToJalali(e._d), "birthday")}
textFieldProps={{ /* بدون تغییر */ }}
/>
نکات مهم
- سازگاری عقبرو:
stepwiseپیشفرضfalse؛ هیچیک از ۴ مصرفکنندهی دیگر (تاریخ نوبت/پنل) نباید رفتارشان عوض شود. این را با grep تأیید کن و دست به آن فایلها نزن. - خروجی
onChangeدقیقاً همان شیءmomentفعلی باشد تاconvertToJalali(e._d)درDateBirthdayو مصرف بکاند (birthday/date_of_birth) نشکند. - بازهی سالها برای تولد:
[امسالِ جلالی - 100, امسالِ جلالی]، نزولی (سالهای جدیدتر بالا) یا صعودی — هرکدام UX بهتری دارد؛ پیشفرض نزولی منطقیتر است. - RTL، فونت Vazir، MUI v5 (
Box/Popover/IconButtonکه از قبل import شدهاند)؛ کتابخانهی جدید اضافه نکن. ازMONTHSوmoment-jalaaliموجود در همان فایل استفاده کن. - اعداد فارسی/لاتین: مطابق رفتار فعلی فایل (
usePersianDigits: false) نگهدار تا یکدست بماند. - بعد از تغییر:
npm run buildبدون خطا؛ دستی تست کن — در داشبورد فیلد تاریخ تولد: کلیک → ابتدا گرید سالها، انتخاب سال → ماهها، انتخاب ماه → روزها، انتخاب روز → مقدار در فیلد ست شود؛ و یک فیلد تاریخِ نوبت را هم چک کن که هنوز مثل قبل (مستقیم نمای روز) کار میکند.