feat: enhance doctor address management by including clinic addresses and enforcing required fields

This commit is contained in:
hamed
2026-06-13 18:30:06 +03:30
parent c1c57df4ff
commit d05c070661
5 changed files with 289 additions and 21 deletions
+26 -7
View File
@@ -771,12 +771,19 @@ function ServicesPicker({ selected, onChange, services, specialties, selectedSpe
const addrSchema = z.object({
name: z.string().optional(),
address: z.string().optional(),
telephone: z.string().max(20).optional(),
address: z.string().min(1, 'آدرس کامل اجباری است'),
telephone: z.string().min(1, 'تلفن اجباری است').max(20),
province_id: z.number().nullable().optional(),
city_id: z.number().nullable().optional(),
latitude: z.string().optional(),
longitude: z.string().optional(),
}).superRefine((data, ctx) => {
if (!data.province_id) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'استان اجباری است', path: ['province_id'] });
}
if (!data.city_id) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'شهر اجباری است', path: ['city_id'] });
}
});
type AddrForm = z.infer<typeof addrSchema>;
@@ -785,7 +792,7 @@ function AddressModal({ open, onClose, existing, doctorUuid, onSaved }: {
existing: AddressData | null; doctorUuid: string;
onSaved: () => void;
}) {
const { register, handleSubmit, watch, setValue, reset } = useForm<AddrForm>({
const { register, handleSubmit, watch, setValue, reset, formState: { errors } } = useForm<AddrForm>({
resolver: zodResolver(addrSchema),
});
const provinceId = watch('province_id');
@@ -864,21 +871,29 @@ function AddressModal({ open, onClose, existing, doctorUuid, onSaved }: {
<input type="text" className="input" placeholder="مثال: کلینیک مهر" {...register('name')} />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">تلفن</label>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
تلفن <span className="text-red-500">*</span>
</label>
<input type="text" dir="ltr" className="cp-input text-left" placeholder="021..." {...register('telephone')} />
{errors.telephone && <p className="text-xs text-red-500 mt-1">{errors.telephone.message}</p>}
</div>
</div>
{/* Row 2: Full address */}
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">آدرس کامل</label>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
آدرس کامل <span className="text-red-500">*</span>
</label>
<textarea rows={2} className="cp-input resize-none" placeholder="خیابان، کوچه، پلاک..." {...register('address')} />
{errors.address && <p className="text-xs text-red-500 mt-1">{errors.address.message}</p>}
</div>
{/* Row 3: Province + City side by side */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">استان</label>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
استان <span className="text-red-500">*</span>
</label>
<SearchableSelect
options={provinces.map(p => ({ value: p.id, label: p.name }))}
value={provinceId ?? null}
@@ -889,9 +904,12 @@ function AddressModal({ open, onClose, existing, doctorUuid, onSaved }: {
setMapFlyTarget(null);
}}
/>
{errors.province_id && <p className="text-xs text-red-500 mt-1">{errors.province_id.message}</p>}
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">شهر</label>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
شهر <span className="text-red-500">*</span>
</label>
<SearchableSelect
options={cities.map(c => ({ value: c.id, label: c.name }))}
value={cityId ?? null}
@@ -906,6 +924,7 @@ function AddressModal({ open, onClose, existing, doctorUuid, onSaved }: {
}
}}
/>
{errors.city_id && <p className="text-xs text-red-500 mt-1">{errors.city_id.message}</p>}
</div>
</div>