dstpabuaran.com/resources/js/pages/admin/manage/stok-opname/stok-opname-sub-row.tsx
Yoga Pangestu 05707aad7d feat: add stok opname management functionality
- Implemented StokOpnameEdit component for editing stock opname entries.
- Created StokOpnameIndex component for listing and managing stock opnames.
- Added StokOpnameCardRow and StokOpnameItemSubRow components for displaying stock opname details.
- Introduced routes for stok opname CRUD operations and actions (submit, verify, reject, cancel).
- Integrated UI components for better user interaction and data presentation.
2026-08-16 19:38:36 +07:00

172 lines
8.9 KiB
TypeScript

import { Fragment } from 'react';
import { ImagePreviewButton } from '@/components/dialogs';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { formatNumber } from '@/lib/format';
import type { StokOpname, StokOpnameItem } from './columns';
const STOCK_QUALITY_CONFIG: Record<string, { label: string; className: string }> = {
good: { label: 'Bagus', className: 'bg-green-100 text-green-800' },
reject: { label: 'Reject', className: 'bg-red-100 text-red-800' },
retail: { label: 'Ecer', className: 'bg-blue-100 text-blue-800' },
};
export function StokOpnameItemSubRow({
stokOpname,
items: loadedItems,
isLoading,
}: {
stokOpname: StokOpname;
items: StokOpnameItem[];
isLoading: boolean;
}) {
const items = loadedItems ?? [];
const groupedByProduct = items.reduce(
(acc, item) => {
const productName = item.product_name ?? 'Tanpa Produk';
if (!acc[productName]) {
acc[productName] = {};
}
const variantName = item.variant_name ?? '-';
if (!acc[productName][variantName]) {
acc[productName][variantName] = [];
}
acc[productName][variantName].push(item);
return acc;
},
{} as Record<string, Record<string, typeof items>>,
);
let counter = 0;
return (
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px] text-center">
No
</TableHead>
<TableHead className="w-[60px]">Foto</TableHead>
<TableHead>Varian</TableHead>
<TableHead className="text-center">Jenis</TableHead>
<TableHead className="text-right">Stok Sistem</TableHead>
<TableHead className="text-right">Stok Fisik</TableHead>
<TableHead className="text-right">Selisih</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell
colSpan={7}
className="text-center text-muted-foreground"
>
Memuat item...
</TableCell>
</TableRow>
) : items.length === 0 ? (
<TableRow>
<TableCell
colSpan={7}
className="text-center text-muted-foreground"
>
Tidak ada item.
</TableCell>
</TableRow>
) : (
Object.entries(groupedByProduct).map(
([productName, variants]) => {
const variantEntries = Object.entries(variants);
return (
<Fragment key={productName}>
<TableRow>
<TableCell
colSpan={7}
className="font-semibold text-muted-foreground bg-muted/30"
>
{productName}
</TableCell>
</TableRow>
{variantEntries.map(([variantName, variantItems]) => {
const firstItem = variantItems[0];
return (
<Fragment key={`${productName}-${variantName}`}>
<TableRow>
<TableCell
colSpan={7}
className="font-medium text-muted-foreground"
>
<div className="flex items-center gap-2">
{firstItem?.photo_url ? (
<ImagePreviewButton
srcs={[
firstItem.photo_conversion_url ?? firstItem.photo_url,
]}
modalSrc={firstItem.photo_url}
title={variantName}
/>
) : (
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-muted text-[10px] text-muted-foreground">
N/A
</div>
)}
{variantName}
</div>
</TableCell>
</TableRow>
{variantItems.map((item) => {
counter++;
const qualityConfig = STOCK_QUALITY_CONFIG[item.stock_quality] ?? STOCK_QUALITY_CONFIG.good;
return (
<TableRow key={item.id}>
<TableCell className="text-center">
{counter}
</TableCell>
<TableCell />
<TableCell />
<TableCell className="text-center">
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${qualityConfig.className}`}>
{qualityConfig.label}
</span>
</TableCell>
<TableCell className="text-right">
{formatNumber(item.system_stock)}
</TableCell>
<TableCell className="text-right font-medium">
{formatNumber(item.physical_stock)}
</TableCell>
<TableCell className={`text-right font-medium ${item.difference > 0 ? 'text-green-600' : item.difference < 0 ? 'text-red-600' : ''}`}>
{item.difference > 0 ? '+' : ''}{formatNumber(item.difference)}
</TableCell>
</TableRow>
);
})}
</Fragment>
);
})}
</Fragment>
);
},
)
)}
</TableBody>
</Table>
</div>
);
}