feat(clinic): enforce maximum gallery image limit of 5 and update validation logic
This commit is contained in:
@@ -34,6 +34,7 @@ L.Icon.Default.mergeOptions({
|
||||
|
||||
const HUES_LIST = [256, 205, 162, 295, 272];
|
||||
const IRAN_CENTER: [number, number] = [32.4279, 53.6880];
|
||||
const MAX_GALLERY_IMAGES = 5;
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -623,10 +624,21 @@ export default function ClinicDetailPage() {
|
||||
};
|
||||
|
||||
const handleGalleryUpload = async (files: FileList) => {
|
||||
const existing = (clinic?.images_clinic ?? []).filter(img => img?.url);
|
||||
const remaining = MAX_GALLERY_IMAGES - existing.length;
|
||||
if (remaining <= 0) {
|
||||
toast.error(`گالری حداکثر ${MAX_GALLERY_IMAGES} عکس میتواند داشته باشد`);
|
||||
return;
|
||||
}
|
||||
const selected = Array.from(files);
|
||||
if (selected.length > remaining) {
|
||||
toast.error(`فقط ${remaining} عکس دیگر میتوانید اضافه کنید (حداکثر ${MAX_GALLERY_IMAGES})`);
|
||||
}
|
||||
const toUpload = selected.slice(0, remaining);
|
||||
setGalleryUploading(true);
|
||||
try {
|
||||
const uploaded: { url: string }[] = [];
|
||||
for (const file of Array.from(files)) {
|
||||
for (const file of toUpload) {
|
||||
const res = await fetch('/file/upload/clinic_pro/clinic/field_image_clinic', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -640,7 +652,6 @@ export default function ClinicDetailPage() {
|
||||
if (json?.data?.url) uploaded.push({ url: json.data.url });
|
||||
}
|
||||
if (uploaded.length > 0 && clinic) {
|
||||
const existing = (clinic.images_clinic ?? []).filter(img => img?.url);
|
||||
await api.patch(`/api/v1/clinic/${uuid}`, { image_clinic: [...existing, ...uploaded] });
|
||||
toast.success(`${uploaded.length} تصویر اضافه شد`);
|
||||
qc.invalidateQueries({ queryKey: ['clinic-detail', uuid] });
|
||||
@@ -930,8 +941,11 @@ export default function ClinicDetailPage() {
|
||||
{/* Gallery */}
|
||||
<div className="card card-pad">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }}>
|
||||
<b style={{ fontSize: 14 }}>گالری تصاویر</b>
|
||||
<button className="btn ghost sm" onClick={() => galleryInputRef.current?.click()} disabled={galleryUploading}>
|
||||
<b style={{ fontSize: 14 }}>
|
||||
گالری تصاویر ({formatNumber((clinic.images_clinic ?? []).filter(i => i?.url).length)}/{formatNumber(MAX_GALLERY_IMAGES)})
|
||||
</b>
|
||||
<button className="btn ghost sm" onClick={() => galleryInputRef.current?.click()}
|
||||
disabled={galleryUploading || (clinic.images_clinic ?? []).filter(i => i?.url).length >= MAX_GALLERY_IMAGES}>
|
||||
<PlusIcon style={{ width: 14, height: 14 }} />
|
||||
{galleryUploading ? 'در حال آپلود...' : 'افزودن تصویر'}
|
||||
</button>
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ Create a new clinic.
|
||||
| `longitude` | float | ❌ | Longitude for map |
|
||||
| `state` | string | ❌ | Province name |
|
||||
| `city` | string | ❌ | City name |
|
||||
| `image_clinic` | object[] | ❌ | Gallery images `[{url: "..."}]` |
|
||||
| `image_clinic` | object[] | ❌ | Gallery images `[{url: "..."}]` — **max 5**; more returns `ERR_VALIDATION_001` (422) |
|
||||
| `clinic_logo` | string | ❌ | Logo URL |
|
||||
| `doctors` | string[] | ❌ | Doctor UUIDs to associate |
|
||||
| `specialties` | integer[] | ❌ | Specialty IDs |
|
||||
|
||||
@@ -30,6 +30,8 @@ use Symfony\Component\Uid\Uuid;
|
||||
#[OA\Tag(name: 'Clinics')]
|
||||
class ClinicController extends BaseController
|
||||
{
|
||||
private const MAX_GALLERY_IMAGES = 5;
|
||||
|
||||
public function __construct(
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
@@ -91,6 +93,9 @@ class ClinicController extends BaseController
|
||||
public function create(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
if (($err = $this->validateGallerySize($data)) !== null) {
|
||||
return $err;
|
||||
}
|
||||
$clinic = new Clinic($user);
|
||||
$this->hydrateClinic($clinic, $data);
|
||||
$this->clinicRepo->save($clinic);
|
||||
@@ -209,6 +214,9 @@ class ClinicController extends BaseController
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
if (($err = $this->validateGallerySize($data)) !== null) {
|
||||
return $err;
|
||||
}
|
||||
$this->hydrateClinic($clinic, $data);
|
||||
$this->clinicRepo->save($clinic);
|
||||
|
||||
@@ -457,6 +465,22 @@ class ClinicController extends BaseController
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private function validateGallerySize(array $data): ?JsonResponse
|
||||
{
|
||||
if (array_key_exists('image_clinic', $data)
|
||||
&& is_array($data['image_clinic'])
|
||||
&& count($data['image_clinic']) > self::MAX_GALLERY_IMAGES
|
||||
) {
|
||||
return $this->error(
|
||||
ErrorCodes::ERR_VALIDATION_001,
|
||||
'گالری تصاویر حداکثر ' . self::MAX_GALLERY_IMAGES . ' عکس میتواند داشته باشد',
|
||||
422,
|
||||
'image_clinic',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function hydrateClinic(Clinic $clinic, array $data): void
|
||||
{
|
||||
if (array_key_exists('name', $data)) $clinic->setName($data['name']);
|
||||
@@ -476,9 +500,9 @@ class ClinicController extends BaseController
|
||||
$clinic->setCityId((int) $data['city'][0]);
|
||||
}
|
||||
|
||||
// Images stored as JSON (from upload response)
|
||||
// Images stored as JSON (from upload response); gallery is capped at 5.
|
||||
if (array_key_exists('image_clinic', $data) && is_array($data['image_clinic'])) {
|
||||
$clinic->setImagesClinic($data['image_clinic']);
|
||||
$clinic->setImagesClinic(array_slice(array_values($data['image_clinic']), 0, self::MAX_GALLERY_IMAGES));
|
||||
}
|
||||
if (array_key_exists('clinic_logo', $data)) {
|
||||
$logo = $data['clinic_logo'];
|
||||
|
||||
Reference in New Issue
Block a user