9.0 KiB
همهی تقویمهای پنل ادمین باید شمسی باشند (رفع تقویم میلادی PersianDateInput)
پروژه
clinicpro (React 19 admin SPA داخل Symfony — assets/admin/)
زمینه
کل محصول فارسی/RTL و تاریخها Jalali (شمسی) است. تقویم شمسی قبلاً نوشته شده:
PersianCalendar.tsx (پیکر ماه/سال/روز شمسی، خروجی YYYY-MM-DD میلادی) و روکش آن
PersianDatePicker.tsx. اما یک کامپوننت دیگر بهنام PersianDateInput.tsx هنوز از
<input type="date"> بومی مرورگر استفاده میکند که تقویم میلادی مرورگر را باز میکند.
هرجای پنل که این کامپوننت استفاده شده، کاربر تقویم میلادی میبیند — خلاف قاعدهی محصول.
مشکل / هدف
هدف: هرجای پنل ادمین که از انتخاب تاریخ (تقویم) استفاده میشود، تقویم شمسی نمایش دهد.
تنها منبع میلادی در کل ادمین: 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 — لایهی متنی شمسی است ولی پیکر بومی میلادی:
{/* hidden native input — opens picker on click */}
<input
ref={hiddenRef}
type="date" // ← تقویم میلادی مرورگر
value={value}
min={min}
max={max}
onChange={e => 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 روی پیکر شمسی
<input type="date"> را حذف کن و همان الگوی PersianDatePicker.tsx را به کار ببر:
state باز/بسته + رندر شرطی <PersianCalendar>. لایهی متنی visible و formatDate(value)
و دکمهی پاککردن (X) و امضای Props فعلی را حفظ کن تا هیچ call siteی نشکند.
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 (
<div style={{ position: 'relative', display: 'inline-block', ...style }} className={className}>
<div onClick={() => setOpen(o => !o)} style={{ /* همان استایل visible فعلی: height 36, border/surface, icon, placeholder color */ }}>
<CalendarDaysIcon /* … */ />
<span style={{ flex: 1 }}>{value ? formatDate(value) : placeholder}</span>
{value && <span onClick={e => { e.stopPropagation(); onChange(''); }}><XMarkIcon /* … */ /></span>}
</div>
{open && (
<PersianCalendar
value={value}
onChange={v => { onChange(v); setOpen(false); }}
onClose={() => setOpen(false)}
enableYearPicker={enableYearPicker}
/>
)}
</div>
);
}
min/maxرا حذف کن (استفادهای ندارند). اگر خواستی امنتر باشی، نگهشان دار ولی بدون اثر — ترجیح: حذف، مطابق قاعدهی «کد مرده ننویس».- استایل لایهی visible دقیقاً همان مقادیر فعلی فایل بماند (height 36،
--border،--surface،minWidth 148، fontSize 13، رنگ placeholder با--text-3).
۲. فعالکردن انتخاب سال برای تاریخ تولد
در pages/PatientRecordFormPage.tsx:132 (فیلد birth_date) پراپ enableYearPicker را بده
تا کاربر بتواند سریع سال تولد را انتخاب کند:
<PersianDateInput value={form.watch('birth_date') ?? ''} onChange={(v) => form.setValue('birth_date', v)} enableYearPicker />
(الگوی مشابه از قبل در PatientRecordInfoForm.tsx:133 با PersianDatePicker … enableYearPicker هست.)
۳. رفع همپوشانی SOLID (اختیاری ولی توصیهشده)
بعد از این تغییر، PersianDateInput و PersianDatePicker تقریباً یکی میشوند
(هر دو = لایهی متنی + PersianCalendar). برای پرهیز از دوگانگی:
- گزینهی ساده:
PersianDateInputرا یک روکش نازک رویPersianDatePickerکن (return <PersianDatePicker {...props} />)، یا - در همین تسک فقط رفتار را یکی کن و در کامنت بالای فایل اشاره کن که این دو باید در آینده ادغام شوند. حذف کامل یکی از آنها → نیازمند بهروزرسانی همهی 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/*نیاز به تغییر ندارند.