diff --git a/app/Http/Requests/Admin/Manage/PurchaseRequest.php b/app/Http/Requests/Admin/Manage/PurchaseRequest.php index dad0407..16fe137 100644 --- a/app/Http/Requests/Admin/Manage/PurchaseRequest.php +++ b/app/Http/Requests/Admin/Manage/PurchaseRequest.php @@ -41,7 +41,8 @@ public function rules(): array 'discount' => ['nullable', 'integer', 'min:0'], 'shipping_cost' => ['nullable', 'integer', 'min:0'], 'notes' => ['nullable', 'string', 'max:100'], - 'photo_key' => ['nullable', 'string', 'max:500'], + 'photo_keys' => ['nullable', 'array', 'max:5'], + 'photo_keys.*' => ['required', 'string', 'max:500'], ]; } @@ -63,7 +64,8 @@ public function attributes(): array 'discount' => 'diskon', 'shipping_cost' => 'ongkir', 'notes' => 'keterangan', - 'photo_key' => 'foto', + 'photo_keys' => 'foto', + 'photo_keys.*' => 'foto', ]; } } diff --git a/app/Services/Admin/Manage/PurchaseService.php b/app/Services/Admin/Manage/PurchaseService.php index 1b2c8d9..d13d691 100644 --- a/app/Services/Admin/Manage/PurchaseService.php +++ b/app/Services/Admin/Manage/PurchaseService.php @@ -12,6 +12,7 @@ use App\Services\NotificationService; use App\Services\S3PresignedService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; class PurchaseService @@ -49,19 +50,19 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = ->paginate($perPage); $paginator->getCollection()->each(function (Purchase $purchase) { - $purchaseMedia = $purchase->getFirstMedia('photos'); - $purchase->photo_url = $purchaseMedia - ? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath()) - : null; - $purchase->photo_conversion_url = $purchaseMedia - ? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath('thumb')) - : null; + $purchaseMedia = $purchase->getMedia('photos'); + $purchase->photo_urls = $purchaseMedia->map( + fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath()) + )->toArray(); + $purchase->photo_conversion_urls = $purchaseMedia->map( + fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath('thumb')) + )->toArray(); }); return $paginator; } - public function getItems(Purchase $purchase): \Illuminate\Support\Collection + public function getItems(Purchase $purchase): Collection { return $purchase->purchaseItems() ->select(['id', 'purchase_id', 'raw_material_price_id', 'quantity', 'unit_price', 'subtotal']) @@ -146,11 +147,13 @@ public function getForEdit(Purchase $purchase): array ->whereIn('raw_material_price_id', $items->pluck('raw_material_price_id')) ->exists(); - $purchaseMedia = $purchase->getFirstMedia('photos'); - $purchasePhotoKey = $purchaseMedia?->getCustomProperty('s3_key') ?? $purchaseMedia?->file_name; - $purchasePhotoUrl = $purchaseMedia - ? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath()) - : null; + $purchaseMedia = $purchase->getMedia('photos'); + $purchasePhotoKeys = $purchaseMedia->map( + fn ($media) => $media->getCustomProperty('s3_key') ?? $media->file_name + )->toArray(); + $purchasePhotoUrls = $purchaseMedia->map( + fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath()) + )->toArray(); return [ 'id' => $purchase->id, @@ -160,8 +163,8 @@ public function getForEdit(Purchase $purchase): array 'discount' => $purchase->discount, 'shipping_cost' => $purchase->shipping_cost, 'notes' => $purchase->notes, - 'photo_key' => $purchasePhotoKey, - 'photo_url' => $purchasePhotoUrl, + 'photo_keys' => $purchasePhotoKeys, + 'photo_urls' => $purchasePhotoUrls, 'variants' => $variants, 'default_mode' => $singleMaterial && ! $sharedWithOther ? 'new' : 'existing', 'existing_material_name' => $singleMaterial ? $materials->first()->name : null, @@ -228,12 +231,11 @@ private function storeFromExisting(array $data): Purchase ->increment('stock', (int) $item['quantity']); } - if (! empty($data['photo_key'])) { - $this->registerMedia( + if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) { + $this->registerPhotos( model: $purchase, - s3Key: $data['photo_key'], + photoKeys: $data['photo_keys'], collectionName: 'photos', - orderColumn: 1, ); } @@ -318,12 +320,11 @@ private function storeNew(array $data): Purchase DB::table('purchase_items')->insert($purchaseItems); - if (! empty($data['photo_key'])) { - $this->registerMedia( + if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) { + $this->registerPhotos( model: $purchase, - s3Key: $data['photo_key'], + photoKeys: $data['photo_keys'], collectionName: 'photos', - orderColumn: 1, ); } @@ -474,7 +475,7 @@ public function update(Purchase $purchase, array $data): Purchase } DB::table('purchase_items')->insert($itemRows); - $this->syncPhoto($purchase, $data); + $this->syncPhotos($purchase, $data['photo_keys'] ?? [], 'photos'); return $purchase; }); diff --git a/resources/js/lib/purchase-draft.ts b/resources/js/lib/purchase-draft.ts index 69ef56c..91c7eea 100644 --- a/resources/js/lib/purchase-draft.ts +++ b/resources/js/lib/purchase-draft.ts @@ -11,7 +11,7 @@ export type PurchaseDraftData = { variant: string; price: number; stock: number; - photo?: string; + photo_keys?: string[]; }>; mode?: 'new' | 'existing'; selectedMaterialName?: string; diff --git a/resources/js/pages/admin/manage/purchase/columns.tsx b/resources/js/pages/admin/manage/purchase/columns.tsx index 1678123..16abf9d 100644 --- a/resources/js/pages/admin/manage/purchase/columns.tsx +++ b/resources/js/pages/admin/manage/purchase/columns.tsx @@ -16,8 +16,8 @@ export type Purchase = { shipping_cost: number; total: number; notes: string | null; - photo_url: string | null; - photo_conversion_url: string | null; + photo_urls: string[]; + photo_conversion_urls: string[]; created_at: string; variants_count: number; total_qty: number; @@ -65,8 +65,8 @@ export type PurchaseForEdit = { discount: number; shipping_cost: number; notes: string | null; - photo_key: string | null; - photo_url: string | null; + photo_keys: string[]; + photo_urls: string[]; variants: { id: number; variant: string; diff --git a/resources/js/pages/admin/manage/purchase/create.tsx b/resources/js/pages/admin/manage/purchase/create.tsx index 7baf5e0..a27ee91 100644 --- a/resources/js/pages/admin/manage/purchase/create.tsx +++ b/resources/js/pages/admin/manage/purchase/create.tsx @@ -14,7 +14,7 @@ import { import { useCallback, useMemo, useRef, useState } from 'react'; import { toast } from 'sonner'; import { ConfirmDialog } from '@/components/dialogs'; -import { FileUpload } from '@/components/inputs'; +import { FileUpload, FileUploadMultiple } from '@/components/inputs'; import { ImagePreviewModal } from '@/components/dialogs'; import { InputError } from '@/components/ui'; import { NumberInput } from '@/components/inputs'; @@ -114,10 +114,15 @@ export default function PurchaseCreate({ suppliers, rawMaterials }: Props) { const [discount, setDiscount] = useState(draft?.discount ?? 0); const [shippingCost, setShippingCost] = useState(draft?.shippingCost ?? 0); const [notes, setNotes] = useState(draft?.notes ?? ''); - const [photo, setPhoto] = useState(draft?.photo ?? null); - const [photoUrl, setPhotoUrl] = useState( - draft?.photo ? getTemporaryUrl(draft.photo) : null, - ); + const [photos, setPhotos] = useState<{ key: string; url: string | null }[]>(() => { + if (draft?.photo_keys && draft.photo_keys.length > 0) { + return draft.photo_keys.map((key: string) => ({ + key, + url: getTemporaryUrl(key), + })); + } + return []; + }); const [uploading, setUploading] = useState(false); const [mode, setMode] = useState<'new' | 'existing'>(draft?.mode ?? 'new'); @@ -158,7 +163,7 @@ export default function PurchaseCreate({ suppliers, rawMaterials }: Props) { qty, ]), ), - photo: photo ?? undefined, + photo_keys: photos.map((p) => p.key), }), [ name, @@ -171,7 +176,7 @@ export default function PurchaseCreate({ suppliers, rawMaterials }: Props) { mode, selectedMaterialName, quantities, - photo, + photos, ], ); @@ -390,7 +395,7 @@ export default function PurchaseCreate({ suppliers, rawMaterials }: Props) { discount, shipping_cost: shippingCost, notes: notes || null, - photo_key: photo, + photo_keys: photos.map((p) => p.key), }; if (mode === 'existing') { @@ -1099,21 +1104,15 @@ export default function PurchaseCreate({ suppliers, rawMaterials }: Props) {
- { - setPhoto(key); - const url = key - ? `https://dstpabuaran.s3.ap-southeast-1.amazonaws.com/${key}` - : null; - setPhotoUrl(url); - }} +
diff --git a/resources/js/pages/admin/manage/purchase/edit.tsx b/resources/js/pages/admin/manage/purchase/edit.tsx index 5fd1fd0..c1b15bd 100644 --- a/resources/js/pages/admin/manage/purchase/edit.tsx +++ b/resources/js/pages/admin/manage/purchase/edit.tsx @@ -11,7 +11,7 @@ import { import { useCallback, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { ConfirmDialog } from '@/components/dialogs'; -import { FileUpload } from '@/components/inputs'; +import { FileUploadMultiple } from '@/components/inputs'; import { ImagePreviewModal } from '@/components/dialogs'; import { InputError } from '@/components/ui'; import { NumberInput } from '@/components/inputs'; @@ -73,8 +73,15 @@ export default function PurchaseEdit({ const [discount, setDiscount] = useState(purchase.discount); const [shippingCost, setShippingCost] = useState(purchase.shipping_cost); const [notes, setNotes] = useState(purchase.notes ?? ''); - const [photo, setPhoto] = useState(purchase.photo_key); - const [photoUrl, setPhotoUrl] = useState(purchase.photo_url); + const [photos, setPhotos] = useState<{ key: string; url: string | null }[]>(() => { + if (purchase.photo_keys && purchase.photo_keys.length > 0) { + return purchase.photo_keys.map((key: string, index: number) => ({ + key, + url: purchase.photo_urls?.[index] ?? getTemporaryUrl(key), + })); + } + return []; + }); const [uploading, setUploading] = useState(false); const [selectedMaterialName, setSelectedMaterialName] = useState( @@ -185,7 +192,7 @@ export default function PurchaseEdit({ discount, shipping_cost: shippingCost, notes: notes || null, - photo_key: photo, + photo_keys: photos.map((p) => p.key), existing_items: Object.entries(quantities) .map(([priceId, quantity]) => ({ raw_material_price_id: Number(priceId), @@ -538,21 +545,15 @@ export default function PurchaseEdit({
- { - setPhoto(key); - const url = key - ? `https://dstpabuaran.s3.ap-southeast-1.amazonaws.com/${key}` - : null; - setPhotoUrl(url); - }} +
diff --git a/resources/js/pages/admin/manage/purchase/purchase-card.tsx b/resources/js/pages/admin/manage/purchase/purchase-card.tsx index f5e39fc..6193b4b 100644 --- a/resources/js/pages/admin/manage/purchase/purchase-card.tsx +++ b/resources/js/pages/admin/manage/purchase/purchase-card.tsx @@ -110,11 +110,11 @@ export function PurchaseCardRow({ - {purchase.photo_url && ( + {purchase.photo_urls && purchase.photo_urls.length > 0 && (