feat: add receipt conversion URLs for media handling across various services and components

This commit is contained in:
Yoga Pangestu 2026-08-12 21:35:46 +07:00
parent ef987360f4
commit 5335e5ad58
26 changed files with 107 additions and 21 deletions

View File

@ -193,6 +193,7 @@ private function formatTransaction(CashTransaction $transaction): array
return $transaction->toArray() + [
'receipt_key' => null,
'receipt_url' => null,
'receipt_conversion_url' => null,
];
}
@ -201,6 +202,7 @@ private function formatTransaction(CashTransaction $transaction): array
return $transaction->toArray() + [
'receipt_key' => $s3Key,
'receipt_url' => $this->s3Service->getTemporaryUrl($s3Key),
'receipt_conversion_url' => $this->s3Service->getTemporaryUrl($media->getPath('thumb')),
];
}
}

View File

@ -155,6 +155,7 @@ private function formatExpense(Expense $expense): array
return $expense->toArray() + [
'receipt_key' => null,
'receipt_url' => null,
'receipt_conversion_url' => null,
];
}
@ -165,9 +166,15 @@ private function formatExpense(Expense $expense): array
return $this->s3Service->getTemporaryUrl($s3Key);
});
$conversionCacheKey = "expense_receipt_conversion_{$media->id}";
$receiptConversionUrl = Cache::remember($conversionCacheKey, now()->addMinutes(55), function () use ($media) {
return $this->s3Service->getTemporaryUrl($media->getPath('thumb'));
});
return $expense->toArray() + [
'receipt_key' => $s3Key,
'receipt_url' => $receiptUrl,
'receipt_conversion_url' => $receiptConversionUrl,
];
}
}

View File

@ -46,6 +46,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$cutting->photo_url = $cuttingMedia
? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
: null;
$cutting->photo_conversion_url = $cuttingMedia
? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath('thumb'))
: null;
$cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
$media = $material->rawMaterialPrice?->getFirstMedia('images');
@ -53,6 +56,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$material->rawMaterialPrice->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
$material->rawMaterialPrice->photo_conversion_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
: null;
}
});
});

View File

@ -49,15 +49,21 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$purchase->photo_url = $purchaseMedia
? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath())
: null;
$purchase->photo_conversion_url = $purchaseMedia
? $this->s3Service->getTemporaryUrl($purchaseMedia->getPath('thumb'))
: null;
$purchase->purchaseItems->each(function (PurchaseItem $item) {
if (! $item->rawMaterialPrice) {
return;
}
$media = $item->rawMaterialPrice->getMedia('images');
$item->rawMaterialPrice->photo_url = $media->first()
? $this->s3Service->getTemporaryUrl($media->first()->getPath())
$media = $item->rawMaterialPrice->getFirstMedia('images');
$item->rawMaterialPrice->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
$item->rawMaterialPrice->photo_conversion_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
: null;
});
});

View File

@ -53,6 +53,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$item->productVariant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
$item->productVariant->photo_conversion_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
: null;
});
});

View File

@ -79,6 +79,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$order->photo_url = $orderMedia
? $this->s3Service->getTemporaryUrl($orderMedia->getPath())
: null;
$order->photo_conversion_url = $orderMedia
? $this->s3Service->getTemporaryUrl($orderMedia->getPath('thumb'))
: null;
$order->orderItems->each(function (OrderItem $item) {
if (! $item->productVariant) {
@ -89,6 +92,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$item->productVariant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
$item->productVariant->photo_conversion_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
: null;
});
$order->profit = $order->total_amount - $order->cogs;

View File

@ -60,6 +60,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$product->productVariants->each(function ($variant) {
$media = $variant->getMedia('images');
$variant->photo_urls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
$variant->photo_conversion_urls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath('thumb')))->toArray();
});
});

View File

@ -48,10 +48,14 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$paginator->getCollection()->each(function ($rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function ($price) {
$media = $price->getMedia('images');
$price->photo_url = $media->first()
? $this->s3Service->getTemporaryUrl($media->first()->getPath())
: null;
$media = $price->getFirstMedia('images');
if ($media) {
$price->photo_url = $this->s3Service->getTemporaryUrl($media->getPath());
$price->photo_conversion_url = $this->s3Service->getTemporaryUrl($media->getPath('thumb'));
} else {
$price->photo_url = null;
$price->photo_conversion_url = null;
}
});
});

