# افزودن فیلد «تاریخ شروع فعالیت» (سال تجربه) در پنل ادمین با تقویم شمسی

## پروژه

`clinicpro` (Admin SPA + کمی backend/docs). سایت عمومی `nobat724_front` تغییری لازم ندارد (توضیح در «نکات مهم»).

## زمینه

هر پزشک باید «سال تجربه» داشته باشد. این عدد در backend از فیلد `activity_time` (تاریخ شروع کار، به‌صورت Unix timestamp) محاسبه می‌شود و همه‌چیزِ backend و سایت عمومی از قبل آماده است:

- `Doctor::$activityTime` (ستون `activity_time`, `integer`, nullable) + `getExperience()` که سال تجربه را حساب می‌کند.
- `toDetailArray()` هم `experience` (عدد سال) و هم `activity_time` (رشتهٔ timestamp) را برمی‌گرداند.
- endpointهای `POST /api/v1/doctor` و `PATCH /api/v1/doctor/{uuid}` کلید `activity_time` را در بدنه می‌پذیرند (`hydrate...` → `setActivityTime((int) $data['activity_time'])`).
- سایت عمومی در `nobat724_front/components/doctor/detailDoctor/Title.js` از قبل `{doctor?.experience} سال تجربه` را نشان می‌دهد.

مشکل این است که **پنل ادمین (Admin SPA) هیچ فیلدی برای ست‌کردن این تاریخ ندارد**، پس `activity_time` همیشه `null` می‌ماند، `getExperience()` صفر برمی‌گرداند و در صفحهٔ عمومی «۰ سال تجربه» نمایش داده می‌شود (مثال: `/doctor/fbc11068-dd01-4c0c-9dab-ab86c38061ca`).

## مشکل / هدف

در فرم‌های پزشک در Admin SPA یک فیلد «تاریخ شروع فعالیت» با **تقویم شمسی** اضافه شود که ترتیب انتخاب آن **سال → ماه → روز** باشد. این کامپوننت از قبل در پروژه وجود دارد: `PersianDatePicker` با prop `enableYearPicker`. مقدار انتخاب‌شده باید به Unix timestamp (ثانیه) تبدیل و در کلید `activity_time` به API ارسال شود؛ و هنگام ویرایش، `activity_time` موجود باید به تاریخ برای نمایش در picker تبدیل شود.

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `clinicpro/assets/admin/pages/DoctorDetailPage.tsx` | صفحهٔ اصلی ویرایش پزشک (هم ادمین هم پروفایل خود پزشک via `DoctorProfilePage`) — فرم ویرایش اینجاست |
| `clinicpro/assets/admin/pages/DoctorFormPage.tsx` | فرم «افزودن پزشک جدید» (POST) |
| `clinicpro/assets/admin/components/ui/PersianDatePicker.tsx` | تقویم شمسی موجود؛ prop `enableYearPicker` = ترتیب سال→ماه→روز |
| `clinicpro/assets/admin/lib/utils.ts` | `toDate()`، `toGregorianDate()`، `formatDate()` برای تبدیل تاریخ |
| `clinicpro/src/Doctor/Controller/DoctorController.php` | annotationهای `OA\Property` بدنهٔ create/update (فاقد `activity_time`) |
| `clinicpro/docs/api/doctor.md` | مستندات request body (فاقد `activity_time`) |

## وضعیت فعلی

### backend (آماده — فقط annotation/doc ناقص)

```php
// src/Doctor/Entity/Doctor.php
public function getExperience(): int
{
    if ($this->activityTime === null) {
        return 0;
    }
    return max(0, (int)((time() - $this->activityTime) / (365.25 * 24 * 3600)));
}

// src/Doctor/Controller/DoctorController.php (hydrate مشترک create/update)
if (array_key_exists('activity_time', $data)) $doctor->setActivityTime((int) $data['activity_time']);
```

### کامپوننت تقویم (قرارداد ورودی/خروجی)

```tsx
// PersianDatePicker.tsx
interface Props {
  value: string;                 // YYYY-MM-DD میلادی
  onChange: (v: string) => void;
  placeholder?: string;
  height?: number;
  minWidth?: number;
  enableYearPicker?: boolean;     // سال→ماه→روز
}
```

مقدار picker یک رشتهٔ **میلادی `YYYY-MM-DD`** است (نمایش داخلی‌اش شمسی است). نمونهٔ استفادهٔ موجود در `RepresentationProfilePage.tsx`:

