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:
parent
1b48f5c24f
commit
fbdad6923c
@ -17,7 +17,7 @@
|
|||||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
|
||||||
#[Appends(['formatted_name', 'formatted_stock'])]
|
#[Appends(['formatted_name', 'formatted_stock', 'formatted_reject_stock', 'formatted_retail_stock'])]
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[ScopedBy([ProductVariantScope::class])]
|
#[ScopedBy([ProductVariantScope::class])]
|
||||||
class ProductVariant extends Model implements HasMedia
|
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
|
public function orderItems(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(OrderItem::class);
|
return $this->hasMany(OrderItem::class);
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
|
||||||
#[Appends(['formatted_price'])]
|
#[Appends(['formatted_price', 'formatted_stock'])]
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[ScopedBy([RawMaterialPriceScope::class])]
|
#[ScopedBy([RawMaterialPriceScope::class])]
|
||||||
class RawMaterialPrice extends Model implements HasMedia
|
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
|
public function cuttingMaterials(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(CuttingMaterial::class);
|
return $this->hasMany(CuttingMaterial::class);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ type ImagePreviewButtonProps = {
|
|||||||
modalSrc?: string;
|
modalSrc?: string;
|
||||||
modalSrcs?: string[];
|
modalSrcs?: string[];
|
||||||
title?: string;
|
title?: string;
|
||||||
|
description?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@ -15,6 +16,7 @@ export function ImagePreviewButton({
|
|||||||
modalSrc,
|
modalSrc,
|
||||||
modalSrcs,
|
modalSrcs,
|
||||||
title,
|
title,
|
||||||
|
description,
|
||||||
alt,
|
alt,
|
||||||
className = 'h-10 w-10',
|
className = 'h-10 w-10',
|
||||||
}: ImagePreviewButtonProps) {
|
}: ImagePreviewButtonProps) {
|
||||||
@ -46,6 +48,7 @@ export function ImagePreviewButton({
|
|||||||
src={modalSources?.[0] ?? srcs[0]}
|
src={modalSources?.[0] ?? srcs[0]}
|
||||||
sources={modalSources ?? srcs}
|
sources={modalSources ?? srcs}
|
||||||
title={title}
|
title={title}
|
||||||
|
description={description}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
@ -13,6 +14,7 @@ type ImagePreviewModalProps = {
|
|||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
src: string | null;
|
src: string | null;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
description?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
sources?: string[];
|
sources?: string[];
|
||||||
};
|
};
|
||||||
@ -22,6 +24,7 @@ export function ImagePreviewModal({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
src,
|
src,
|
||||||
title,
|
title,
|
||||||
|
description,
|
||||||
alt = 'Preview',
|
alt = 'Preview',
|
||||||
sources,
|
sources,
|
||||||
}: ImagePreviewModalProps) {
|
}: ImagePreviewModalProps) {
|
||||||
@ -55,9 +58,10 @@ setCurrentIndex(0);
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent showCloseButton>
|
<DialogContent showCloseButton>
|
||||||
{title && (
|
{(title || description) && (
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
{title && <DialogTitle>{title}</DialogTitle>}
|
||||||
|
{description && <DialogDescription>{description}</DialogDescription>}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
)}
|
)}
|
||||||
{currentSrc && (
|
{currentSrc && (
|
||||||
|
|||||||
@ -133,7 +133,8 @@ export function CuttingCardRow({
|
|||||||
<ImagePreviewButton
|
<ImagePreviewButton
|
||||||
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
|
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
|
||||||
modalSrc={cutting.photo_url}
|
modalSrc={cutting.photo_url}
|
||||||
title="Foto Cutting"
|
title={productName}
|
||||||
|
description={cutting.description ?? formatDateTime(cutting.created_at)}
|
||||||
className="h-16 w-16"
|
className="h-16 w-16"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -120,7 +120,8 @@ export function PurchaseCardRow({
|
|||||||
<ImagePreviewButton
|
<ImagePreviewButton
|
||||||
srcs={[purchase.photo_conversion_url ?? purchase.photo_url]}
|
srcs={[purchase.photo_conversion_url ?? purchase.photo_url]}
|
||||||
modalSrc={purchase.photo_url}
|
modalSrc={purchase.photo_url}
|
||||||
title="Foto Belanja"
|
title={purchase.supplier.name}
|
||||||
|
description={formatDateTime(purchase.created_at)}
|
||||||
className="h-16 w-16"
|
className="h-16 w-16"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -203,7 +203,8 @@ export function TransactionCardRow({
|
|||||||
<ImagePreviewButton
|
<ImagePreviewButton
|
||||||
srcs={[transaction.photo_conversion_url ?? transaction.photo_url]}
|
srcs={[transaction.photo_conversion_url ?? transaction.photo_url]}
|
||||||
modalSrc={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"
|
className="h-16 w-16"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,9 +11,11 @@ export type ProductVariant = {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
stock: number;
|
stock: number;
|
||||||
formatted_stock: string
|
formatted_stock: string;
|
||||||
reject_stock: number;
|
reject_stock: number;
|
||||||
|
formatted_reject_stock: string;
|
||||||
retail_stock: number;
|
retail_stock: number;
|
||||||
|
formatted_retail_stock: string;
|
||||||
photo_urls: string[];
|
photo_urls: string[];
|
||||||
photo_conversion_urls: string[];
|
photo_conversion_urls: string[];
|
||||||
product_prices: {
|
product_prices: {
|
||||||
|
|||||||
@ -93,6 +93,7 @@ export function VariantSubRow({
|
|||||||
srcs={variant.photo_conversion_urls?.length > 0 ? variant.photo_conversion_urls : variant.photo_urls}
|
srcs={variant.photo_conversion_urls?.length > 0 ? variant.photo_conversion_urls : variant.photo_urls}
|
||||||
modalSrcs={variant.photo_urls}
|
modalSrcs={variant.photo_urls}
|
||||||
title={variant.name}
|
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">
|
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export type RawMaterialVariant = {
|
|||||||
variant: string;
|
variant: string;
|
||||||
price: number;
|
price: number;
|
||||||
stock: number;
|
stock: number;
|
||||||
|
formatted_stock: string;
|
||||||
photo_url: string | null;
|
photo_url: string | null;
|
||||||
photo_conversion_url: string | null;
|
photo_conversion_url: string | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -90,6 +90,7 @@ export function RawMaterialVariantSubRow({
|
|||||||
srcs={[variant.photo_conversion_url ?? variant.photo_url]}
|
srcs={[variant.photo_conversion_url ?? variant.photo_url]}
|
||||||
modalSrc={variant.photo_url}
|
modalSrc={variant.photo_url}
|
||||||
title={variant.variant}
|
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">
|
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user