View File

@ -3,6 +3,8 @@ import { ImagePreviewModal } from '@/components/dialogs';
type ImagePreviewButtonProps = {
srcs: string[];
modalSrc?: string;
modalSrcs?: string[];
title?: string;
alt?: string;
className?: string;
@ -10,12 +12,16 @@ type ImagePreviewButtonProps = {
export function ImagePreviewButton({
srcs,
modalSrc,
modalSrcs,
title,
alt,
className = 'h-10 w-10',
}: ImagePreviewButtonProps) {
const [open, setOpen] = useState(false);
const modalSources = modalSrcs ?? (modalSrc ? [modalSrc] : null);
return (
<>
<button
@ -37,8 +43,8 @@ export function ImagePreviewButton({
<ImagePreviewModal
open={open}
onOpenChange={setOpen}
src={srcs[0]}
sources={srcs}
src={modalSources?.[0] ?? srcs[0]}
sources={modalSources ?? srcs}
title={title}
/>
</>

View File

@ -13,6 +13,7 @@ export type CashTransaction = {
description: string;
receipt_key: string | null;
receipt_url: string | null;
receipt_conversion_url: string | null;
created_at: string;
formatted_created_at: string;
created_by: {
@ -113,15 +114,17 @@ export function createTransactionColumns(
id: 'receipt',
header: () => <span>Bukti</span>,
cell: ({ row }) => {
const receiptConversionUrl = row.original.receipt_conversion_url;
const receiptUrl = row.original.receipt_url;
if (!receiptUrl) {
if (!receiptConversionUrl) {
return <span className="text-muted-foreground">-</span>;
}
return (
<ImagePreviewButton
srcs={[receiptUrl]}
srcs={[receiptConversionUrl]}
modalSrc={receiptUrl ?? undefined}
title={row.original.description}
/>
);

View File

@ -10,6 +10,7 @@ export type Expense = {
description: string;
receipt_key: string | null;
receipt_url: string | null;
receipt_conversion_url: string | null;
created_at: string;
formatted_created_at: string;
created_by: {
@ -51,15 +52,17 @@ export function createExpenseColumns(
id: 'receipt',
header: () => <span>Bukti</span>,
cell: ({ row }) => {
const receiptConversionUrl = row.original.receipt_conversion_url;
const receiptUrl = row.original.receipt_url;
if (!receiptUrl) {
if (!receiptConversionUrl) {
return <span className="text-muted-foreground">-</span>;
}
return (
<ImagePreviewButton
srcs={[receiptUrl]}
srcs={[receiptConversionUrl]}
modalSrc={receiptUrl ?? undefined}
title={row.original.description}
/>
);

View File

@ -28,6 +28,7 @@ export type Cutting = {
total_material_cost: number | null;
cost_per_unit: number | null;
photo_url: string | null;
photo_conversion_url: string | null;
created_at: string;
created_by: {
id: number;
@ -48,6 +49,7 @@ export type Cutting = {
price: number;
stock: number;
photo_url: string | null;
photo_conversion_url: string | null;
raw_material: {
id: number;
name: string;

View File

@ -131,7 +131,8 @@ export function CuttingCardRow({
{cutting.photo_url && (
<div className="mt-2">
<ImagePreviewButton
srcs={[cutting.photo_url]}
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
modalSrc={cutting.photo_url}
title="Foto Cutting"
className="h-16 w-16"
/>

View File

@ -127,9 +127,17 @@ acc[name] = [];
<ImagePreviewButton
srcs={[
item
.raw_material_price
.photo_conversion_url ??
item
.raw_material_price
.photo_url,
]}
modalSrc={
item
.raw_material_price
.photo_url
}
title={
item
.raw_material_price
@ -222,9 +230,17 @@ acc[name] = [];
<ImagePreviewButton
srcs={[
item
.raw_material_price
.photo_conversion_url ??
item
.raw_material_price
.photo_url,
]}
modalSrc={
item
.raw_material_price
.photo_url
}
title={
item
.raw_material_price

View File

@ -17,6 +17,7 @@ export type Purchase = {
total: number;
notes: string | null;
photo_url: string | null;
photo_conversion_url: string | null;
created_at: string;
supplier: {
id: number;
@ -40,6 +41,7 @@ export type Purchase = {
price: number;
stock: number;
photo_url: string | null;
photo_conversion_url: string | null;
raw_material: {
id: number;
name: string;

View File

@ -118,7 +118,8 @@ export function PurchaseCardRow({
{purchase.photo_url && (
<div className="mt-2">
<ImagePreviewButton
srcs={[purchase.photo_url]}
srcs={[purchase.photo_conversion_url ?? purchase.photo_url]}
modalSrc={purchase.photo_url}
title="Foto Belanja"
className="h-16 w-16"
/>

View File

@ -50,9 +50,9 @@ export function PurchaseItemSubRow({ purchase }: { purchase: Purchase }) {
{item.raw_material_price?.photo_url ? (
<ImagePreviewButton
srcs={[
item.raw_material_price
.photo_url,
item.raw_material_price.photo_conversion_url ?? item.raw_material_price.photo_url,
]}
modalSrc={item.raw_material_price.photo_url}
title={
item.raw_material_price.variant
}

View File

@ -10,6 +10,7 @@ export type RestockItem = {
id: number;
name: string;
photo_url: string | null;
photo_conversion_url: string | null;
product: {
id: number;
name: string;

View File

@ -101,9 +101,17 @@ acc[name] = [];
<ImagePreviewButton
srcs={[
item
.product_variant
.photo_conversion_url ??
item
.product_variant
.photo_url,
]}
modalSrc={
item
.product_variant
.photo_url
}
title={
item
.product_variant

View File

@ -19,6 +19,7 @@ export type TransactionItem = {
id: number;
name: string;
photo_url: string | null;
photo_conversion_url: string | null;
product: {
id: number;
name: string;
@ -50,6 +51,7 @@ export type Transaction = {
total_amount: number;
notes: string | null;
photo_url: string | null;
photo_conversion_url: string | null;
created_at: string;
created_by: {
id: number;

View File

@ -201,7 +201,8 @@ export function TransactionCardRow({
{transaction.photo_url && (
<div className="mt-2">
<ImagePreviewButton
srcs={[transaction.photo_url]}
srcs={[transaction.photo_conversion_url ?? transaction.photo_url]}
modalSrc={transaction.photo_url}
title="Foto Pesanan"
className="h-16 w-16"
/>

View File

@ -52,8 +52,9 @@ export function TransactionItemSubRow({ transaction }: { transaction: Transactio
{item.product_variant?.photo_url ? (
<ImagePreviewButton
srcs={[
item.product_variant.photo_url,
item.product_variant.photo_conversion_url ?? item.product_variant.photo_url,
]}
modalSrc={item.product_variant.photo_url}
title={item.product_variant.name}
/>
) : (

View File

@ -15,6 +15,7 @@ export type ProductVariant = {
reject_stock: number;
retail_stock: number;
photo_urls: string[];
photo_conversion_urls: string[];
product_prices: {
id: number;
type: string;

View File

@ -90,7 +90,8 @@ export function VariantSubRow({
<TableCell>
{variant.photo_urls?.length > 0 ? (
<ImagePreviewButton
srcs={variant.photo_urls}
srcs={variant.photo_conversion_urls?.length > 0 ? variant.photo_conversion_urls : variant.photo_urls}
modalSrcs={variant.photo_urls}
title={variant.name}
/>
) : (

View File

@ -4,6 +4,7 @@ export type RawMaterialVariant = {
price: number;
stock: number;
photo_url: string | null;
photo_conversion_url: string | null;
};
export type RawMaterial = {

View File

@ -87,7 +87,8 @@ export function RawMaterialVariantSubRow({
<TableCell>
{variant.photo_url ? (
<ImagePreviewButton
srcs={[variant.photo_url]}
srcs={[variant.photo_conversion_url ?? variant.photo_url]}
modalSrc={variant.photo_url}
title={variant.variant}
/>
) : (