```tsx
<PersianDatePicker value={birthDate} onChange={setBirthDate} placeholder="تاریخ تولد" enableYearPicker />
```

### فرم ویرایش پزشک — `DoctorDetailPage.tsx` (وضعیت فعلی، بدون فیلد تاریخ)

```tsx
// interface (خط ~44)
experience: number; activity_time: string | null; medical_system_code: string | null;

// zod schema (خط ~2191)
const editSchema = z.object({
  name: z.string().min(2, 'نام حداقل ۲ کاراکتر'),
  gender: z.enum(['man', 'woman']).optional().or(z.literal('')),
  degree: z.string().optional().or(z.literal('')),
  medical_system_code: z.string().max(30).optional().or(z.literal('')),
  // ... activity_time نیست
});

// reset مقادیر هنگام لود (خط ~2383)
reset({
  name: doctor.name,
  gender: (doctor.gender as any) ?? '',
  degree: doctor.degree ?? '',
  medical_system_code: doctor.medical_system_code ?? '',
  // ... activity_time نیست
});

// ارسال PATCH (خط ~2415)
mutationFn: (body: EditForm) => api.patch<ApiResponse<any>>(`/api/v1/doctor/${uuid}`, {
  title: body.name,
  gender: editGender || undefined,
  degree: body.degree || undefined,
  medical_system_code: body.medical_system_code || undefined,
  info: body.info || undefined,
  // ... activity_time نیست
}),

// JSX فرم — بعد از grid «درجه تحصیلی / کد نظام پزشکی» (خط ~2878)
```

### فرم ساخت پزشک — `DoctorFormPage.tsx` (خط ~282)

```tsx
mutationFn: (values: FormValues) =>
  api.post<ApiResponse<{ uuid: string }>>(createDoctorEndpoint, {
    // ...
    gender: gender || undefined,
    degree: values.degree || undefined,
    medical_system_code: values.medical_system_code || undefined,
    info: values.info || undefined,
    // ... activity_time نیست
  }),
```

## وظایف

### ۱. افزودن فیلد تاریخ به فرم ویرایش `DoctorDetailPage.tsx` (اصلی)

state جدا برای تاریخ نگه‌دار (مثل الگوی `editGender`) تا نیازی به دست‌زدن به zod نباشد:

```tsx
const [editActivityDate, setEditActivityDate] = useState('');   // YYYY-MM-DD میلادی یا ''
```

هنگام لود در `useEffect`/`reset` مقدار اولیه را از `activity_time` (ثانیه) بساز:

```tsx
setEditActivityDate(
  doctor.activity_time ? toGregorianDate(toDate(Number(doctor.activity_time))!) : ''
);
```

(از `../lib/utils` → `toDate`, `toGregorianDate` را import کن اگر نیستند.)

در JSX، بعد از grid «درجه تحصیلی / کد نظام پزشکی» (خط ~۲۸۷۸) یک `EditField` اضافه کن:

```tsx
<div style={{ marginTop: 14 }}>
  <EditField label="تاریخ شروع فعالیت">
    <PersianDatePicker
      value={editActivityDate}
      onChange={setEditActivityDate}
      placeholder="انتخاب تاریخ"
      enableYearPicker
      minWidth={200}
    />
  </EditField>
</div>
```

در `updateMut` کلید `activity_time` را اضافه کن (تبدیل به ثانیه؛ خالی → `null` تا پاک شود):

```tsx
activity_time: editActivityDate
  ? Math.floor(new Date(`${editActivityDate}T12:00:00`).getTime() / 1000)
  : null,
```

> نکته: backend فقط وقتی کلید `activity_time` در بدنه **باشد** آن را ست می‌کند؛ ارسال `null` باعث `setActivityTime((int) null) = 0` می‌شود. اگر می‌خواهی «پاک‌کردن» واقعی (بازگشت به null) پشتیبانی شود، ببین وظیفهٔ ۳ (backend باید `null` را جدا از عدد مدیریت کند). در غیر این صورت وقتی خالی است اصلاً کلید را نفرست:
> ```tsx
> ...(editActivityDate ? { activity_time: Math.floor(new Date(`${editActivityDate}T12:00:00`).getTime()/1000) } : {}),
> ```
> رویکرد دوم (نفرستادن هنگام خالی) ساده‌تر و بدون تغییر backend است — همان را استفاده کن مگر پاک‌کردن لازم باشد.

