feat(patient): complete "اطلاعات پرونده" demographic form
Backend:
- Add profile columns field_of_study, province_id, city_id, postal_code,
referral_source (UserProfile + migration).
- Extend PATCH /api/v1/patient/{uuid} to persist all demographic fields
and return them in the patient profile payload.
- Support editable mobile (login identifier): validation, uniqueness,
User.setMobileNumber, new ERR_PROFILE_002.
- Update docs/api/patient.md.
Frontend:
- New reusable Input, Field, and PatientRecordInfoForm (RHF + Zod).
- usePatient/useUpdatePatient hooks and patientForm mapping helpers.
- Extend the existing "info" tab in MyPatientsPage to the full field set
via the shared form (province/city/insurance options, Jalali date).
Tests: Patient entity + PATCH integration (PHPUnit); form, hooks, and
mapping helpers (Vitest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import type { SelectOption } from '../components/ui/SearchableSelect';
|
||||
import type { PatientProfile, PatientProfileUpdate } from '../types';
|
||||
import type { PatientFormValues } from '../components/PatientRecordInfoForm';
|
||||
|
||||
// ── enumهای ثابت فرم «اطلاعات پرونده» (بدون منبع API) ─────────────────────────
|
||||
export const GENDER_OPTS: SelectOption[] = [
|
||||
{ value: 'female', label: 'زن' },
|
||||
{ value: 'male', label: 'مرد' },
|
||||
];
|
||||
export const MARITAL_OPTS: SelectOption[] = [
|
||||
{ value: 'single', label: 'مجرد' },
|
||||
{ value: 'married', label: 'متأهل' },
|
||||
{ value: 'divorced', label: 'مطلقه' },
|
||||
{ value: 'widowed', label: 'بیوه' },
|
||||
];
|
||||
export const EDUCATION_OPTS: SelectOption[] = [
|
||||
'زیر دیپلم', 'دیپلم', 'کاردانی', 'کارشناسی', 'کارشناسی ارشد', 'دکتری',
|
||||
].map((v) => ({ value: v, label: v }));
|
||||
export const REFERRAL_OPTS: SelectOption[] = [
|
||||
'اینستاگرام', 'معرفی دوستان', 'جستجوی اینترنتی', 'بیلبورد', 'سایر',
|
||||
].map((v) => ({ value: v, label: v }));
|
||||
|
||||
// ── تبدیل تاریخ: timestamp ثانیهای ↔ رشتهٔ میلادی YYYY-MM-DD ─────────────────
|
||||
export function tsToDateStr(ts?: number | null): string {
|
||||
if (!ts) return '';
|
||||
return new Date(ts * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
export function dateStrToTs(s: string): number | null {
|
||||
if (!s) return null;
|
||||
const t = Date.parse(`${s}T00:00:00Z`);
|
||||
return Number.isNaN(t) ? null : Math.floor(t / 1000);
|
||||
}
|
||||
|
||||
/** پروفایل API → مقادیر پیشفرض فرم. */
|
||||
export function profileToFormValues(p: PatientProfile | null): PatientFormValues {
|
||||
return {
|
||||
name: p?.name ?? '',
|
||||
mobile: p?.mobile ?? '',
|
||||
national_code: p?.national_code ?? '',
|
||||
gender: p?.gender ?? null,
|
||||
birth_date: tsToDateStr(p?.date_of_birth),
|
||||
fathers_name: p?.fathers_name ?? '',
|
||||
marital_status: p?.marital_status ?? null,
|
||||
basic_insurance_id: p?.basic_insurance_id ?? null,
|
||||
field_of_study: p?.field_of_study ?? '',
|
||||
education: p?.education ?? null,
|
||||
job: p?.job ?? '',
|
||||
province_id: p?.province_id ?? null,
|
||||
city_id: p?.city_id ?? null,
|
||||
home_phone: p?.home_phone ?? '',
|
||||
address: p?.address ?? '',
|
||||
postal_code: p?.postal_code ?? '',
|
||||
referral_source: p?.referral_source ?? null,
|
||||
description: p?.description ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
/** مقادیر فرم → بدنهٔ PATCH. موبایل خالی ارسال نمیشود تا شناسهٔ ورود دستنخورده بماند. */
|
||||
export function formValuesToPayload(v: PatientFormValues): PatientProfileUpdate {
|
||||
const num = (x: number | string | null): number | null =>
|
||||
x === null || x === '' ? null : Number(x);
|
||||
const payload: PatientProfileUpdate = {
|
||||
name: v.name,
|
||||
national_code: v.national_code,
|
||||
gender: v.gender,
|
||||
fathers_name: v.fathers_name,
|
||||
date_of_birth: dateStrToTs(v.birth_date),
|
||||
marital_status: v.marital_status,
|
||||
education: v.education,
|
||||
field_of_study: v.field_of_study,
|
||||
job: v.job,
|
||||
basic_insurance_id: num(v.basic_insurance_id),
|
||||
province_id: num(v.province_id),
|
||||
city_id: num(v.city_id),
|
||||
home_phone: v.home_phone,
|
||||
address: v.address,
|
||||
postal_code: v.postal_code,
|
||||
referral_source: v.referral_source,
|
||||
description: v.description,
|
||||
};
|
||||
if (v.mobile) payload.mobile = v.mobile;
|
||||
return payload;
|
||||
}
|
||||
Reference in New Issue
Block a user