'use client'; import Tabs from '@/components/Tabs'; import React, { useState, useMemo } from 'react'; import { ColumnDef } from '@tanstack/react-table'; import Table, { CustomHeaderRow } from '@/components/Table'; import Card from '@/components/Card'; import Badge from '@/components/Badge'; import { formatCurrency, formatNumber, formatDate } from '@/lib/helper'; import { BaseClosingSales } from '@/types/api/closing/closing'; interface SalesReportTableProps { type?: 'detail'; initialValues?: BaseClosingSales; } const generateCustomHeaders = (template: { groups: Array<{ label: string; field?: string; rowSpan?: number; colSpan?: number; subLabels?: string[]; }>; }): CustomHeaderRow[] => { const mainRow: Array<{ id: string; content: React.ReactNode; colSpan?: number; rowSpan?: number; className: string; }> = []; const subRow: Array<{ id: string; content: React.ReactNode; colSpan?: number; rowSpan?: number; className: string; }> = []; let subColumnIndex = 0; template.groups.forEach((group) => { if (group.subLabels) { mainRow.push({ id: `${group.field || 'group'}-${subColumnIndex}`, content: group.label, colSpan: group.colSpan, className: 'px-4 py-3 text-xs font-semibold text-gray-700 text-center whitespace-nowrap border border-gray-300', }); group.subLabels.forEach((subLabel) => { subRow.push({ id: `sub-${subColumnIndex}`, content: subLabel, className: 'px-4 py-3 text-xs font-semibold text-gray-700 text-left whitespace-nowrap border border-gray-300 border-t-0', }); subColumnIndex++; }); } else { mainRow.push({ id: `${group.field}-header`, content: group.label, rowSpan: group.rowSpan, className: 'px-4 py-3 text-xs font-semibold text-gray-700 text-left whitespace-nowrap border border-gray-300', }); } }); const rows: CustomHeaderRow[] = [ { id: 'main-header', cells: mainRow, className: 'bg-gray-50', }, ]; if (subRow.length > 0) { rows.push({ id: 'sub-header', cells: subRow, className: 'bg-gray-50', }); } return rows; }; // TODO: TEMPORARY - Remove this when backend API returns English field names // eslint-disable-next-line @typescript-eslint/no-explicit-any const mapIndonesianDataToEnglish = (data: any): BaseClosingSales[] => { if (!data || !data.penjualan || !Array.isArray(data.penjualan)) { return []; } // eslint-disable-next-line @typescript-eslint/no-explicit-any return data.penjualan.map((item: any) => ({ id: item.id, realization_date: item.tanggal_realisasi, age_label: item.umur_label, umur_minggu: item.umur_minggu, delivery_order_number: item.no_do, product: item.produk, jenis_produk: item.jenis_produk, customer: item.customer, quantity: item.qty, weight: item.kg, average: item.avg, price: item.harga, total: item.total, kandang: item.kandang, payment_status: item.status_pembayaran, })); }; // END TODO const SalesReportTable = ({ type = 'detail', initialValues, }: SalesReportTableProps) => { const [activeTabId, setActiveTabId] = useState('penjualan'); const salesBroilerData: BaseClosingSales[] = useMemo(() => { if (activeTabId === 'penjualan' && initialValues) { // TODO: TEMPORARY - Remove this when backend API returns English field names // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (initialValues.penjualan && Array.isArray(initialValues.penjualan)) { return mapIndonesianDataToEnglish(initialValues); } // END TODO return [initialValues]; } return []; }, [initialValues, activeTabId]); const totals = useMemo(() => { if (salesBroilerData.length === 0) { return { totalQuantity: 0, totalWeight: 0, avgWeight: 0, avgPricePartner: 0, totalPartner: 0, avgPriceAct: 0, totalAct: 0, }; } const totalQuantity = salesBroilerData.reduce( (sum, item) => sum + (item.quantity || 0), 0 ); const totalWeight = salesBroilerData.reduce( (sum, item) => sum + (item.weight || 0), 0 ); const avgWeight = totalQuantity > 0 ? totalWeight / totalQuantity : 0; const validPriceItems = salesBroilerData.filter( (item) => item.price != null && item.price > 0 ); const avgPricePartner = validPriceItems.length > 0 ? validPriceItems.reduce((sum, item) => sum + item.price, 0) / validPriceItems.length : 0; const totalPartner = salesBroilerData.reduce( (sum, item) => sum + (item.total || 0), 0 ); const avgPriceAct = avgPricePartner; const totalAct = totalPartner; return { totalQuantity, totalWeight, avgWeight, avgPricePartner, totalPartner, avgPriceAct, totalAct, }; }, [salesBroilerData]); const salesColumns: ColumnDef[] = useMemo( () => [ { id: 'realization_date', accessorKey: 'realization_date', header: 'Tanggal Realisasi', cell: (props) => { const date = props.row.original.realization_date; return date ? formatDate(date, 'DD MMM YYYY') : '-'; }, }, { id: 'age_label', accessorKey: 'age_label', header: 'Umur', cell: (props) => props.getValue() || '-', }, { id: 'delivery_order_number', accessorKey: 'delivery_order_number', header: 'No. DO', cell: (props) => props.getValue() || '-', }, { id: 'product', accessorKey: 'product', header: 'Produk', cell: (props) => props.getValue() || '-', }, { id: 'customer', accessorKey: 'customer', header: 'Customer', cell: (props) => props.getValue() || '-', }, { id: 'quantity', accessorKey: 'quantity', header: 'Ekor', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatNumber(value)}
); }, }, { id: 'weight', accessorKey: 'weight', header: 'Kg', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatNumber(value)}
); }, }, { id: 'average', accessorKey: 'average', header: 'AVG (Kg)', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatNumber(value)}
); }, }, { id: 'price_partner', accessorKey: 'price', header: 'Harga Mitra (Rp)', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatCurrency(value)}
); }, }, { id: 'total_mitra', accessorKey: 'total', header: 'Total Mitra (Rp)', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatCurrency(value)}
); }, }, { id: 'price_act', accessorKey: 'price', header: 'Harga Act (Rp)', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatCurrency(value)}
); }, }, { id: 'total_act', accessorKey: 'total', header: 'Total Act (Rp)', cell: (props) => { const value = props.getValue() as number; const isSummary = props.row.id === 'summary'; return (
{formatCurrency(value)}
); }, }, { id: 'kandang', accessorKey: 'kandang', header: 'Kandang', cell: (props) => props.getValue() || '-', }, { id: 'payment_status', accessorKey: 'payment_status', header: 'Status Pembayaran', cell: (props) => { const status = props.getValue() as string; const getStatusColor = (status: string) => { if (!status) return 'neutral'; switch (status.toLowerCase()) { case 'lunas': return 'success'; case 'pending': return 'warning'; case 'belum lunas': return 'error'; default: return 'neutral'; } }; return ( {status || '-'} ); }, }, ], [] ); const headerTemplate = { groups: [ { label: 'Tanggal Realisasi', field: 'realization_date', rowSpan: 2 }, { label: 'Umur', field: 'age_label', rowSpan: 2 }, { label: 'No. DO', field: 'delivery_order_number', rowSpan: 2 }, { label: 'Produk', field: 'product', rowSpan: 2 }, { label: 'Customer', field: 'customer', rowSpan: 2 }, { label: 'Jumlah', colSpan: 2, subLabels: ['Ekor', 'Kg'], }, { label: 'AVG (Kg)', field: 'average', rowSpan: 2 }, { label: 'Harga Mitra (Rp)', field: 'price_partner', rowSpan: 2 }, { label: 'Total Mitra (Rp)', field: 'total_partner', rowSpan: 2 }, { label: 'Harga Act (Rp)', field: 'price_act', rowSpan: 2 }, { label: 'Total Act (Rp)', field: 'total_act', rowSpan: 2 }, { label: 'Kandang', field: 'kandang', rowSpan: 2 }, { label: 'Status Pembayaran', field: 'payment_status', rowSpan: 2 }, ], }; const salesCustomHeaderRows = useMemo( () => generateCustomHeaders(headerTemplate), [] ); return ( <>

Penjualan Ayam Besar

0} footerContent={ } className={{ tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto text-sm', headerRowClassName: 'hidden', bodyRowClassName: 'hover:bg-gray-50 transition-colors', bodyColumnClassName: 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300', }} /> ), }, ]} variant='lifted' /> ); }; export default SalesReportTable;
Total Penjualan - - - - {formatNumber(totals.totalQuantity)} {formatNumber(totals.totalWeight)} {formatNumber(totals.avgWeight)} {formatCurrency(totals.avgPricePartner)} {formatCurrency(totals.totalPartner)} {formatCurrency(totals.avgPriceAct)} {formatCurrency(totals.totalAct)} - -