feat: update media handling to use 'images' collection and improve path generation

- Refactored CustomPathGenerator to handle S3 keys and default paths more effectively.
- Changed media collection references from 'photos' to 'images' across various services.
- Updated methods to retrieve media URLs using the new path generation logic.
- Enhanced transaction-related components to display associated images.
This commit is contained in:
Yoga Pangestu 2026-08-12 12:11:29 +07:00
parent 8377c25173
commit e9faa6a322
16 changed files with 201 additions and 161 deletions

View File

@ -9,6 +9,16 @@ class CustomPathGenerator implements PathGenerator
{
public function getPath(Media $media): string
{
$s3Key = $media->getCustomProperty('s3_key');
if ($s3Key) {
return dirname($s3Key).'/';
}
if (str_contains($media->file_name, '/')) {
return '';
}
$model = $media->model;
if ($model && method_exists($model, 'getMediaPath')) {
@ -20,20 +30,44 @@ public function getPath(Media $media): string
public function getPathForConversions(Media $media): string
{
return $this->getPath($media).'conversions/';
$s3Key = $media->getCustomProperty('s3_key');
$dir = $s3Key ? dirname($s3Key).'/' : $this->defaultPath($media);
return $dir.'conversions/';
}
public function getPathForResponsiveImages(Media $media): string
{
return $this->getPath($media).'responsive/';
$s3Key = $media->getCustomProperty('s3_key');
$dir = $s3Key ? dirname($s3Key).'/' : $this->defaultPath($media);
return $dir.'responsive/';
}
private function defaultPath(Media $media): string
{
$module = strtolower(class_basename($media->model_type));
$date = $media->created_at->format('Y/m/d');
$id = $media->id;
$moduleMap = [
'UserProfile' => 'user-profile',
'Attendance' => 'attendance',
'SystemConfiguration' => 'setting',
'HomepageConfiguration' => 'homepage',
'ProductVariant' => 'product',
'Cutting' => 'cutting',
'RawMaterialPrice' => 'raw-material',
'CashTransaction' => 'cash',
'Expense' => 'expense',
'Order' => 'order',
'Restock' => 'restock',
'Purchase' => 'purchase',
'OwnerVerificationRequest' => 'owner_verification_request',
];
return "{$module}/{$date}/{$id}/";
$modelClass = class_basename($media->model_type);
$module = $moduleMap[$modelClass] ?? strtolower($modelClass);
$collection = $media->collection_name;
$date = $media->created_at->format('Y-m-d');
$id = $media->model_id;
return "{$module}/{$collection}/{$date}/{$id}/";
}
}

View File

@ -55,10 +55,10 @@ public function rawMaterial(): BelongsTo
public function getPhotoUrlAttribute(): ?string
{
$media = $this->getFirstMedia('photos');
$media = $this->getFirstMedia('images');
return $media
? app(S3PresignedService::class)->getTemporaryUrl($media->file_name)
? app(S3PresignedService::class)->getTemporaryUrl($media->getPath())
: null;
}
}

View File

@ -56,7 +56,7 @@ public function deposit(array $data): CashTransaction
));
if (array_key_exists('receipt_key', $data)) {
$this->syncReceipt($transaction, $data['receipt_key'] ?? null, 'receipts', 'cash_transaction_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
$this->syncReceipt($transaction, $data['receipt_key'] ?? null, 'photos', 'cash_transaction_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
NotificationService::notify(
@ -78,7 +78,7 @@ public function withdrawal(array $data): CashTransaction
));
if (array_key_exists('receipt_key', $data)) {
$this->syncReceipt($transaction, $data['receipt_key'] ?? null, 'receipts', 'cash_transaction_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
$this->syncReceipt($transaction, $data['receipt_key'] ?? null, 'photos', 'cash_transaction_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
NotificationService::notify(
@ -123,7 +123,7 @@ public function update(CashTransaction $transaction, array $data): CashTransacti
]);
if (array_key_exists('receipt_key', $data)) {
$this->syncReceipt($transaction, $data['receipt_key'] ?? null, 'receipts', 'cash_transaction_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
$this->syncReceipt($transaction, $data['receipt_key'] ?? null, 'photos', 'cash_transaction_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
return $transaction;
@ -163,12 +163,12 @@ public function destroy(CashTransaction $transaction): bool
$cashAccount->update(['balance' => $newBalance]);
$media = $transaction->getFirstMedia('receipts');
$media = $transaction->getFirstMedia('photos');
if ($media) {
Cache::forget("cash_transaction_receipt_{$media->id}");
}
$transaction->clearMediaCollection('receipts');
$transaction->clearMediaCollection('photos');
$deleted = $transaction->delete();
@ -187,7 +187,7 @@ public function destroy(CashTransaction $transaction): bool
private function formatTransaction(CashTransaction $transaction): array
{
$media = $transaction->getFirstMedia('receipts');
$media = $transaction->getFirstMedia('photos');
if (! $media) {
return $transaction->toArray() + [
@ -196,11 +196,7 @@ private function formatTransaction(CashTransaction $transaction): array
];
}
$s3Key = $media->file_name;
if (str_starts_with($s3Key, '/') || ! str_contains($s3Key, '/')) {
$s3Key = $media->getPath();
}
$s3Key = $media->getCustomProperty('s3_key') ?? $media->getPath();
return $transaction->toArray() + [
'receipt_key' => $s3Key,

View File

@ -53,7 +53,7 @@ public function store(array $data): Expense
]);
if (array_key_exists('receipt_key', $data)) {
$this->syncReceipt($expense, $data['receipt_key'] ?? null, 'receipts', 'expense_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
$this->syncReceipt($expense, $data['receipt_key'] ?? null, 'photos', 'expense_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
return $expense;
@ -100,7 +100,7 @@ public function update(Expense $expense, array $data): Expense
]);
if (array_key_exists('receipt_key', $data)) {
$this->syncReceipt($expense, $data['receipt_key'] ?? null, 'receipts', 'expense_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
$this->syncReceipt($expense, $data['receipt_key'] ?? null, 'photos', 'expense_receipt', $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
return $expense;
@ -125,12 +125,12 @@ public function destroy(Expense $expense): bool
type: CashTransactionType::DEPOSIT,
);
$media = $expense->getFirstMedia('receipts');
$media = $expense->getFirstMedia('photos');
if ($media) {
Cache::forget("expense_receipt_{$media->id}");
}
$expense->clearMediaCollection('receipts');
$expense->clearMediaCollection('photos');
$deleted = $expense->delete();
@ -149,7 +149,7 @@ public function destroy(Expense $expense): bool
private function formatExpense(Expense $expense): array
{
$media = $expense->getFirstMedia('receipts');
$media = $expense->getFirstMedia('photos');
if (! $media) {
return $expense->toArray() + [
@ -158,11 +158,7 @@ private function formatExpense(Expense $expense): array
];
}
$s3Key = $media->file_name;
if (str_starts_with($s3Key, '/') || ! str_contains($s3Key, '/')) {
$s3Key = $media->getPath();
}
$s3Key = $media->getCustomProperty('s3_key') ?? $media->getPath();
$cacheKey = "expense_receipt_{$media->id}";
$receiptUrl = Cache::remember($cacheKey, now()->addMinutes(55), function () use ($s3Key) {

View File

@ -168,7 +168,7 @@ public function checkIn(array $data): Attendance
]);
if (! empty($data['photo'])) {
$this->registerMediaFromBase64($attendance, $data['photo'], 'check-in', 'attendances');
$this->registerMediaFromBase64($attendance, $data['photo'], 'checkin', 'attendances');
}
NotificationService::notify(
@ -190,7 +190,7 @@ public function checkOut(Attendance $attendance, array $data): Attendance
]);
if (! empty($data['photo'])) {
$this->registerMediaFromBase64($attendance, $data['photo'], 'check-out', 'attendances');
$this->registerMediaFromBase64($attendance, $data['photo'], 'checkout', 'attendances');
}
NotificationService::notify(
@ -303,15 +303,15 @@ private function formatAttendance(Attendance $attendance): array
$toArray['work_duration_minutes'] = $checkIn->diffInMinutes($end);
}
$checkInMedia = $attendance->getFirstMedia('check-in');
$checkOutMedia = $attendance->getFirstMedia('check-out');
$checkInMedia = $attendance->getFirstMedia('checkin');
$checkOutMedia = $attendance->getFirstMedia('checkout');
$toArray['check_in_photo'] = $checkInMedia
? Cache::remember("attendance_check_in_{$checkInMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkInMedia->file_name))
? Cache::remember("attendance_check_in_{$checkInMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkInMedia->getPath()))
: null;
$toArray['check_out_photo'] = $checkOutMedia
? Cache::remember("attendance_check_out_{$checkOutMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkOutMedia->file_name))
? Cache::remember("attendance_check_out_{$checkOutMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkOutMedia->getPath()))
: null;
return $toArray;

View File

@ -42,16 +42,16 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
->paginate($perPage);
$paginator->getCollection()->each(function (Cutting $cutting) {
$cuttingMedia = $cutting->getFirstMedia('photos');
$cuttingMedia = $cutting->getFirstMedia('images');
$cutting->photo_url = $cuttingMedia
? $this->s3Service->getTemporaryUrl($cuttingMedia->file_name)
? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
: null;
$cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
$media = $material->rawMaterialPrice?->getFirstMedia('photos');
$media = $material->rawMaterialPrice?->getFirstMedia('images');
if ($material->rawMaterialPrice) {
$material->rawMaterialPrice->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
}
});
@ -71,7 +71,7 @@ public function getForEdit(Cutting $cutting): array
$result = $cutting->cuttingResults->first();
$materials = $cutting->cuttingMaterials->map(function (CuttingMaterial $material) {
$media = $material->rawMaterialPrice?->getFirstMedia('photos');
$media = $material->rawMaterialPrice?->getFirstMedia('images');
return [
'id' => $material->id,
@ -81,7 +81,7 @@ public function getForEdit(Cutting $cutting): array
'combination_id' => $material->combination_id,
'variant' => $material->rawMaterialPrice?->variant,
'photo_url' => $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null,
];
});
@ -99,10 +99,10 @@ public function getForEdit(Cutting $cutting): array
];
});
$cuttingMedia = $cutting->getFirstMedia('photos');
$photoKey = $cuttingMedia?->file_name;
$cuttingMedia = $cutting->getFirstMedia('images');
$photoKey = $cuttingMedia?->getCustomProperty('s3_key') ?? $cuttingMedia?->file_name;
$photoUrl = $cuttingMedia
? $this->s3Service->getTemporaryUrl($cuttingMedia->file_name)
? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
: null;
return [
@ -213,7 +213,7 @@ public function store(array $data): Cutting
$this->registerMedia(
model: $cutting,
s3Key: $data['photo_key'],
collectionName: 'photos',
collectionName: 'images',
orderColumn: 1,
);
}
@ -337,7 +337,7 @@ public function destroy(Cutting $cutting): bool
}
}
$cutting->clearMediaCollection('photos');
$cutting->clearMediaCollection('images');
$cutting->cuttingResults()->delete();
$cutting->cuttingMaterials()->delete();
$cutting->cuttingMaterialCombinations()->delete();

View File

@ -47,7 +47,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$paginator->getCollection()->each(function (Purchase $purchase) {
$purchaseMedia = $purchase->getFirstMedia('photos');
$purchase->photo_url = $purchaseMedia
? $this->s3Service->getTemporaryUrl($purchaseMedia->file_name)
? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath())
: null;
$purchase->purchaseItems->each(function (PurchaseItem $item) {
@ -55,9 +55,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
return;
}
$media = $item->rawMaterialPrice->getMedia('photos');
$media = $item->rawMaterialPrice->getMedia('images');
$item->rawMaterialPrice->photo_url = $media->first()
? $this->s3Service->getTemporaryUrl($media->first()->file_name)
? $this->s3Service->getTemporaryUrl($media->first()->getPath())
: null;
});
});
@ -78,9 +78,9 @@ public function getForCreate(): array
->get()
->each(function (RawMaterial $rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
$media = $price->getFirstMedia('photos');
$media = $price->getFirstMedia('images');
$price->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
});
}),
@ -103,15 +103,15 @@ public function getForEdit(Purchase $purchase): array
return null;
}
$media = $item->rawMaterialPrice->getFirstMedia('photos');
$media = $item->rawMaterialPrice->getFirstMedia('images');
return [
'id' => $item->rawMaterialPrice->id,
'variant' => $item->rawMaterialPrice->variant,
'price' => $item->unit_price,
'stock' => $item->quantity,
'photo_key' => $media?->file_name,
'photo_url' => $media ? $this->s3Service->getTemporaryUrl($media->file_name) : null,
'photo_key' => $media?->getCustomProperty('s3_key') ?? $media?->file_name,
'photo_url' => $media ? $this->s3Service->getTemporaryUrl($media->getPath()) : null,
];
})->filter()->values();
@ -129,9 +129,9 @@ public function getForEdit(Purchase $purchase): array
->exists();
$purchaseMedia = $purchase->getFirstMedia('photos');
$purchasePhotoKey = $purchaseMedia?->file_name;
$purchasePhotoKey = $purchaseMedia?->getCustomProperty('s3_key') ?? $purchaseMedia?->file_name;
$purchasePhotoUrl = $purchaseMedia
? $this->s3Service->getTemporaryUrl($purchaseMedia->file_name)
? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath())
: null;
return [
@ -267,7 +267,7 @@ private function storeNew(array $data): Purchase
$this->registerMedia(
model: $priceModel,
s3Key: $variantData['photo_key'],
collectionName: 'photos',
collectionName: 'images',
orderColumn: 1,
);
}
@ -412,11 +412,12 @@ public function update(Purchase $purchase, array $data): Purchase
]);
}
if (! empty($v['photo_key']) && $price->getFirstMedia('photos')?->file_name !== $v['photo_key']) {
if (! empty($v['photo_key']) && $price->getFirstMedia('images')?->getCustomProperty('s3_key') !== $v['photo_key']) {
$price->clearMediaCollection('images');
$this->registerMedia(
model: $price,
s3Key: $v['photo_key'],
collectionName: 'photos',
collectionName: 'images',
orderColumn: 1,
);
}

View File

@ -49,9 +49,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
return;
}
$media = $item->productVariant->getFirstMedia('photos');
$media = $item->productVariant->getFirstMedia('images');
$item->productVariant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
});
});

