feat: enhance product and raw material models with additional stock formatting attributes and update image preview components to include descriptions

This commit is contained in:
Yoga Pangestu 2026-08-12 22:14:08 +07:00
parent 1b48f5c24f
commit fbdad6923c
11 changed files with 44 additions and 8 deletions

View File

@ -17,7 +17,7 @@
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
#[Appends(['formatted_name', 'formatted_stock'])]
#[Appends(['formatted_name', 'formatted_stock', 'formatted_reject_stock', 'formatted_retail_stock'])]
#[Guarded(['id'])]
#[ScopedBy([ProductVariantScope::class])]
class ProductVariant extends Model implements HasMedia
@ -47,6 +47,20 @@ protected function formattedStock(): Attribute
);
}
protected function formattedRejectStock(): Attribute
{
return Attribute::make(
get: fn () => number_format($this->reject_stock, 0, ',', '.'),
);
}
protected function formattedRetailStock(): Attribute
{
return Attribute::make(
get: fn () => number_format($this->retail_stock, 0, ',', '.'),
);
}
public function orderItems(): HasMany
{
return $this->hasMany(OrderItem::class);

View File

@ -18,7 +18,7 @@
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
#[Appends(['formatted_price'])]
#[Appends(['formatted_price', 'formatted_stock'])]
#[Guarded(['id'])]
#[ScopedBy([RawMaterialPriceScope::class])]
class RawMaterialPrice extends Model implements HasMedia
@ -40,6 +40,13 @@ protected function formattedPrice(): Attribute
);
}
protected function formattedStock(): Attribute
{
return Attribute::make(
get: fn () => number_format($this->stock, 0, ',', '.'),
);
}
public function cuttingMaterials(): HasMany
{
return $this->hasMany(CuttingMaterial::class);

View File

@ -6,6 +6,7 @@ type ImagePreviewButtonProps = {
modalSrc?: string;
modalSrcs?: string[];
title?: string;
description?: string;
alt?: string;
className?: string;
};
@ -15,6 +16,7 @@ export function ImagePreviewButton({
modalSrc,
modalSrcs,
title,
description,
alt,
className = 'h-10 w-10',
}: ImagePreviewButtonProps) {
@ -46,6 +48,7 @@ export function ImagePreviewButton({
src={modalSources?.[0] ?? srcs[0]}
sources={modalSources ?? srcs}
title={title}
description={description}
/>
</>
);

View File

@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
@ -13,6 +14,7 @@ type ImagePreviewModalProps = {
onOpenChange: (open: boolean) => void;
src: string | null;
title?: string;
description?: string;
alt?: string;
sources?: string[];
};
@ -22,6 +24,7 @@ export function ImagePreviewModal({
onOpenChange,
src,
title,
description,
alt = 'Preview',
sources,
}: ImagePreviewModalProps) {
@ -55,9 +58,10 @@ setCurrentIndex(0);
}}
>
<DialogContent showCloseButton>
{title && (
{(title || description) && (
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
{title && <DialogTitle>{title}</DialogTitle>}
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
)}
{currentSrc && (

View File

@ -133,7 +133,8 @@ export function CuttingCardRow({
<ImagePreviewButton
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
modalSrc={cutting.photo_url}
title="Foto Cutting"
title={productName}
description={cutting.description ?? formatDateTime(cutting.created_at)}
className="h-16 w-16"
/>
</div>

View File

@ -120,7 +120,8 @@ export function PurchaseCardRow({
<ImagePreviewButton
srcs={[purchase.photo_conversion_url ?? purchase.photo_url]}
modalSrc={purchase.photo_url}
title="Foto Belanja"
title={purchase.supplier.name}
description={formatDateTime(purchase.created_at)}
className="h-16 w-16"
/>
</div>

View File

@ -203,7 +203,8 @@ export function TransactionCardRow({
<ImagePreviewButton
srcs={[transaction.photo_conversion_url ?? transaction.photo_url]}
modalSrc={transaction.photo_url}
title="Foto Pesanan"
title={transaction.customer?.name ?? transaction.order_number}
description={transaction.notes ?? formatDateTime(transaction.created_at)}
className="h-16 w-16"
/>
</div>

View File

@ -11,9 +11,11 @@ export type ProductVariant = {
id: number;
name: string;
stock: number;
formatted_stock: string
formatted_stock: string;
reject_stock: number;
formatted_reject_stock: string;
retail_stock: number;
formatted_retail_stock: string;
photo_urls: string[];
photo_conversion_urls: string[];
product_prices: {

View File

@ -93,6 +93,7 @@ export function VariantSubRow({
srcs={variant.photo_conversion_urls?.length > 0 ? variant.photo_conversion_urls : variant.photo_urls}
modalSrcs={variant.photo_urls}
title={variant.name}
description={`Stok Bagus: ${variant.formatted_stock} | Stok Reject: ${variant.formatted_reject_stock} | Stok Ecer: ${variant.formatted_retail_stock}`}
/>
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">

View File

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

View File

@ -90,6 +90,7 @@ export function RawMaterialVariantSubRow({
srcs={[variant.photo_conversion_url ?? variant.photo_url]}
modalSrc={variant.photo_url}
title={variant.variant}
description={`Stok: ${variant.formatted_stock}`}
/>
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">