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'; export type ProductVariant = { id: number; name: string; stock: number; reject_stock: number; retail_stock: number; photo_urls: string[]; product_prices: { id: number; type: string; type_label: string; price: number; }[]; }; export type Product = { id: number; name: string; slug: string; description: string | null; status: string; categories: { id: number; name: string; }[]; product_variants: ProductVariant[]; }; function getStatusLabel(status: string): string { const labels: Record = { active: 'Aktif', inactive: 'Non Aktif', draft: 'Draft', }; return labels[status] ?? status; } function getStatusVariant(status: string): string { const variants: Record = { active: 'bg-green-100 text-green-800', inactive: 'bg-red-100 text-red-800', draft: 'bg-yellow-100 text-yellow-800', }; return variants[status] ?? 'bg-gray-100 text-gray-800'; } function getFilteredVariants( allVariants: ProductVariant[], searchValue: string, ): ProductVariant[] { const query = searchValue.toLowerCase().trim(); return query ? allVariants.filter((v) => v.name.toLowerCase().includes(query)) : allVariants; } type CreateColumnsParams = { handleEdit: (product: Product) => void; handleDeleteClick: (product: Product) => void; handleVariantEdit: (product: Product) => void; handleVariantDeleteClick: ( product: Product, variant: ProductVariant, ) => void; toggleStatusUrl: (id: number) => string; }; export function createProductColumns( params: CreateColumnsParams, ): ColumnDef[] { const { handleEdit, handleDeleteClick, handleVariantEdit, handleVariantDeleteClick, toggleStatusUrl, } = params; return [ { id: 'expand', header: '', cell: ({ row }) => { const hasVariants = (row.original.product_variants?.length ?? 0) > 0; if (!hasVariants) { return null; } return ( ); }, meta: { className: 'w-[40px]', headerClassName: 'w-[40px]', }, }, { id: 'variant_names', accessorFn: (row) => row.product_variants?.map((v) => v.name).join(' ') ?? '', header: () => null, cell: () => null, meta: { className: 'hidden', headerClassName: 'hidden', }, }, { accessorKey: 'name', header: () => Nama Produk, cell: ({ row }) => { const product = row.original; return (
{product.name} {product.categories ?.map((c) => c.name) .join(', ') || '-'}
); }, }, { id: 'variants', header: () => Varian, cell: ({ row, table }) => { const searchValue = (table .getColumn('variant_names') ?.getFilterValue() as string) ?? ''; const variants = getFilteredVariants( row.original.product_variants ?? [], searchValue, ); return ( {variants.length} varian ); }, }, { id: 'stock', header: () => Stok Bagus, meta: { className: 'w-[80px] text-center', headerClassName: 'w-[80px] text-center', }, cell: ({ row, table }) => { const searchValue = (table .getColumn('variant_names') ?.getFilterValue() as string) ?? ''; const variants = getFilteredVariants( row.original.product_variants ?? [], searchValue, ); const totalStock = variants.reduce( (sum, v) => sum + (v.stock ?? 0), 0, ); return ( {formatNumber(totalStock)} ); }, }, { id: 'reject_stock', header: () => ( Stok Reject ), meta: { className: 'w-[80px] text-center', headerClassName: 'w-[80px] text-center', }, cell: ({ row, table }) => { const searchValue = (table .getColumn('variant_names') ?.getFilterValue() as string) ?? ''; const variants = getFilteredVariants( row.original.product_variants ?? [], searchValue, ); const totalReject = variants.reduce( (sum, v) => sum + (v.reject_stock ?? 0), 0, ); return ( {formatNumber(totalReject)} ); }, }, { id: 'retail_stock', header: () => Stok Ecer, meta: { className: 'w-[80px] text-center', headerClassName: 'w-[80px] text-center', }, cell: ({ row, table }) => { const searchValue = (table .getColumn('variant_names') ?.getFilterValue() as string) ?? ''; const variants = getFilteredVariants( row.original.product_variants ?? [], searchValue, ); const totalRetail = variants.reduce( (sum, v) => sum + (v.retail_stock ?? 0), 0, ); return ( {formatNumber(totalRetail)} ); }, }, { id: 'total_stock', header: () => Total Stok, meta: { className: 'w-[80px] text-center', headerClassName: 'w-[80px] text-center', }, cell: ({ row, table }) => { const searchValue = (table .getColumn('variant_names') ?.getFilterValue() as string) ?? ''; const variants = getFilteredVariants( row.original.product_variants ?? [], searchValue, ); const totalStock = variants.reduce( (sum, v) => sum + (v.stock ?? 0), 0, ); const totalReject = variants.reduce( (sum, v) => sum + (v.reject_stock ?? 0), 0, ); const totalRetail = variants.reduce( (sum, v) => sum + (v.retail_stock ?? 0), 0, ); const total = totalStock + totalReject + totalRetail; return ( {formatNumber(total)} ); }, }, { accessorKey: 'status', header: () => Status, cell: ({ row }) => { const product = row.original; const isToggleable = product.status === 'active' || product.status === 'inactive'; const isChecked = product.status === 'active'; function handleToggle(checked: boolean) { router.post( toggleStatusUrl(product.id), {}, { preserveScroll: true, }, ); } if (!isToggleable) { return ( {getStatusLabel(product.status)} ); } return (
{getStatusLabel(product.status)}
); }, }, { id: 'actions', header: () => Aksi, meta: { className: 'w-[100px] text-center', headerClassName: 'w-[100px] text-center', }, cell: ({ row }) => { const product = row.original; return ( , onClick: () => handleEdit(product), }, { label: 'Hapus', icon: ( ), onClick: () => handleDeleteClick(product), }, ]} /> ); }, }, ]; }