View File

@ -74,14 +74,19 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$order->channel_label = $order->channel->label();
$order->price_type_label = $order->price_type->label();
$orderMedia = $order->getFirstMedia('photos');
$order->photo_url = $orderMedia
? $this->s3Service->getTemporaryUrl($orderMedia->getPath())
: null;
$order->orderItems->each(function (OrderItem $item) {
if (! $item->productVariant) {
return;
}
$media = $item->productVariant->getFirstMedia('photos');
$media = $item->productVariant->getFirstMedia('images');
$item->productVariant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
});

View File

@ -57,8 +57,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$paginator->getCollection()->each(function ($product) {
$product->productVariants->each(function ($variant) {
$media = $variant->getMedia('photos');
$variant->photo_urls = $media->map(fn($m) => $this->s3Service->getTemporaryUrl($m->file_name))->toArray();
$media = $variant->getMedia('images');
$variant->photo_urls = $media->map(fn($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
});
});
@ -160,9 +160,9 @@ public function getForEdit(Product $product): array
]);
$variants = $product->productVariants->map(function (ProductVariant $variant) {
$media = $variant->getMedia('photos');
$photoKeys = $media->pluck('file_name')->toArray();
$photoUrls = $media->map(fn($m) => $this->s3Service->getTemporaryUrl($m->file_name))->toArray();
$media = $variant->getMedia('images');
$photoKeys = $media->map(fn($m) => $m->getCustomProperty('s3_key') ?? $m->file_name)->toArray();
$photoUrls = $media->map(fn($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
return [
'id' => $variant->id,
@ -222,7 +222,7 @@ public function update(Product $product, array $data): Product
->whereNotIn('id', $existingVariantIds)
->each(function (ProductVariant $variant) {
$variant->productPrices()->delete();
$variant->clearMediaCollection('photos');
$variant->clearMediaCollection('images');
$variant->delete();
});
@ -379,14 +379,7 @@ public function update(Product $product, array $data): Product
continue;
}
// Compare existing photo keys vs new ones
$existingKeys = $variant->getMedia('photos')->pluck('file_name')->sort()->values()->toArray();
$newKeys = collect($variantData['photo_keys'])->sort()->values()->toArray();
if ($existingKeys !== $newKeys) {
$variant->clearMediaCollection('photos');
$this->variantService->registerPhotos($variant, $variantData['photo_keys']);
}
$this->variantService->syncPhotos($variant, $variantData['photo_keys']);
}
return $product;
@ -409,7 +402,7 @@ public function destroy(Product $product): bool
$result = DB::transaction(function () use ($product) {
$product->productVariants->each(function (ProductVariant $variant) {
$variant->productPrices()->delete();
$variant->clearMediaCollection('photos');
$variant->clearMediaCollection('images');
$variant->delete();
});

View File

@ -37,9 +37,9 @@ public function getForRestock(): array
->get()
->each(function (Product $product) {
$product->productVariants->each(function (ProductVariant $variant) {
$media = $variant->getFirstMedia('photos');
$media = $variant->getFirstMedia('images');
$variant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
$capitalPrice = $variant->productPrices
@ -66,9 +66,9 @@ public function getForTransaction(): array
->get()
->each(function (Product $product) {
$product->productVariants->each(function (ProductVariant $variant) {
$media = $variant->getFirstMedia('photos');
$media = $variant->getFirstMedia('images');
$variant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
$prices = $variant->productPrices->mapWithKeys(fn ($p) => [$p->type->value => $p->price]);
@ -84,9 +84,9 @@ public function getForEdit(ProductVariant $variant): array
'media',
]);
$media = $variant->getMedia('photos');
$photoKeys = $media->pluck('file_name')->toArray();
$photoUrls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->file_name))->toArray();
$media = $variant->getMedia('images');
$photoKeys = $media->map(fn ($m) => $m->getCustomProperty('s3_key') ?? $m->file_name)->toArray();
$photoUrls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
return [
'id' => $variant->id,
@ -136,8 +136,7 @@ public function update(ProductVariant $variant, array $data): ProductVariant
);
if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) {
$variant->clearMediaCollection('photos');
$this->registerPhotos($variant, $data['photo_keys']);
$this->syncPhotos($variant, $data['photo_keys']);
}
});
@ -157,7 +156,7 @@ public function destroy(Product $product, ProductVariant $variant): bool
$result = DB::transaction(function () use ($variant) {
$variant->productPrices()->delete();
$variant->clearMediaCollection('photos');
$variant->clearMediaCollection('images');
return $variant->delete();
});
@ -172,25 +171,6 @@ public function destroy(Product $product, ProductVariant $variant): bool
return $result;
}
public function registerPhotos(ProductVariant $variant, array $photoKeys): void
{
foreach ($photoKeys as $order => $s3Key) {
$this->registerMedia(
model: $variant,
s3Key: $s3Key,
collectionName: 'photos',
orderColumn: $order + 1,
);
}
}
public function getTemporaryUrls(ProductVariant $variant): array
{
$media = $variant->getMedia('photos');
return $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->file_name))->toArray();
}
public function transferStock(ProductVariant $variant, array $data): ProductVariant
{
$quantity = (int) $data['quantity'];

View File

@ -39,9 +39,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$paginator->getCollection()->each(function ($rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function ($price) {
$media = $price->getMedia('photos');
$media = $price->getMedia('images');
$price->photo_url = $media->first()
? $this->s3Service->getTemporaryUrl($media->first()->file_name)
? $this->s3Service->getTemporaryUrl($media->first()->getPath())
: null;
});
});
@ -92,19 +92,17 @@ public function getForEdit(RawMaterial $rawMaterial): array
]);
$variants = $rawMaterial->rawMaterialPrices->map(function (RawMaterialPrice $price) {
$media = $price->getMedia('photos');
$photoKey = $media->first()?->file_name;
$photoUrl = $media->first()
? $this->s3Service->getTemporaryUrl($media->first()->file_name)
: null;
$media = $price->getMedia('images');
$photoKeys = $media->map(fn ($m) => $m->getCustomProperty('s3_key') ?? $m->file_name)->toArray();
$photoUrls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
return [
'id' => $price->id,
'variant' => $price->variant,
'price' => $price->price,
'stock' => $price->stock,
'photo_key' => $photoKey,
'photo_url' => $photoUrl,
'photo_key' => $photoKeys[0] ?? null,
'photo_url' => $photoUrls[0] ?? null,
];
});
@ -134,7 +132,7 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
$rawMaterial->rawMaterialPrices()
->whereNotIn('id', $existingVariantIds)
->each(function (RawMaterialPrice $price) {
$price->clearMediaCollection('photos');
$price->clearMediaCollection('images');
$price->delete();
});
@ -184,9 +182,9 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
]);
if (isset($variantData['photo_key'])) {
$existingKey = $priceModel->getMedia('photos')->first()?->file_name;
$existingKey = $priceModel->getMedia('images')->first()?->getCustomProperty('s3_key');
if ($existingKey !== $variantData['photo_key']) {
$priceModel->clearMediaCollection('photos');
$priceModel->clearMediaCollection('images');
if ($variantData['photo_key']) {
$this->registerPhoto($priceModel, $variantData['photo_key']);
}
@ -202,7 +200,7 @@ public function destroy(RawMaterial $rawMaterial): bool
{
return DB::transaction(function () use ($rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
$price->clearMediaCollection('photos');
$price->clearMediaCollection('images');
$price->delete();
});
@ -216,14 +214,4 @@ public function toggleStatus(RawMaterial $rawMaterial): void
'is_active' => ! $rawMaterial->is_active,
]);
}
private function registerPhoto(RawMaterialPrice $price, string $s3Key): void
{
$this->registerMedia(
model: $price,
s3Key: $s3Key,
collectionName: 'photos',
orderColumn: 1,
);
}
}

