# همه‌ی تقویم‌های پنل ادمین باید شمسی باشند (رفع تقویم میلادی PersianDateInput) ## پروژه `clinicpro` (React 19 admin SPA داخل Symfony — `assets/admin/`) ## زمینه کل محصول فارسی/RTL و تاریخ‌ها Jalali (شمسی) است. تقویم شمسی قبلاً نوشته شده: `PersianCalendar.tsx` (پیکر ماه/سال/روز شمسی، خروجی `YYYY-MM-DD` میلادی) و روکش آن `PersianDatePicker.tsx`. اما یک کامپوننت دیگر به‌نام `PersianDateInput.tsx` هنوز از `` بومی مرورگر استفاده می‌کند که **تقویم میلادی** مرورگر را باز می‌کند. هرجای پنل که این کامپوننت استفاده شده، کاربر تقویم میلادی می‌بیند — خلاف قاعده‌ی محصول. ## مشکل / هدف **هدف:** هرجای پنل ادمین که از انتخاب تاریخ (تقویم) استفاده می‌شود، تقویم شمسی نمایش دهد. **تنها منبع میلادی در کل ادمین:** `assets/admin/components/ui/PersianDateInput.tsx` خط ۵۶ (`type="date"`). با اصلاح همین یک فایل، همه‌ی call siteهای زیر یک‌جا شمسی می‌شوند (بدون تغییر در آن‌ها، چون امضای Props ثابت می‌ماند). بررسی انجام‌شده: - `PersianCalendar` / `PersianDatePicker` قبلاً شمسی‌اند — نیازی به بازنویسی ندارند. - در کل `assets/admin` فقط **یک** `type="date"` وجود دارد (همین فایل). هیچ `datetime-local` / `month` / `week` بومی دیگری نیست. - تقویم ماهانه‌ی درون `DoctorDetailPage.tsx` و `PersianDateInput` محلیِ همان فایل از قبل با `jalaali-js` شمسی‌اند — دست نزن. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `assets/admin/components/ui/PersianDateInput.tsx` | **تنها فایلی که تغییر می‌کند** — حذف input بومی، استفاده از PersianCalendar | | `assets/admin/components/ui/PersianCalendar.tsx` | پیکر شمسی موجود (Props: `value`, `onChange`, `onClose`, `enableYearPicker`) — مرجع | | `assets/admin/components/ui/PersianDatePicker.tsx` | الگوی درستِ استفاده از PersianCalendar (کپی همین ساختار) | | `assets/admin/lib/utils.ts` → `formatDate()` | نمایش شمسیِ مقدار انتخاب‌شده (از قبل استفاده می‌شود) | ### call siteهای فعلی PersianDateInput (نباید تغییر کنند — فقط برای اطمینان از سازگاری Props) ``` components/AppointmentActions.tsx:482,613 value/onChange components/InsuranceModal.tsx:153,157 value/onChange/placeholder components/NewAppointmentDrawer.tsx:257 value/onChange components/PatientsFilterModal.tsx:84,86 value/onChange/placeholder pages/AppointmentEditPage.tsx:150 value/onChange pages/MyPaymentsPage.tsx:108,111 value/onChange/placeholder pages/AppointmentCreatePage.tsx:254 value/onChange pages/PatientDetailPage.tsx:373,510 value/onChange pages/PatientRecordFormPage.tsx:132 value/onChange ← تاریخ تولد (نیاز به enableYearPicker) pages/DoctorDetailPage.tsx:1687,1914,1918 این‌ها به PersianDateInput محلیِ همان فایل وصل‌اند، نه فایل مشترک — دست نزن ``` ## وضعیت فعلی (کد مشکل‌دار) `assets/admin/components/ui/PersianDateInput.tsx` — لایه‌ی متنی شمسی است ولی پیکر بومی میلادی: ```tsx {/* hidden native input — opens picker on click */} onChange(e.target.value)} style={{ position: 'absolute', opacity: 0, pointerEvents: 'none', width: 1, height: 1, top: 0, left: 0 }} tabIndex={-1} /> ``` `Props` فعلی: `value, onChange, placeholder?, min?, max?, style?, className?`. نکته: `min`/`max` در هیچ call siteی پاس داده نمی‌شوند (Prop مرده‌اند). ## وظایف ### ۱. بازنویسی `PersianDateInput.tsx` روی پیکر شمسی `` را حذف کن و همان الگوی `PersianDatePicker.tsx` را به کار ببر: state باز/بسته + رندر شرطی ``. لایه‌ی متنی visible و `formatDate(value)` و دکمه‌ی پاک‌کردن (X) و امضای Props فعلی را **حفظ کن** تا هیچ call siteی نشکند. ```tsx import { useState } from 'react'; import { CalendarDaysIcon, XMarkIcon } from '@heroicons/react/24/outline'; import { formatDate } from '../../lib/utils'; import PersianCalendar from './PersianCalendar'; interface Props { value: string; // YYYY-MM-DD میلادی onChange: (v: string) => void; placeholder?: string; enableYearPicker?: boolean; // برای تاریخ تولد style?: React.CSSProperties; className?: string; } export default function PersianDateInput({ value, onChange, placeholder = 'انتخاب تاریخ', enableYearPicker = false, style, className }: Props) { const [open, setOpen] = useState(false); return (
setOpen(o => !o)} style={{ /* همان استایل visible فعلی: height 36, border/surface, icon, placeholder color */ }}> {value ? formatDate(value) : placeholder} {value && { e.stopPropagation(); onChange(''); }}>}
{open && ( { onChange(v); setOpen(false); }} onClose={() => setOpen(false)} enableYearPicker={enableYearPicker} /> )}
); } ``` - `min`/`max` را حذف کن (استفاده‌ای ندارند). اگر خواستی امن‌تر باشی، نگه‌شان دار ولی بدون اثر — ترجیح: حذف، مطابق قاعده‌ی «کد مرده ننویس». - استایل لایه‌ی visible دقیقاً همان مقادیر فعلی فایل بماند (height 36، `--border`، `--surface`، `minWidth 148`، fontSize 13، رنگ placeholder با `--text-3`). ### ۲. فعال‌کردن انتخاب سال برای تاریخ تولد در `pages/PatientRecordFormPage.tsx:132` (فیلد `birth_date`) پراپ `enableYearPicker` را بده تا کاربر بتواند سریع سال تولد را انتخاب کند: ```tsx form.setValue('birth_date', v)} enableYearPicker /> ``` (الگوی مشابه از قبل در `PatientRecordInfoForm.tsx:133` با `PersianDatePicker … enableYearPicker` هست.) ### ۳. رفع هم‌پوشانی SOLID (اختیاری ولی توصیه‌شده) بعد از این تغییر، `PersianDateInput` و `PersianDatePicker` تقریباً یکی می‌شوند (هر دو = لایه‌ی متنی + PersianCalendar). برای پرهیز از دوگانگی: - گزینه‌ی ساده: `PersianDateInput` را یک روکش نازک روی `PersianDatePicker` کن (`return `)، یا - در همین تسک فقط رفتار را یکی کن و در کامنت بالای فایل اشاره کن که این دو باید در آینده ادغام شوند. حذف کامل یکی از آن‌ها → نیازمند به‌روزرسانی همه‌ی importها است؛ اگر انجامش می‌دهی، همه‌ی call siteها را هم اصلاح کن و tscرا سبز نگه دار. ## نکات مهم - بعد از تغییر، حتماً تایپ‌چک: `ddev exec npx tsc --noEmit --project tsconfig.json` باید سبز شود. - خروجی `PersianCalendar.onChange` همان `YYYY-MM-DD` میلادی است؛ قرارداد داده‌ی ارسالی به API تغییر نمی‌کند — فقط UI تقویم شمسی می‌شود. رفتار submit/فیلترها نباید عوض شود. - `DoctorDetailPage.tsx` یک `PersianDateInput` **محلیِ درون‌فایل** دارد (تعریف حدود خط ۱۷۷) که با `jalaali-js` از قبل شمسی است و پراپ `minDate` دارد؛ به فایل مشترک ربطی ندارد — دست نزن. - تست‌ها: اگر تستی برای `PersianDateInput` هست، آپدیت کن؛ در غیر این صورت یک تست کوتاه Vitest اضافه کن که کلیک روی input، پیکر شمسی را باز می‌کند و انتخاب روز، `onChange` با `YYYY-MM-DD` را صدا می‌زند (حالت موفق + پاک‌کردن مقدار). - تغییرِ فقط-UI است؛ backend و `docs/api/*` نیاز به تغییر ندارند.