diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx new file mode 100644 index 00000000..525d1dcf --- /dev/null +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -0,0 +1,554 @@ +'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'; + +type BaseClosingSales = { + id: number; + realization_date: string; + week_age: number; + age_label: string; + delivery_order_number: string; + product: string; + product_type: string; + customer: string; + quantity: number; + weight: number; + average: number; + price: number; + total: number; + kandang: string; + kandang_id: number; + payment_status: string; +}; + +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; +}; + +const SalesReportTable = () => { + const [activeTabId, setActiveTabId] = useState('penjualan'); + + const salesBroilerData: BaseClosingSales[] = useMemo( + () => [ + { + id: 1, + realization_date: '2025-10-02', + week_age: 3, + age_label: '3 Weeks', + delivery_order_number: 'DO.MBU.699', + product: 'MBU BERLIAN CHICK A', + product_type: 'CHICKEN', + customer: 'TIAN YUSTIAN', + quantity: 1045, + weight: 1000, + average: 0.96, + price: 25300, + total: 25300000, + kandang: 'ACE AWANG', + kandang_id: 1, + payment_status: 'Lunas', + }, + { + id: 2, + realization_date: '2025-10-07', + week_age: 4, + age_label: '4 Weeks', + delivery_order_number: 'DO.MBU.1037', + product: 'MBU BERLIAN CHICK A', + product_type: 'CHICKEN', + customer: 'ZAENAL MUTAQIN', + quantity: 850, + weight: 1211.4, + average: 1.43, + price: 23700, + total: 28710180, + kandang: 'ACE AWANG', + kandang_id: 1, + payment_status: 'Lunas', + }, + { + id: 3, + realization_date: '2025-10-09', + week_age: 4, + age_label: '4 Weeks', + delivery_order_number: 'DO.MBU.1107', + product: 'MBU BERLIAN CHICK A', + product_type: 'CHICKEN', + customer: 'CORNELIUS TONY KUSTANTO', + quantity: 560, + weight: 990, + average: 1.77, + price: 23100, + total: 22869000, + kandang: 'ACE AWANG', + kandang_id: 1, + payment_status: 'Lunas', + }, + { + id: 4, + realization_date: '2025-10-09', + week_age: 0, + age_label: '', + delivery_order_number: 'DO.MBU.1108', + product: 'MBU BERLIAN CHICK A', + product_type: 'CHICKEN', + customer: 'CV. KOPO AB', + quantity: 1088, + weight: 1934.3, + average: 1.78, + price: 23100, + total: 44682330, + kandang: 'ACE AWANG', + kandang_id: 1, + payment_status: 'Lunas', + }, + { + id: 5, + realization_date: '2025-10-09', + week_age: 0, + age_label: '', + delivery_order_number: 'DO.MBU.1110', + product: 'MBU BERLIAN CHICK A', + product_type: 'CHICKEN', + customer: 'H. MAMAN ROMANSAH', + quantity: 624, + weight: 1121.4, + average: 1.8, + price: 22960, + total: 25747344, + kandang: 'ACE AWANG', + kandang_id: 1, + payment_status: 'Lunas', + }, + { + id: 6, + realization_date: '2025-10-09', + week_age: 0, + age_label: '', + delivery_order_number: 'DO.MBU.1133', + product: 'MBU BERLIAN CHICK A', + product_type: 'CHICKEN', + customer: 'PT. SAMUDERA MULIA LESTARI', + quantity: 624, + weight: 1102.3, + average: 1.77, + price: 23100, + total: 25463130, + kandang: 'ACE AWANG', + kandang_id: 1, + payment_status: 'Lunas', + }, + ], + [] + ); + + const totals = useMemo(() => { + 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 + ); + 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 +

+ + + + {/* Summary Row */} +
+ + + + + + + + + + + + + + + + + + +
+ Total Penjualan + + - + + - + + - + + - + + {formatNumber(totals.totalQuantity)} + + {formatNumber(totals.totalWeight)} + + {formatNumber(totals.avgWeight)} + + {formatCurrency(totals.avgPricePartner)} + + {formatCurrency(totals.totalPartner)} + + {formatCurrency(totals.avgPriceAct)} + + {formatCurrency(totals.totalAct)} + + - + + - +
+
+ + ), + }, + ]} + variant='lifted' + /> +
+ + ); +}; + +export default SalesReportTable;