View File

@ -30,9 +30,9 @@ public function getForCutting(): array
->get()
->each(function (RawMaterial $rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
$media = $price->getFirstMedia('photos');
$media = $price->getFirstMedia('images');
$price->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
});
})
@ -43,11 +43,9 @@ public function getForEdit(RawMaterialPrice $variant): array
{
$variant->load('media');
$media = $variant->getMedia('photos');
$photoKey = $media->first()?->file_name;
$photoUrl = $media->first()
? $this->s3Service->getTemporaryUrl($media->first()->file_name)
: null;
$media = $variant->getMedia('images');
$photoKeys = $media->map(fn ($m) => $m->getCustomProperty('s3_key') ?? $m->file_name)->toArray();
$photoUrls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
return [
'id' => $variant->id,
@ -55,8 +53,8 @@ public function getForEdit(RawMaterialPrice $variant): array
'variant' => $variant->variant,
'price' => $variant->price,
'stock' => $variant->stock,
'photo_key' => $photoKey,
'photo_url' => $photoUrl,
'photo_key' => $photoKeys[0] ?? null,
'photo_url' => $photoUrls[0] ?? null,
];
}
@ -70,11 +68,11 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice
]);
if (array_key_exists('photo_key', $data)) {
$existingKey = $variant->getMedia('photos')->first()?->file_name;
$existingKey = $variant->getMedia('images')->first()?->getCustomProperty('s3_key');
$newKey = $data['photo_key'];
if ($existingKey !== $newKey) {
$variant->clearMediaCollection('photos');
$variant->clearMediaCollection('images');
if ($newKey) {
$this->registerPhoto($variant, $newKey);
}
@ -95,7 +93,7 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice
public function destroy(RawMaterial $rawMaterial, RawMaterialPrice $variant): bool
{
$result = DB::transaction(function () use ($variant) {
$variant->clearMediaCollection('photos');
$variant->clearMediaCollection('images');
return $variant->delete();
});
@ -110,13 +108,4 @@ public function destroy(RawMaterial $rawMaterial, RawMaterialPrice $variant): bo
return $result;
}
private function registerPhoto(RawMaterialPrice $variant, string $s3Key): void
{
$this->registerMedia(
model: $variant,
s3Key: $s3Key,
collectionName: 'photos',
orderColumn: 1,
);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Services\Concerns;
use App\Services\S3PresignedService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
@ -66,21 +67,21 @@ private function registerMedia(
?int $orderColumn = null,
?string $name = null,
): void {
$defaultName = pathinfo($s3Key, PATHINFO_FILENAME);
$fileName = pathinfo($s3Key, PATHINFO_BASENAME);
Media::create([
'model_type' => $model->getMorphClass(),
'model_id' => $model->id,
'uuid' => Str::uuid(),
'collection_name' => $collectionName,
'name' => $name ?? $defaultName,
'file_name' => $s3Key,
'name' => $name ?? pathinfo($s3Key, PATHINFO_FILENAME),
'file_name' => $fileName,
'mime_type' => $mimeType ?? 'image/jpeg',
'disk' => 's3',
'conversions_disk' => 's3',
'size' => $fileSize ?? 0,
'manipulations' => [],
'custom_properties' => [],
'custom_properties' => ['s3_key' => $s3Key],
'generated_conversions' => $generatedConversions,
'responsive_images' => [],
'order_column' => $orderColumn ?? 1,
@ -93,7 +94,7 @@ private function syncPhoto(Model $model, array $data, string $collectionName = '
return;
}
$currentKey = $model->getFirstMedia($collectionName)?->file_name;
$currentKey = $model->getFirstMedia($collectionName)?->getCustomProperty('s3_key');
if ($data['photo_key'] === $currentKey) {
return;
@ -114,11 +115,7 @@ private function syncPhoto(Model $model, array $data, string $collectionName = '
private function syncReceipt(Model $model, ?string $newKey, string $collectionName = 'receipts', string $cachePrefix = 'receipt', ?int $fileSize = null, ?string $fileMimeType = null): void
{
$currentMedia = $model->getFirstMedia($collectionName);
$currentKey = $currentMedia?->file_name;
if ($currentMedia && ! str_contains($currentKey, '/')) {
$currentKey = $currentMedia->getPath();
}
$currentKey = $currentMedia?->getCustomProperty('s3_key');
if ($newKey === $currentKey) {
return;
@ -141,4 +138,53 @@ private function syncReceipt(Model $model, ?string $newKey, string $collectionNa
);
}
}
private function registerPhoto(Model $model, string $s3Key, string $collectionName = 'images'): void
{
$this->registerMedia(
model: $model,
s3Key: $s3Key,
collectionName: $collectionName,
orderColumn: 1,
);
}
public function registerPhotos(Model $model, array $photoKeys, string $collectionName = 'images'): void
{
foreach ($photoKeys as $order => $s3Key) {
$this->registerMedia(
model: $model,
s3Key: $s3Key,
collectionName: $collectionName,
orderColumn: $order + 1,
);
}
}
public function syncPhotos(Model $model, array $photoKeys, string $collectionName = 'images'): void
{
$existingMedia = $model->getMedia($collectionName);
$existingKeys = $existingMedia
->map(fn (Media $m) => $m->getCustomProperty('s3_key') ?? $m->file_name)
->toArray();
foreach ($existingMedia as $media) {
$key = $media->getCustomProperty('s3_key') ?? $media->file_name;
if (! in_array($key, $photoKeys)) {
$media->delete();
}
}
$newKeys = array_values(array_diff($photoKeys, $existingKeys));
$this->registerPhotos($model, $newKeys, $collectionName);
}
public function getTemporaryUrls(Model $model, string $collectionName = 'images'): array
{
$media = $model->getMedia($collectionName);
return $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
}
}

View File

@ -49,6 +49,7 @@ export type Transaction = {
nego_price: number | null;
total_amount: number;
notes: string | null;
photo_url: string | null;
created_at: string;
created_by: {
id: number;

View File

@ -1,4 +1,5 @@
import { CheckCircle, ChevronDown, Pencil, Printer, Send, Trash2, XCircle } from 'lucide-react';
import { ImagePreviewButton } from '@/components/dialogs';
import { RowActions } from '@/components/data-display';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -195,6 +196,16 @@ export function TransactionCardRow({
{formatCurrency(transaction.cogs)}
</span>
</div>
{transaction.photo_url && (
<div className="mt-2">
<ImagePreviewButton
srcs={[transaction.photo_url]}
title="Foto Pesanan"
className="h-16 w-16"
/>
</div>
)}
</div>
<div className="flex shrink-0 items-center gap-1">