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` بدون خطا؛ دستی تست کن — در داشبورد فیلد تاریخ تولد: کلیک → ابتدا گرید سالها، انتخاب سال → ماهها، انتخاب ماه → روزها، انتخاب روز → مقدار در فیلد ست شود؛ و یک فیلد تاریخِ نوبت را هم چک کن که هنوز مثل قبل (مستقیم نمای روز) کار میکند.
diff --git a/.claude/prompt/profile-birthday-send-direction-fix.md b/.claude/prompt/profile-birthday-send-direction-fix.md
new file mode 100644
index 0000000..d7b14ed
--- /dev/null
+++ b/.claude/prompt/profile-birthday-send-direction-fix.md
@@ -0,0 +1,57 @@
+# رفع جهت تبدیل تاریخ تولد هنگام ایجاد پروفایل
+
+## پروژه
+
+`nobat724_front` (سایت عمومی، داشبورد).
+
+> **Cross-repo:** وابسته به پرامپت بکاند `clinicpro/.claude/prompt/profile-birthday-jalali-persist.md` (که `birthday` را بهصورت Unix ذخیره/برمیگرداند). آن **اول** اجرا شود.
+
+## زمینه
+
+تاریخ تولد در فرم داشبورد بهصورت رشتهی جلالی از `JalaliDatePicker` میآید (`information.birthday = "jYYYY/jM/jD"`). تابع `changeDateType(data, toTimeStamp)` در `helper/index.js` بین جلالیرشته و Unix تبدیل میکند:
+- `toTimeStamp=true` → `moment(birthday, "jYYYY/jMM/jDD").unix()` (جلالی → Unix، برای **ارسال**)
+- `toTimeStamp=false` → `moment.unix(birthday).format("jYYYY/jMM/jDD")` (Unix → جلالی، برای **نمایش**)
+
+بکاند `birthday` را بهصورت **Unix** میپذیرد و برمیگرداند. مسیر بهروزرسانی (`patchData`) درست `changeDateType(usedKays, true)` میزند. اما مسیر ایجاد (`postData`) بهاشتباه `changeDateType(information, false)` میزند — یعنی هنگام **ارسال**، جهت تبدیل برعکس است و `moment.unix(جلالی-رشته)` یک مقدار بیمعنا تولید میکند. در نتیجه هنگام ایجاد پروفایل، تاریخ تولد خراب ذخیره میشود.
+
+## مشکل / هدف
+
+در `postData` جهت `changeDateType` باید `true` باشد (مثل `patchData`)، تا `birthday` جلالی به Unix تبدیل و سپس ارسال شود.
+
+## فایلهای مرتبط
+
+| فایل | نقش |
+|------|-----|
+| `components/dashboard/userAccount/detailUser/information/ButtonSendData.js` | `postData` (ایجاد) — جهت اشتباه |
+| `helper/index.js` | `changeDateType` (مرجع — تغییر نمیکند) |
+
+## وضعیت فعلی (کد واقعی)
+
+```js
+const postData = () => {
+ request
+ .postUserProfile(changeDateType(information, false)) // ❌ باید true باشد (مثل patchData)
+ // ...
+};
+
+const patchData = () => {
+ const usedKays = removeAdditionalKeysDashboard(information);
+ request
+ .patchUserProfile(changeDateType(usedKays, true), information?.uuid, ...) // ✅ درست
+ // ...
+};
+```
+
+## وظایف
+
+### ۱. اصلاح جهت در `postData`
+```js
+request.postUserProfile(changeDateType(information, true))
+```
+- فقط `false` → `true`. بقیهی منطق `postData` بدون تغییر.
+
+## نکات مهم
+
+- `changeDateType` و `patchData` تغییر نمیکنند؛ فقط یک آرگومان در `postData` اصلاح میشود.
+- نمایش (load) از قبل با `changeDateType(changedData, false)` در `information/index.js` درست است و `birthday` که حالا بکاند بهصورت Unix میدهد را به جلالی تبدیل میکند — تغییری لازم ندارد.
+- بعد از تغییر: `npm run build` بدون خطا؛ تست دستی — ایجاد پروفایل جدید با تاریخ تولد → ذخیره و سپس نمایش درستِ همان تاریخ؛ و بهروزرسانی هم همچنان درست کار کند.
diff --git a/components/common/JalaliDatePicker.js b/components/common/JalaliDatePicker.js
index 17faced..a5ef2dd 100644
--- a/components/common/JalaliDatePicker.js
+++ b/components/common/JalaliDatePicker.js
@@ -24,6 +24,20 @@ const MONTHS = [
"اسفند",
];
+const MIN_BIRTH_YEAR = 1332;
+
+// Parse an incoming value that may be a Jalali string ("1370/5/15"), a Date,
+// or a moment. Returns a valid moment, falling back to now() when invalid.
+const parseValue = (value) => {
+ if (!value) return moment();
+ if (typeof value === "string") {
+ const m = moment(value, "jYYYY/jM/jD");
+ return m.isValid() ? m : moment();
+ }
+ const m = moment(value);
+ return m.isValid() ? m : moment();
+};
+
function JalaliDatePicker({
value,
onChange,
@@ -39,20 +53,20 @@ function JalaliDatePicker({
prevIcon = null,
dayLabelRender = null,
isFirst = false,
+ stepwise = false,
}) {
const [anchorEl, setAnchorEl] = useState(null);
- const [selectedDate, setSelectedDate] = useState(
- value ? moment(value) : moment()
- );
- const [displayDate, setDisplayDate] = useState(
- value ? moment(value) : moment()
+ const [selectedDate, setSelectedDate] = useState(() =>
+ value ? parseValue(value) : null
);
+ const [displayDate, setDisplayDate] = useState(() => parseValue(value));
+ const [viewMode, setViewMode] = useState(stepwise ? "year" : "day");
const nextIconRef = useRef(null);
const prevIconRef = useRef(null);
useEffect(() => {
if (value) {
- const newDate = moment(value);
+ const newDate = parseValue(value);
setSelectedDate(newDate);
setDisplayDate(newDate);
}
@@ -61,11 +75,15 @@ function JalaliDatePicker({
const handleClick = (event) => {
if (!disabled) {
setAnchorEl(event.currentTarget);
+ if (stepwise) {
+ setViewMode("year");
+ }
}
};
const handleClose = () => {
setAnchorEl(null);
+ if (stepwise) setViewMode("year");
};
const open = Boolean(anchorEl);
@@ -87,6 +105,16 @@ function JalaliDatePicker({
handleClose();
};
+ const handleYearSelect = (year) => {
+ setDisplayDate(moment(displayDate).jYear(year));
+ setViewMode("month");
+ };
+
+ const handleMonthSelect = (month) => {
+ setDisplayDate(moment(displayDate).jMonth(month));
+ setViewMode("day");
+ };
+
const handlePrevMonth = () => {
setDisplayDate(moment(displayDate).subtract(1, "jMonth"));
};
@@ -120,7 +148,211 @@ function JalaliDatePicker({
return selectedDate.format("jYYYY/jMM/jDD");
};
- const days = getDaysInMonth();
+ const renderYearView = () => {
+ const currentYear = moment().jYear();
+ // newest first, down to the minimum birth year
+ const years = Array.from(
+ { length: currentYear - MIN_BIRTH_YEAR + 1 },
+ (_, i) => currentYear - i
+ );
+ return (
+ <>
+
+ انتخاب سال
+
+
+ {years.map((year) => {
+ const isSelected = selectedDate && selectedDate.jYear() === year;
+ return (
+ handleYearSelect(year)}
+ sx={{
+ textAlign: "center",
+ py: 1.5,
+ cursor: "pointer",
+ borderRadius: "4px",
+ fontSize: "14px",
+ bgcolor: isSelected ? "primary.main" : "transparent",
+ color: isSelected ? "primary.contrastText" : "text.primary",
+ "&:hover": { bgcolor: isSelected ? "primary.dark" : "action.hover" },
+ }}
+ >
+ {year}
+
+ );
+ })}
+
+ >
+ );
+ };
+
+ const renderMonthView = () => (
+ <>
+
+ setViewMode("year")}
+ sx={{ fontWeight: 600, fontSize: "14px", cursor: "pointer" }}
+ >
+ {displayDate.jYear()}
+
+
+
+ {MONTHS.map((monthName, idx) => {
+ const isSelected =
+ selectedDate &&
+ selectedDate.jYear() === displayDate.jYear() &&
+ selectedDate.jMonth() === idx;
+ return (
+ handleMonthSelect(idx)}
+ sx={{
+ textAlign: "center",
+ py: 1.5,
+ cursor: "pointer",
+ borderRadius: "4px",
+ fontSize: "14px",
+ bgcolor: isSelected ? "primary.main" : "transparent",
+ color: isSelected ? "primary.contrastText" : "text.primary",
+ "&:hover": { bgcolor: isSelected ? "primary.dark" : "action.hover" },
+ }}
+ >
+ {monthName}
+
+ );
+ })}
+
+ >
+ );
+
+ const renderDayView = () => {
+ const days = getDaysInMonth();
+ return (
+ <>
+ {/* Header */}
+
+ {nextIcon ? (
+
+ {nextIcon}
+
+ ) : (
+
+
+
+ )}
+
+ setViewMode("month") : undefined}
+ sx={{ fontWeight: 600, fontSize: "14px", cursor: stepwise ? "pointer" : "default" }}
+ >
+ {MONTHS[displayDate.jMonth()]} {displayDate.jYear()}
+
+
+ {prevIcon ? (
+
+ {prevIcon}
+
+ ) : (
+
+
+
+ )}
+
+
+ {/* Weekday labels */}
+
+ {dayLabelRender ? (
+ dayLabelRender()
+ ) : (
+ <>
+ {WEEKDAYS.map((day, index) => (
+
+ {day}
+
+ ))}
+ >
+ )}
+
+
+ {/* Days grid */}
+
+ {days.map((day, index) => {
+ if (!day) {
+ return ;
+ }
+
+ const dayDate = moment(displayDate)
+ .jDate(day)
+ .jMonth(displayDate.jMonth())
+ .jYear(displayDate.jYear());
+
+ const isSelected =
+ selectedDate &&
+ dayDate.jYear() === selectedDate.jYear() &&
+ dayDate.jMonth() === selectedDate.jMonth() &&
+ dayDate.jDate() === selectedDate.jDate();
+
+ const isToday =
+ dayDate.jYear() === moment().jYear() &&
+ dayDate.jMonth() === moment().jMonth() &&
+ dayDate.jDate() === moment().jDate();
+
+ const isDisabled = disabledDates && disabledDates(dayDate);
+
+ return (
+ !isDisabled && handleDateClick(day)}
+ sx={{
+ textAlign: "center",
+ py: 1,
+ cursor: isDisabled ? "not-allowed" : "pointer",
+ borderRadius: "4px",
+ fontSize: "14px",
+ bgcolor: isSelected ? "primary.main" : "transparent",
+ color: isSelected
+ ? "primary.contrastText"
+ : isDisabled
+ ? "text.disabled"
+ : "text.primary",
+ border: isToday && !isSelected ? "1px solid" : "none",
+ borderColor: "primary.main",
+ opacity: isDisabled ? 0.3 : 1,
+ "&:hover": {
+ bgcolor: isDisabled
+ ? "transparent"
+ : isSelected
+ ? "primary.dark"
+ : "action.hover",
+ },
+ }}
+ >
+ {day}
+
+ );
+ })}
+
+ >
+ );
+ };
return (
@@ -158,150 +390,12 @@ function JalaliDatePicker({
},
}}
>
-
- {/* Header */}
-
- {nextIcon ? (
-
- {nextIcon}
-
- ) : (
-
-
-
- )}
-
-
- {MONTHS[displayDate.jMonth()]} {displayDate.jYear()}
-
-
- {prevIcon ? (
-
- {prevIcon}
-
- ) : (
-
-
-
- )}
-
-
- {/* Weekday labels */}
-
- {dayLabelRender ? (
- dayLabelRender()
- ) : (
- <>
- {WEEKDAYS.map((day, index) => (
-
- {day}
-
- ))}
- >
- )}
-
-
- {/* Days grid */}
-
- {days.map((day, index) => {
- if (!day) {
- return ;
- }
-
- const dayDate = moment(displayDate)
- .jDate(day)
- .jMonth(displayDate.jMonth())
- .jYear(displayDate.jYear());
-
- const isSelected =
- selectedDate &&
- dayDate.jYear() === selectedDate.jYear() &&
- dayDate.jMonth() === selectedDate.jMonth() &&
- dayDate.jDate() === selectedDate.jDate();
-
- const isToday =
- dayDate.jYear() === moment().jYear() &&
- dayDate.jMonth() === moment().jMonth() &&
- dayDate.jDate() === moment().jDate();
-
- const isDisabled = disabledDates && disabledDates(dayDate);
-
- return (
- !isDisabled && handleDateClick(day)}
- sx={{
- textAlign: "center",
- py: 1,
- cursor: isDisabled ? "not-allowed" : "pointer",
- borderRadius: "4px",
- fontSize: "14px",
- bgcolor: isSelected ? "primary.main" : "transparent",
- color: isSelected
- ? "primary.contrastText"
- : isDisabled
- ? "text.disabled"
- : "text.primary",
- border: isToday && !isSelected ? "1px solid" : "none",
- borderColor: "primary.main",
- opacity: isDisabled ? 0.3 : 1,
- "&:hover": {
- bgcolor: isDisabled
- ? "transparent"
- : isSelected
- ? "primary.dark"
- : "action.hover",
- },
- }}
- >
- {day}
-
- );
- })}
-
+
+ {stepwise && viewMode === "year"
+ ? renderYearView()
+ : stepwise && viewMode === "month"
+ ? renderMonthView()
+ : renderDayView()}
diff --git a/components/dashboard/userAccount/detailUser/information/ButtonSendData.js b/components/dashboard/userAccount/detailUser/information/ButtonSendData.js
index 77f1f0d..1b8cbb9 100644
--- a/components/dashboard/userAccount/detailUser/information/ButtonSendData.js
+++ b/components/dashboard/userAccount/detailUser/information/ButtonSendData.js
@@ -27,7 +27,7 @@ function ButtonSendData({
const postData = () => {
request
- .postUserProfile(changeDateType(information, false))
+ .postUserProfile(changeDateType(information, true))
.then((response) => {
if (response.uuid) {
fetch("/api/auth/refresh", {
diff --git a/components/dashboard/userAccount/detailUser/information/form/DateBirthday.js b/components/dashboard/userAccount/detailUser/information/form/DateBirthday.js
index 53893c0..0565195 100644
--- a/components/dashboard/userAccount/detailUser/information/form/DateBirthday.js
+++ b/components/dashboard/userAccount/detailUser/information/form/DateBirthday.js
@@ -11,6 +11,7 @@ function DateBirthday({ data, changeData, error }) {
value={data}
placeholder="تاریخ تولد"
error={error}
+ stepwise
onChange={(e) => {
changeData(convertToJalali(e._d), "birthday");
}}