feat: add formatted stock display and enhance product variant attributes
This commit is contained in:
parent
1fc058f974
commit
0a6a441ec4
@ -15,7 +15,7 @@
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
||||
#[Appends(['formatted_name'])]
|
||||
#[Appends(['formatted_name', 'formatted_stock'])]
|
||||
#[Guarded(['id'])]
|
||||
#[ScopedBy([ProductVariantScope::class])]
|
||||
class ProductVariant extends Model implements HasMedia
|
||||
@ -34,7 +34,14 @@ protected function casts(): array
|
||||
protected function formattedName(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => ucfirst($this->name),
|
||||
get: fn() => ucfirst($this->name),
|
||||
);
|
||||
}
|
||||
|
||||
protected function formattedStock(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn() => number_format($this->stock, 0, ',', '.'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Fragment } from 'react';
|
||||
import { formatNumber } from '@/lib/format';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import type { Restock } from './columns';
|
||||
@ -14,8 +15,20 @@ import type { Restock } from './columns';
|
||||
export function RestockItemSubRow({ restock }: { restock: Restock }) {
|
||||
const items = restock.restock_items ?? [];
|
||||
|
||||
const groupedByProduct = items.reduce(
|
||||
(acc, item) => {
|
||||
const name = item.product_variant?.product?.name ?? 'Tanpa Produk';
|
||||
if (!acc[name]) acc[name] = [];
|
||||
acc[name].push(item);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, typeof items>,
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-4 overflow-x-auto">
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@ -23,7 +36,6 @@ export function RestockItemSubRow({ restock }: { restock: Restock }) {
|
||||
No
|
||||
</TableHead>
|
||||
<TableHead className="w-[60px]">Foto</TableHead>
|
||||
<TableHead>Produk</TableHead>
|
||||
<TableHead>Varian</TableHead>
|
||||
<TableHead className="text-right">
|
||||
Harga Modal
|
||||
@ -36,49 +48,94 @@ export function RestockItemSubRow({ restock }: { restock: Restock }) {
|
||||
{items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={7}
|
||||
colSpan={6}
|
||||
className="text-center text-muted-foreground"
|
||||
>
|
||||
Tidak ada item.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
items.map((item, index) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="text-center">
|
||||
{index + 1}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.product_variant?.photo_url ? (
|
||||
<ImagePreviewButton
|
||||
srcs={[
|
||||
item.product_variant.photo_url,
|
||||
]}
|
||||
title={item.product_variant.name}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
||||
N/A
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.product_variant?.product?.name ?? '-'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.product_variant?.name ?? '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatCurrency(item.unit_price)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{formatNumber(item.quantity)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium">
|
||||
{formatCurrency(item.subtotal)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
Object.entries(groupedByProduct).map(
|
||||
([productName, groupItems]) => {
|
||||
const totalQty = groupItems.reduce(
|
||||
(sum, item) => sum + (item.quantity ?? 0),
|
||||
0,
|
||||
);
|
||||
const totalSubtotal = groupItems.reduce(
|
||||
(sum, item) => sum + (item.subtotal ?? 0),
|
||||
0,
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment key={productName}>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={4}
|
||||
className="font-medium text-muted-foreground bg-muted/20"
|
||||
>
|
||||
{productName}
|
||||
</TableCell>
|
||||
<TableCell className="text-center font-medium text-muted-foreground bg-muted/20">
|
||||
{formatNumber(totalQty)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium text-muted-foreground bg-muted/20">
|
||||
{formatCurrency(totalSubtotal)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{groupItems.map((item) => {
|
||||
counter++;
|
||||
return (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="text-center">
|
||||
{counter}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.product_variant
|
||||
?.photo_url ? (
|
||||
<ImagePreviewButton
|
||||
srcs={[
|
||||
item
|
||||
.product_variant
|
||||
.photo_url,
|
||||
]}
|
||||
title={
|
||||
item
|
||||
.product_variant
|
||||
.name
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
||||
N/A
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.product_variant
|
||||
?.name ?? '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatCurrency(
|
||||
item.unit_price,
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{formatNumber(
|
||||
item.quantity,
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium">
|
||||
{formatCurrency(
|
||||
item.subtotal,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { router } from '@inertiajs/react';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
import { ChevronRight, Pencil, Trash2 } from 'lucide-react';
|
||||
import { RowActions } from '@/components/row-actions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { formatNumber } from '@/lib/format';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { router } from '@inertiajs/react';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
import { ChevronRight, Pencil, Trash2 } from 'lucide-react';
|
||||
|
||||
export type ProductVariant = {
|
||||
id: number;
|
||||
name: string;
|
||||
stock: number;
|
||||
formatted_stock: string
|
||||
reject_stock: number;
|
||||
retail_stock: number;
|
||||
photo_urls: string[];
|
||||
|
||||
@ -67,7 +67,7 @@ export function TransferStockDialog({
|
||||
Stok Bagus Tersedia
|
||||
</Label>
|
||||
<p className="text-sm font-medium">
|
||||
{variant.stock}
|
||||
{variant.formatted_stock}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user