همچنین (اختیاری ولی مفید): در بخش نمایش، کارت `experience` از قبل هست (`InfoCard ... label="سابقه (سال)"`); می‌توانی زیرش تاریخ شروع را با `formatDate(doctor.activity_time)` نشان دهی.

### ۲. افزودن همان فیلد به فرم ساخت `DoctorFormPage.tsx`

- `const [activityDate, setActivityDate] = useState('')`
- در JSX کنار سایر فیلدهای «اطلاعات حرفه‌ای» یک `PersianDatePicker ... enableYearPicker` با همان الگو.
- در بدنهٔ `api.post(createDoctorEndpoint, {...})` هنگام مقدار داشتن، `activity_time` را به ثانیه اضافه کن (همان تبدیل وظیفهٔ ۱).

### ۳. (فقط اگر «پاک‌کردن» لازم است) اصلاح hydrate در backend

اگر تصمیم گرفتی ارسال `null` را پشتیبانی کنی، در `DoctorController` جایی که `activity_time` hydrate می‌شود مقدار `null` را جدا مدیریت کن تا به `0` تبدیل نشود:

```php
if (array_key_exists('activity_time', $data)) {
    $doctor->setActivityTime($data['activity_time'] !== null ? (int) $data['activity_time'] : null);
}
```

اگر رویکرد «نفرستادن هنگام خالی» را انتخاب کردی، این وظیفه لازم نیست.

### ۴. به‌روزرسانی annotation و مستندات API

- در `DoctorController` به بدنهٔ **هر دو** endpoint (create ~خط ۶۳ و update ~خط ۲۷۱) این property را اضافه کن:
  ```php
  new OA\Property(property: 'activity_time', type: 'integer', nullable: true, description: 'Unix timestamp (ثانیه) تاریخ شروع فعالیت؛ مبنای محاسبهٔ سال تجربه'),
  ```
- در `clinicpro/docs/api/doctor.md`: به جدول request body هر دو endpoint (POST و PATCH) ردیف `activity_time` (integer, Unix seconds, nullable) را اضافه کن و توضیح بده که `experience` در پاسخ از همین فیلد محاسبه می‌شود.

## نکات مهم

- **تقویم موجود را استفاده کن، جدید نساز:** `PersianDatePicker` با `enableYearPicker` دقیقاً ترتیب سال→ماه→روز را می‌دهد (همان که کاربر گفت «قبلاً این تقویم را نوشتی»). از `PersianDateInput` (که native `type=date` میلادی است) استفاده **نکن**.
- **قرارداد مقدار picker میلادی است** (`YYYY-MM-DD`)؛ نمایش داخلی خودش شمسی است. تبدیل به/از Unix timestamp سمت فرم انجام می‌شود.
- **واحد timestamp ثانیه است** (نه میلی‌ثانیه) — `getExperience()` با `time()` (ثانیه) کار می‌کند. حتماً `/1000` بزن.
- برای ثبات در برابر timezone از `T12:00:00` (ظهر) هنگام ساخت `Date` استفاده کن تا با تبدیل شمسی یک‌روز-جابه‌جایی رخ ندهد (الگوی موجود در `utils.toDate`).
- `DoctorProfilePage` صرفاً `<DoctorDetailPage isOwnProfile />` است؛ با اصلاح `DoctorDetailPage` هر دو مسیر (ادمین و پروفایل خود پزشک) پوشش داده می‌شوند.
- **سایت عمومی تغییری لازم ندارد:** `Title.js` از قبل `doctor.experience` را رندر می‌کند؛ به‌محض ست‌شدن `activity_time`، مقدار درست نمایش داده می‌شود. (اختیاری: اگر خواستی وقتی `experience === 0` عبارت «۰ سال تجربه» نمایش داده نشود، این یک تغییر کوچک نمایشی در `nobat724_front` است، خارج از این پرامپت.)
- بعد از تغییر TSX: `ddev exec npx tsc --noEmit --project tsconfig.json` و `ddev exec yarn dev` برای build.
- بعد از تغییر annotation کنترلر: `ddev exec php bin/console cache:clear` و طبق قانون پروژه `docs/api/doctor.md` را در همین session به‌روز کن.
