Files
clinicpro/.claude/prompt/admin-jalali-date-input.md
T

157 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# همه‌ی تقویم‌های پنل ادمین باید شمسی باشند (رفع تقویم میلادی 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` — لایه‌ی متنی شمسی است ولی پیکر بومی میلادی:
```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ی نشکند.
```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 (
<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` را بده
تا کاربر بتواند سریع سال تولد را انتخاب کند:
```tsx
<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/*` نیاز به تغییر ندارند.