diff --git a/src/components/helper/pdf/table/PdfTable.tsx b/src/components/helper/pdf/table/PdfTable.tsx
new file mode 100644
index 00000000..27369db5
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfTable.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { View, StyleSheet } from '@react-pdf/renderer';
+import { PdfThead, PdfColumn } from './PdfThead';
+import { PdfTbody, PdfTbodyCell } from './PdfTbody';
+import { PdfTfoot, PdfTfootCell } from './PdfTfoot';
+
+const styles = StyleSheet.create({
+ table: {
+ borderWidth: 1,
+ borderColor: '#000000',
+ marginBottom: 15,
+ },
+});
+
+interface PdfTableProps {
+ columns: PdfColumn[];
+ data: PdfTbodyCell[][];
+ footer?: PdfTfootCell[];
+ footerLabel?: string;
+ firstRow?: {
+ valueKey: string;
+ value: number;
+ align?: 'right';
+ color?: string;
+ };
+}
+
+export const PdfTable = ({
+ columns,
+ data,
+ footer,
+ footerLabel = 'Total',
+ firstRow,
+}: PdfTableProps) => {
+ return (
+
+
+
+ {footer && footer.length > 0 && (
+
+ )}
+
+ );
+};
diff --git a/src/components/helper/pdf/table/PdfTbody.tsx b/src/components/helper/pdf/table/PdfTbody.tsx
new file mode 100644
index 00000000..fee79726
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfTbody.tsx
@@ -0,0 +1,219 @@
+'use client';
+
+import { Text, View, StyleSheet } from '@react-pdf/renderer';
+
+export interface PdfColumn {
+ key: string;
+ header: string;
+ flex: number;
+ align?: 'left' | 'center' | 'right';
+}
+
+export interface PdfTbodyCell {
+ key: string;
+ value: string | number | React.ReactNode;
+ align?: 'left' | 'center' | 'right';
+ color?: string;
+ formatAs?: 'text' | 'date' | 'currency' | 'number';
+ formatDate?: string;
+}
+
+const styles = StyleSheet.create({
+ tableRow: {
+ flexDirection: 'row',
+ },
+ tableBorderBottom: {
+ borderBottomWidth: 1,
+ borderBottomColor: '#000000',
+ borderBottomStyle: 'solid',
+ },
+ tableCell: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'left',
+ },
+ tableCellLast: {
+ flex: 1,
+ padding: 4,
+ fontSize: 7,
+ borderRightWidth: 0,
+ },
+ tableCellRight: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'right',
+ },
+ tableCellCenter: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'center',
+ },
+ tableCellNo: {
+ flex: 0.5,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'center',
+ },
+});
+
+interface PdfTbodyProps {
+ columns: PdfColumn[];
+ rows: PdfTbodyCell[][];
+ firstRow?: {
+ valueKey: string;
+ value: number;
+ align?: 'right';
+ color?: string;
+ };
+ formatDate?: (date: string, format: string) => string;
+ formatNumber?: (num: number) => string;
+ formatCurrency?: (num: number) => string;
+}
+
+export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
+ return (
+ <>
+ {/* First Row */}
+ {firstRow && (
+
+ {columns.map((column, index) => {
+ const isLastColumn = index === columns.length - 1;
+ const isfirstRowColumn = column.key === firstRow.valueKey;
+ const align = column.align || 'center';
+
+ const cellStyle =
+ column.key === 'no'
+ ? [styles.tableCellNo, { flex: column.flex }]
+ : isfirstRowColumn
+ ? [
+ styles.tableCellRight,
+ {
+ flex: column.flex,
+ color: firstRow.color || 'black',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : align === 'right'
+ ? [
+ styles.tableCellRight,
+ {
+ flex: column.flex,
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : align === 'center'
+ ? [
+ styles.tableCellCenter,
+ {
+ flex: column.flex,
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : isLastColumn
+ ? [
+ styles.tableCellLast,
+ {
+ flex: column.flex,
+ borderRightWidth: 0,
+ },
+ ]
+ : [styles.tableCell, { flex: column.flex }];
+
+ return (
+
+ {isfirstRowColumn ? firstRow.value : ''}
+
+ );
+ })}
+
+ )}
+
+ {/* Data Rows */}
+ {rows.map((row, rowIndex) => {
+ const isLastRow = rowIndex === rows.length - 1;
+
+ return (
+
+ {columns.map((column, colIndex) => {
+ const cell = row.find((c) => c.key === column.key);
+ const isLastColumn = colIndex === columns.length - 1;
+ const align = cell?.align || column.align || 'center';
+
+ const cellStyle =
+ column.key === 'no'
+ ? [styles.tableCellNo, { flex: column.flex }]
+ : align === 'right'
+ ? [
+ styles.tableCellRight,
+ {
+ flex: column.flex,
+ color: cell?.color || 'black',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : align === 'center'
+ ? [
+ styles.tableCellCenter,
+ {
+ flex: column.flex,
+ color: cell?.color || 'black',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : isLastColumn
+ ? [
+ styles.tableCellLast,
+ { flex: column.flex, borderRightWidth: 0 },
+ ]
+ : [
+ styles.tableCell,
+ {
+ flex: column.flex,
+ color: cell?.color || 'black',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ];
+
+ return (
+
+ {cell?.value !== undefined &&
+ cell?.value !== null &&
+ cell?.value !== '' ? (
+ typeof cell.value === 'object' ? (
+ cell.value
+ ) : (
+ {String(cell.value)}
+ )
+ ) : (
+ -
+ )}
+
+ );
+ })}
+
+ );
+ })}
+ >
+ );
+};
diff --git a/src/components/helper/pdf/table/PdfTfoot.tsx b/src/components/helper/pdf/table/PdfTfoot.tsx
new file mode 100644
index 00000000..a9f209b1
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfTfoot.tsx
@@ -0,0 +1,131 @@
+'use client';
+
+import { Text, View, StyleSheet } from '@react-pdf/renderer';
+
+export interface PdfColumn {
+ key: string;
+ header: string;
+ flex: number;
+ align?: 'left' | 'center' | 'right';
+}
+
+export interface PdfTfootCell {
+ key: string;
+ value: string | number;
+ align?: 'left' | 'center' | 'right';
+ flex?: number;
+ color?: string;
+}
+
+const styles = StyleSheet.create({
+ tableRow: {
+ flexDirection: 'row',
+ },
+ summaryRow: {
+ backgroundColor: '#F0F0F0',
+ fontWeight: 'bold',
+ },
+ tableCell: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'left',
+ },
+ tableCellLast: {
+ flex: 1,
+ padding: 4,
+ fontSize: 7,
+ borderRightWidth: 0,
+ },
+ tableCellRight: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'right',
+ },
+ tableCellCenter: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'center',
+ },
+ tableCellNo: {
+ flex: 0.5,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ textAlign: 'center',
+ },
+});
+
+interface PdfTfootProps {
+ columns: PdfColumn[];
+ cells: PdfTfootCell[];
+ label?: string;
+}
+
+export const PdfTfoot = ({
+ columns,
+ cells,
+ label = 'Total',
+}: PdfTfootProps) => {
+ return (
+
+ {columns.map((column, index) => {
+ const isLastColumn = index === columns.length - 1;
+ const cellData = cells.find((c) => c.key === column.key);
+
+ const cellStyle =
+ column.key === 'no'
+ ? [
+ styles.tableCellNo,
+ { flex: column.flex, borderRightWidth: isLastColumn ? 0 : 1 },
+ ]
+ : cellData?.align === 'right'
+ ? [
+ styles.tableCellRight,
+ {
+ flex: column.flex,
+ color: cellData?.color || 'black',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : cellData?.align === 'center'
+ ? [
+ styles.tableCellCenter,
+ {
+ flex: column.flex,
+ color: cellData?.color || 'black',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : isLastColumn
+ ? [styles.tableCellLast, { flex: column.flex }]
+ : [
+ styles.tableCell,
+ {
+ flex: column.flex,
+ color: cellData?.color || 'black',
+ },
+ ];
+
+ return (
+
+ {column.key === 'no' ? label : cellData?.value || ''}
+
+ );
+ })}
+
+ );
+};
diff --git a/src/components/helper/pdf/table/PdfThead.tsx b/src/components/helper/pdf/table/PdfThead.tsx
new file mode 100644
index 00000000..89037216
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfThead.tsx
@@ -0,0 +1,89 @@
+'use client';
+
+import { Text, View, StyleSheet } from '@react-pdf/renderer';
+
+export interface PdfColumn {
+ key: string;
+ header: string;
+ flex: number;
+ align?: 'left' | 'center' | 'right';
+}
+
+const styles = StyleSheet.create({
+ tableRow: {
+ flexDirection: 'row',
+ },
+ tableHeader: {
+ backgroundColor: '#F5F5F5',
+ },
+ tableCellHeader: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ fontWeight: 'bold',
+ backgroundColor: '#F5F5F5',
+ borderBottomWidth: 1,
+ borderBottomColor: '#000000',
+ borderBottomStyle: 'solid',
+ paddingVertical: 12,
+ textAlign: 'center',
+ },
+ tableCellHeaderRight: {
+ flex: 1,
+ borderRightWidth: 1,
+ borderRightColor: '#000000',
+ borderRightStyle: 'solid',
+ padding: 4,
+ fontSize: 7,
+ fontWeight: 'bold',
+ backgroundColor: '#F5F5F5',
+ textAlign: 'right',
+ borderBottomWidth: 1,
+ borderBottomColor: '#000000',
+ borderBottomStyle: 'solid',
+ paddingVertical: 12,
+ },
+});
+
+interface PdfTheadProps {
+ columns: PdfColumn[];
+}
+
+export const PdfThead = ({ columns }: PdfTheadProps) => {
+ return (
+
+ {columns.map((column, index) => {
+ const align = column.align || 'center';
+ const isLastColumn = index === columns.length - 1;
+
+ const cellStyle =
+ align === 'right'
+ ? [
+ styles.tableCellHeaderRight,
+ {
+ flex: column.flex,
+ textAlign: 'right' as const,
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ]
+ : [
+ styles.tableCellHeader,
+ {
+ flex: column.flex,
+ textAlign: align as 'left' | 'center' | 'right',
+ borderRightWidth: isLastColumn ? 0 : 1,
+ },
+ ];
+
+ return (
+
+ {column.header}
+
+ );
+ })}
+
+ );
+};
diff --git a/src/components/helper/pdf/table/index.ts b/src/components/helper/pdf/table/index.ts
new file mode 100644
index 00000000..35839f17
--- /dev/null
+++ b/src/components/helper/pdf/table/index.ts
@@ -0,0 +1,7 @@
+export { PdfTable } from './PdfTable';
+export { PdfThead } from './PdfThead';
+export { PdfTbody } from './PdfTbody';
+export { PdfTfoot } from './PdfTfoot';
+export type { PdfColumn } from './PdfThead';
+export type { PdfTbodyCell } from './PdfTbody';
+export type { PdfTfootCell } from './PdfTfoot';
diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
index d4f6587a..e6c5d66e 100644
--- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
+++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
@@ -12,6 +12,12 @@ import {
import { formatDate, formatCurrency, formatNumber } from '@/lib/helper';
import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
+import {
+ PdfTable,
+ PdfColumn,
+ PdfTbodyCell,
+ PdfTfootCell,
+} from '@/components/helper/pdf/table';
Font.register({
family: 'Helvetica',
@@ -45,97 +51,6 @@ const pdfStyles = StyleSheet.create({
marginBottom: 5,
color: '#333333',
},
- table: {
- borderWidth: 1,
- borderColor: '#000000',
- marginBottom: 15,
- },
- tableRow: {
- flexDirection: 'row',
- },
- tableHeader: {
- backgroundColor: '#F5F5F5',
- },
- tableCell: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 7,
- textAlign: 'left',
- },
- tableCellNo: {
- flex: 0.5,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 7,
- textAlign: 'center',
- },
- tableCellLast: {
- flex: 1,
- padding: 4,
- fontSize: 7,
- },
- tableCellHeader: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 7,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- textAlign: 'center',
- },
- tableCellHeaderRight: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 7,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- textAlign: 'right',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- },
- tableCellRight: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 7,
- textAlign: 'right',
- },
- tableCellCenter: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 7,
- textAlign: 'center',
- },
- tableBorderBottom: {
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- },
- summaryRow: {
- backgroundColor: '#F0F0F0',
- fontWeight: 'bold',
- },
badge: {
backgroundColor: '#1f74bf',
color: '#FFFFFF',
@@ -217,6 +132,165 @@ const getParameterText = (
return paramsText;
};
+// Helper functions for PdfTable
+const getTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'trans_date', header: 'Tanggal DO', flex: 1.2, align: 'center' },
+ {
+ key: 'delivery_date',
+ header: 'Tanggal Realisasi',
+ flex: 1.2,
+ align: 'center',
+ },
+ { key: 'aging', header: 'Aging', flex: 0.8, align: 'center' },
+ { key: 'reference', header: 'Referensi', flex: 1.5, align: 'left' },
+ { key: 'vehicle_numbers', header: 'No Polisi', flex: 1.2, align: 'left' },
+ { key: 'qty', header: 'Qty', flex: 0.8, align: 'right' },
+ { key: 'weight', header: 'Berat', flex: 1, align: 'right' },
+ { key: 'average_weight', header: 'Rata-Rata', flex: 0.8, align: 'right' },
+ { key: 'unit_price', header: 'Harga/Unit', flex: 1.2, align: 'right' },
+ { key: 'final_price', header: 'Harga Akhir', flex: 1.2, align: 'right' },
+ { key: 'total_price', header: 'Total', flex: 1.2, align: 'right' },
+ { key: 'payment_amount', header: 'Pembayaran', flex: 1.2, align: 'right' },
+ { key: 'accounts_receivable', header: 'Saldo', flex: 1.2, align: 'right' },
+ { key: 'status', header: 'Keterangan', flex: 1.5, align: 'center' },
+ { key: 'pickup_info', header: 'Pengambilan', flex: 1, align: 'left' },
+ { key: 'sales_person', header: 'Sales', flex: 1.5, align: 'left' },
+];
+
+const getTableData = (
+ rows: CustomerPaymentReport['rows']
+): PdfTbodyCell[][] => {
+ return rows.map((item, index) => [
+ { key: 'no', value: index + 1 },
+ {
+ key: 'trans_date',
+ value: item.trans_date ? formatDate(item.trans_date, 'DD MMM YY') : '-',
+ },
+ {
+ key: 'delivery_date',
+ value: item.delivery_date
+ ? formatDate(item.delivery_date, 'DD MMM YY')
+ : '-',
+ },
+ {
+ key: 'aging',
+ value:
+ item.aging_day != null ? `${formatNumber(item.aging_day)} hari` : '-',
+ },
+ { key: 'reference', value: item.reference || '-' },
+ {
+ key: 'vehicle_numbers',
+ value:
+ Array.isArray(item.vehicle_numbers) && item.vehicle_numbers.length > 0
+ ? item.vehicle_numbers.join(', ')
+ : '-',
+ },
+ { key: 'qty', value: formatNumber(item.qty), align: 'right' },
+ { key: 'weight', value: formatNumber(item.weight), align: 'right' },
+ {
+ key: 'average_weight',
+ value: formatNumber(item.average_weight),
+ align: 'right',
+ },
+ {
+ key: 'unit_price',
+ value: formatCurrency(item.unit_price),
+ align: 'right',
+ },
+ {
+ key: 'final_price',
+ value: formatCurrency(item.final_price),
+ align: 'right',
+ },
+ {
+ key: 'total_price',
+ value: formatCurrency(item.total_price),
+ align: 'right',
+ },
+ {
+ key: 'payment_amount',
+ value: formatCurrency(item.payment_amount),
+ align: 'right',
+ },
+ {
+ key: 'accounts_receivable',
+ value: formatCurrency(item.accounts_receivable),
+ align: 'right',
+ color: item.accounts_receivable < 0 ? '#DC2626' : undefined,
+ },
+ {
+ key: 'status',
+ value: item.status ? (
+
+ {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
+
+ ) : (
+ '-'
+ ),
+ },
+ {
+ key: 'pickup_info',
+ value:
+ Array.isArray(item.pickup_info) && item.pickup_info.length > 0
+ ? item.pickup_info.join(', ')
+ : '-',
+ },
+ { key: 'sales_person', value: item.sales_person || '-' },
+ ]);
+};
+
+const getTableFooter = (
+ summary: CustomerPaymentReport['summary']
+): PdfTfootCell[] => [
+ { key: 'no', value: 'Total' },
+ { key: 'trans_date', value: '' },
+ { key: 'delivery_date', value: '' },
+ { key: 'aging', value: '' },
+ { key: 'reference', value: '' },
+ { key: 'vehicle_numbers', value: '' },
+ { key: 'qty', value: formatNumber(summary?.total_qty || 0), align: 'right' },
+ {
+ key: 'weight',
+ value: formatNumber(summary?.total_weight || 0),
+ align: 'right',
+ },
+ { key: 'average_weight', value: '' },
+ { key: 'unit_price', value: '' },
+ {
+ key: 'final_price',
+ value: formatCurrency(summary?.total_final_amount || 0),
+ align: 'right',
+ },
+ {
+ key: 'total_price',
+ value: formatCurrency(summary?.total_grand_amount || 0),
+ align: 'right',
+ },
+ {
+ key: 'payment_amount',
+ value: formatCurrency(summary?.total_payment || 0),
+ align: 'right',
+ },
+ {
+ key: 'accounts_receivable',
+ value: formatCurrency(summary?.total_accounts_receivable || 0),
+ align: 'right',
+ color:
+ (summary?.total_accounts_receivable || 0) < 0 ? '#DC2626' : undefined,
+ },
+ { key: 'status', value: '' },
+ { key: 'pickup_info', value: '' },
+ { key: 'sales_person', value: '' },
+];
+
const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
return (
@@ -269,329 +343,27 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{/* Table */}
-
- {/* Table Header */}
-
-
- No
-
-
- Tanggal DO
-
-
- Tanggal Realisasi
-
-
- Aging
-
-
- Referensi
-
-
- No Polisi
-
-
- Qty
-
-
- Berat
-
-
- Rata-Rata
-
-
- Harga/Unit
-
-
- Harga Akhir
-
-
- Total
-
-
- Pembayaran
-
-
- Saldo
-
-
- Keterangan
-
-
- Pengambilan
-
-
- Sales
-
-
-
- {/* Table Body */}
- <>
- {/* Initial Balance Row */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {formatCurrency(customerReport.initial_balance || 0)}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Data Rows */}
- {customerReport.rows.map((item, index) => (
-
-
- {index + 1}
-
-
-
- {item.trans_date
- ? formatDate(item.trans_date, 'DD MMM YY')
- : '-'}
-
-
-
-
- {item.delivery_date
- ? formatDate(item.delivery_date, 'DD MMM YY')
- : '-'}
-
-
-
-
- {item.aging_day != null
- ? `${formatNumber(item.aging_day)} hari`
- : '-'}
-
-
-
- {item.reference || '-'}
-
-
-
- {Array.isArray(item.vehicle_numbers)
- ? item.vehicle_numbers.length > 0
- ? item.vehicle_numbers.join(', ')
- : '-'
- : '-'}
-
-
-
- {formatNumber(item.qty)}
-
-
- {formatNumber(item.weight)}
-
-
- {formatNumber(item.average_weight)}
-
-
- {formatCurrency(item.unit_price)}
-
-
- {formatCurrency(item.final_price)}
-
-
- {formatCurrency(item.total_price)}
-
-
- {formatCurrency(item.payment_amount)}
-
-
-
- {formatCurrency(item.accounts_receivable)}
-
-
-
- {item.status ? (
-
-
- {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
-
-
- ) : (
- -
- )}
-
-
-
- {Array.isArray(item.pickup_info)
- ? item.pickup_info.length > 0
- ? item.pickup_info.join(', ')
- : '-'
- : '-'}
-
-
-
- {item.sales_person || '-'}
-
-
- ))}
- >
-
- {/* Summary Row */}
- {customerReport.summary && (
-
-
- Total
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {formatNumber(customerReport.summary.total_qty)}
-
-
-
- {formatNumber(customerReport.summary.total_weight)}
-
-
-
-
-
-
-
-
-
-
- {formatCurrency(customerReport.summary.total_final_amount)}
-
-
-
-
- {formatCurrency(customerReport.summary.total_grand_amount)}
-
-
-
-
- {formatCurrency(customerReport.summary.total_payment)}
-
-
-
-
- {formatCurrency(
- customerReport.summary.total_accounts_receivable
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
+
))}
diff --git a/src/components/pages/report/logistic-stock/export/PurchasesPerSupplierExport.tsx b/src/components/pages/report/logistic-stock/export/PurchasesPerSupplierExport.tsx
index a7967159..bd6f301a 100644
--- a/src/components/pages/report/logistic-stock/export/PurchasesPerSupplierExport.tsx
+++ b/src/components/pages/report/logistic-stock/export/PurchasesPerSupplierExport.tsx
@@ -11,6 +11,11 @@ import {
} from '@react-pdf/renderer';
import { LogisticPurchasePerSupplierReport } from '@/types/api/report/logistic-stock';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
+import {
+ PdfTable,
+ PdfColumn,
+ PdfTbodyCell,
+} from '@/components/helper/pdf/table';
Font.register({
family: 'Helvetica',
@@ -39,117 +44,6 @@ const pdfStyles = StyleSheet.create({
marginBottom: 8,
color: '#1f74bf',
},
- table: {
- borderWidth: 1,
- borderColor: '#000000',
- marginBottom: 15,
- },
- tableRow: {
- flexDirection: 'row',
- },
- tableHeader: {
- backgroundColor: '#F5F5F5',
- },
- tableCell: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'left',
- },
- tableCellNo: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'center',
- },
- tableCellLast: {
- flex: 1,
- padding: 4,
- fontSize: 8,
- },
- tableCellHeader: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- textAlign: 'center',
- },
- tableCellHeaderRight: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- textAlign: 'right',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- },
- tableCellHeaderLast: {
- flex: 1,
- padding: 4,
- fontSize: 8,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- textAlign: 'center',
- },
- tableCellRight: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'right',
- },
- tableCellCenter: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'center',
- },
- tableCellCenterLast: {
- flex: 1,
- padding: 4,
- fontSize: 8,
- textAlign: 'center',
- },
- tableBorderBottom: {
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- },
- supplierSection: {
- marginBottom: 10,
- },
- supplierSectionBreak: {
- marginBottom: 15,
- },
badge: {
backgroundColor: '#1f74bf',
color: '#FFFFFF',
@@ -174,6 +68,12 @@ const pdfStyles = StyleSheet.create({
flexWrap: 'wrap',
marginBottom: 8,
},
+ supplierSection: {
+ marginBottom: 10,
+ },
+ supplierSectionBreak: {
+ marginBottom: 15,
+ },
});
interface PurchasesPerSupplierExportParams {
@@ -218,6 +118,85 @@ const getParameterText = (
return paramsText;
};
+// Helper functions for PdfTable
+const getTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'receive_date', header: 'Tanggal Terima', flex: 1, align: 'center' },
+ { key: 'po_date', header: 'Tanggal PO', flex: 1, align: 'center' },
+ { key: 'po_number', header: 'Referensi', flex: 1, align: 'left' },
+ { key: 'product', header: 'Produk', flex: 1, align: 'left' },
+ { key: 'warehouse', header: 'Tujuan', flex: 1, align: 'left' },
+ { key: 'qty', header: 'Qty', flex: 0.8, align: 'right' },
+ { key: 'unit_price', header: 'Harga Beli', flex: 1.2, align: 'right' },
+ {
+ key: 'purchase_value',
+ header: 'Nilai Pembelian',
+ flex: 1.5,
+ align: 'right',
+ },
+ {
+ key: 'transport_price',
+ header: 'Biaya Transport',
+ flex: 1.2,
+ align: 'right',
+ },
+ { key: 'total_amount', header: 'Total', flex: 1.5, align: 'right' },
+ { key: 'expedition', header: 'Armada', flex: 1.2, align: 'center' },
+ { key: 'delivery_number', header: 'Surat Jalan', flex: 1, align: 'left' },
+];
+
+const getTableData = (
+ rows: LogisticPurchasePerSupplierReport['rows']
+): PdfTbodyCell[][] => {
+ return rows.map((item, index) => [
+ { key: 'no', value: index + 1, align: 'center' },
+ {
+ key: 'receive_date',
+ value: formatDate(item.receive_date, 'DD-MMM-YYYY'),
+ align: 'center',
+ },
+ {
+ key: 'po_date',
+ value: formatDate(item.po_date, 'DD-MMM-YYYY'),
+ align: 'center',
+ },
+ { key: 'po_number', value: item.po_number || '-' },
+ { key: 'product', value: item.product?.name || '-' },
+ { key: 'warehouse', value: item.warehouse?.name || '-' },
+ { key: 'qty', value: formatNumber(item.qty || 0), align: 'right' },
+ {
+ key: 'unit_price',
+ value: formatCurrency(item.unit_price || 0),
+ align: 'right',
+ },
+ {
+ key: 'purchase_value',
+ value: formatCurrency(item.purchase_value || 0),
+ align: 'right',
+ },
+ {
+ key: 'transport_price',
+ value: formatCurrency(item.transport_unit_price || 0),
+ align: 'right',
+ },
+ {
+ key: 'total_amount',
+ value: formatCurrency(item.total_amount || 0),
+ align: 'right',
+ },
+ {
+ key: 'expedition',
+ value: (
+
+ {item.expedition || '-'}
+
+ ),
+ align: 'center',
+ },
+ { key: 'delivery_number', value: item.delivery_number || '-' },
+ ]);
+};
+
const createPDFDocument = (
supplierReports: LogisticPurchasePerSupplierReport[],
params: PurchasesPerSupplierExportParams['params']
@@ -266,114 +245,10 @@ const createPDFDocument = (
{supplierReport.supplier.name}
-
- {/* Table Header */}
-
-
- No
-
-
- Tanggal Terima
-
-
- Tanggal PO
-
-
- Referensi
-
-
- Produk
-
-
- Tujuan
-
-
- Qty
-
-
- Harga Beli
-
-
- Nilai Pembelian
-
-
- Biaya Transport
-
-
- Total
-
-
- Armada
-
-
- Surat Jalan
-
-
-
- {/* Table Body */}
- {supplierReport.rows.map(
- (
- item: LogisticPurchasePerSupplierReport['rows'][number],
- index: number
- ) => (
-
-
- {index + 1}
-
-
-
- {formatDate(item.receive_date, 'DD-MMM-YYYY')}
-
-
-
- {formatDate(item.po_date, 'DD-MMM-YYYY')}
-
-
- {item.po_number || '-'}
-
-
- {item.product?.name || '-'}
-
-
- {item.warehouse?.name || '-'}
-
-
- {formatNumber(item.qty || 0)}
-
-
- {formatCurrency(item.unit_price || 0)}
-
-
- {formatCurrency(item.purchase_value || 0)}
-
-
-
- {formatCurrency(item.transport_unit_price || 0)}
-
-
-
- {formatCurrency(item.total_amount || 0)}
-
-
-
- {item.expedition || '-'}
-
-
-
- {item.delivery_number || '-'}
-
-
- )
- )}
-
+
);
}
diff --git a/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx b/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx
index c01eeb61..5366f3cd 100644
--- a/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx
+++ b/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx
@@ -29,6 +29,7 @@ import Menu from '@/components/menu/Menu';
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExport';
import toast from 'react-hot-toast';
import * as XLSX from 'xlsx';
+import { Icon } from '@iconify/react';
const PurchasesPerSupplierTab = () => {
// ===== STATE MANAGEMENT =====
@@ -723,27 +724,6 @@ const PurchasesPerSupplierTab = () => {
subtitle='Laporan > Rekapitulasi Pembelian Per Supplier'
className={{ wrapper: 'w-full', body: 'p-1!' }}
>
-
-
-
-
- Export
-
- }
- align='end'
- >
-
-
-
{
/>
+
+
+
+
+ Export
+
+
+ }
+ align='end'
+ >
+
+
+
{!isSubmitted ? (
@@ -880,18 +888,25 @@ const PurchasesPerSupplierTab = () => {
key={supplierReport.supplier.id}
title={supplierReport.supplier.name}
subtitle={`Total Pembelian: ${formatCurrency(totalPurchase)}`}
- className={{ wrapper: 'w-full' }}
+ className={{
+ wrapper: 'w-full rounded-2xl',
+ body: 'p-0',
+ title:
+ 'py-1.5 px-3 bg-primary text-white text-lg font-normal',
+ subtitle:
+ 'px-3 pb-1 bg-primary text-white text-sm font-normal',
+ }}
variant='bordered'
collapsible={true}
>
0}
className={{
- containerClassName: 'w-full',
- tableWrapperClassName: 'overflow-x-auto mt-4',
+ containerClassName: 'w-full mb-0!',
+ tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx
index 9b05a88d..3a76d8f4 100644
--- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx
+++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx
@@ -15,6 +15,12 @@ import {
HppPerKandangPerWeightRange,
} from '@/types/api/report/hpp-per-kandang';
import { formatDate, formatNumber, formatCurrency } from '@/lib/helper';
+import {
+ PdfTable,
+ PdfColumn,
+ PdfTbodyCell,
+ PdfTfootCell,
+} from '@/components/helper/pdf/table';
Font.register({
family: 'Helvetica',
@@ -43,85 +49,6 @@ const pdfStyles = StyleSheet.create({
marginBottom: 8,
color: '#1f74bf',
},
- table: {
- borderWidth: 1,
- borderColor: '#000000',
- marginBottom: 15,
- },
- tableRow: {
- flexDirection: 'row',
- },
- tableHeader: {
- backgroundColor: '#F5F5F5',
- },
- tableCell: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'left',
- },
- tableCellHeader: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- textAlign: 'center',
- },
- tableCellHeaderRight: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- fontWeight: 'bold',
- backgroundColor: '#F5F5F5',
- textAlign: 'right',
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- paddingVertical: 12,
- },
- tableCellRight: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'right',
- },
- tableCellCenter: {
- flex: 1,
- borderRightWidth: 1,
- borderRightColor: '#000000',
- borderRightStyle: 'solid',
- padding: 4,
- fontSize: 8,
- textAlign: 'center',
- },
- tableBorderBottom: {
- borderBottomWidth: 1,
- borderBottomColor: '#000000',
- borderBottomStyle: 'solid',
- },
- supplierSection: {
- marginBottom: 10,
- },
- supplierSectionBreak: {
- marginBottom: 15,
- },
parameterBadge: {
backgroundColor: '#F5F5F5',
color: '#333333',
@@ -136,6 +63,9 @@ const pdfStyles = StyleSheet.create({
flexWrap: 'wrap',
marginBottom: 8,
},
+ section: {
+ marginBottom: 15,
+ },
});
interface HppPerKandangExportParams {
@@ -192,6 +122,215 @@ const getParameterText = (params: HppPerKandangExportParams['params']) => {
return paramsText;
};
+// Helper functions for PdfTable - Rekapitulasi
+const getRekapitulasiColumns = (): PdfColumn[] => [
+ { key: 'rentang_bw', header: 'Rentang BW', flex: 1.2, align: 'center' },
+ { key: 'sisa_butir', header: 'Sisa Butir', flex: 1, align: 'right' },
+ { key: 'sisa_kg', header: 'Sisa Kg', flex: 1, align: 'right' },
+ {
+ key: 'rata_rata_bobot',
+ header: 'Rata-Rata Bobot (Kg)',
+ flex: 1.2,
+ align: 'right',
+ },
+ { key: 'feed_supplier', header: 'Feed (Supplier)', flex: 1.5, align: 'left' },
+ { key: 'doc_supplier', header: 'DOC (Supplier)', flex: 1.2, align: 'left' },
+ {
+ key: 'rata_harga_doc',
+ header: 'Rata-Rata Harga DOC',
+ flex: 1.2,
+ align: 'right',
+ },
+ { key: 'hpp_telur', header: 'HPP Telur (RP/KG)', flex: 1.2, align: 'right' },
+ { key: 'nominal_sisa', header: 'Nominal Sisa', flex: 1.2, align: 'right' },
+];
+
+const getRekapitulasiData = (
+ perWeightRange: HppPerKandangPerWeightRange[]
+): PdfTbodyCell[][] => {
+ return perWeightRange.map((group) => [
+ { key: 'rentang_bw', value: group.label, align: 'center' },
+ {
+ key: 'sisa_butir',
+ value: formatNumber(group.egg_production_pieces),
+ align: 'right',
+ },
+ {
+ key: 'sisa_kg',
+ value: formatNumber(group.egg_production_kg),
+ align: 'right',
+ },
+ {
+ key: 'rata_rata_bobot',
+ value: formatNumber(group.avg_weight_kg),
+ align: 'right',
+ },
+ {
+ key: 'feed_supplier',
+ value:
+ group.feed_suppliers
+ ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
+ .join(' | ') || '-',
+ },
+ {
+ key: 'doc_supplier',
+ value:
+ group.doc_suppliers
+ ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
+ .join(' | ') || '-',
+ },
+ {
+ key: 'rata_harga_doc',
+ value: formatCurrency(group.average_doc_price_rp),
+ align: 'right',
+ },
+ {
+ key: 'hpp_telur',
+ value: formatCurrency(group.egg_hpp_rp_per_kg),
+ align: 'right',
+ },
+ {
+ key: 'nominal_sisa',
+ value: formatCurrency(group.egg_value_rp),
+ align: 'right',
+ },
+ ]);
+};
+
+// Helper functions for PdfTable - Detail Per Kandang
+const getDetailColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'kandang', header: 'Kandang', flex: 1.5, align: 'left' },
+ { key: 'rentang_bw', header: 'Rentang BW', flex: 1, align: 'left' },
+ {
+ key: 'rata_rata_bobot',
+ header: 'Rata-Rata Bobot (Kg)',
+ flex: 1,
+ align: 'right',
+ },
+ { key: 'sisa_butir', header: 'Sisa Butir', flex: 0.8, align: 'right' },
+ { key: 'sisa_kg', header: 'Sisa Kg (Telur)', flex: 0.8, align: 'right' },
+ { key: 'feed_supplier', header: 'Feed (Supplier)', flex: 1.2, align: 'left' },
+ { key: 'doc_supplier', header: 'DOC (Supplier)', flex: 1, align: 'left' },
+ {
+ key: 'rata_harga_doc',
+ header: 'Rata-Rata Harga DOC',
+ flex: 1.2,
+ align: 'right',
+ },
+ { key: 'hpp_telur', header: 'HPP Telur (RP/KG)', flex: 1, align: 'right' },
+ { key: 'nominal_sisa', header: 'Nominal Sisa', flex: 1.2, align: 'right' },
+];
+
+const getDetailData = (rows: HppPerKandangRow[]): PdfTbodyCell[][] => {
+ return rows.map((item, index) => [
+ { key: 'no', value: index + 1, align: 'center' },
+ { key: 'kandang', value: item.kandang?.name || '-' },
+ {
+ key: 'rentang_bw',
+ value: `${item.weight_range.weight_min.toFixed(2)} - ${item.weight_range.weight_max.toFixed(2)}`,
+ },
+ {
+ key: 'rata_rata_bobot',
+ value: formatNumber(item.avg_weight_kg),
+ align: 'right',
+ },
+ {
+ key: 'sisa_butir',
+ value: formatNumber(item.egg_production_pieces),
+ align: 'right',
+ },
+ {
+ key: 'sisa_kg',
+ value: formatNumber(item.egg_production_kg),
+ align: 'right',
+ },
+ {
+ key: 'feed_supplier',
+ value:
+ item.feed_suppliers
+ ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
+ .join(' | ') || '-',
+ },
+ {
+ key: 'doc_supplier',
+ value:
+ item.doc_suppliers
+ ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
+ .join(' | ') || '-',
+ },
+ {
+ key: 'rata_harga_doc',
+ value: formatCurrency(item.average_doc_price_rp),
+ align: 'right',
+ },
+ {
+ key: 'hpp_telur',
+ value: formatCurrency(item.egg_hpp_rp_per_kg),
+ align: 'right',
+ },
+ {
+ key: 'nominal_sisa',
+ value: formatCurrency(item.egg_value_rp),
+ align: 'right',
+ },
+ ]);
+};
+
+const getDetailFooter = (
+ summary: HppPerKandangReport['summary']
+): PdfTfootCell[] => {
+ if (!summary?.total) return [];
+
+ const allFeedSuppliers =
+ summary.total.feed_suppliers
+ ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
+ .join(' | ') || '-';
+
+ const allDocSuppliers =
+ summary.total.doc_suppliers
+ ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
+ .join(' | ') || '-';
+
+ return [
+ { key: 'no', value: 'TOTAL' },
+ { key: 'kandang', value: 'ALL' },
+ { key: 'rentang_bw', value: '-' },
+ {
+ key: 'rata_rata_bobot',
+ value: formatNumber(summary.total.average_weight_kg),
+ align: 'right',
+ },
+ {
+ key: 'sisa_butir',
+ value: formatNumber(summary.total.total_egg_production_pieces),
+ align: 'right',
+ },
+ {
+ key: 'sisa_kg',
+ value: formatNumber(summary.total.total_egg_production_kg),
+ align: 'right',
+ },
+ { key: 'feed_supplier', value: allFeedSuppliers },
+ { key: 'doc_supplier', value: allDocSuppliers },
+ {
+ key: 'rata_harga_doc',
+ value: formatCurrency(summary.total.total_average_doc_price_rp),
+ align: 'right',
+ },
+ {
+ key: 'hpp_telur',
+ value: formatCurrency(summary.total.average_egg_hpp_rp_per_kg),
+ align: 'right',
+ },
+ {
+ key: 'nominal_sisa',
+ value: formatCurrency(summary.total.total_egg_value_rp),
+ align: 'right',
+ },
+ ];
+};
+
const createPDFDocument = (
data: HppPerKandangExportParams['data'],
params: HppPerKandangExportParams['params']
@@ -216,404 +355,23 @@ const createPDFDocument = (
{/* Rekapitulasi Section */}
-
+
Rekapitulasi
-
-
- {/* Table Header */}
-
-
- Rentang BW
-
-
- Sisa Butir
-
-
- Sisa Kg
-
-
- Rata-Rata Bobot (Kg)
-
-
- Feed (Supplier)
-
-
- DOC (Supplier)
-
-
- Rata-Rata Harga DOC
-
-
- HPP Telur (RP/KG)
-
-
- Nominal Sisa
-
-
-
- {/* Table Body - Rekapitulasi */}
- {rekapitulasiByWeightRange.map(
- (group: HppPerKandangPerWeightRange, index: number) => (
-
-
- {group.label}
-
-
- {formatNumber(group.egg_production_pieces)}
-
-
- {formatNumber(group.egg_production_kg)}
-
-
- {formatNumber(group.avg_weight_kg)}
-
-
-
- {group.feed_suppliers
- ?.map(
- (s: { alias?: string; name: string }) =>
- s.alias || s.name
- )
- .join(' | ') || '-'}
-
-
-
-
- {group.doc_suppliers
- ?.map(
- (s: { alias?: string; name: string }) =>
- s.alias || s.name
- )
- .join(' | ') || '-'}
-
-
-
- {formatCurrency(group.average_doc_price_rp)}
-
-
- {formatCurrency(group.egg_hpp_rp_per_kg)}
-
-
- {formatCurrency(group.egg_value_rp)}
-
-
- )
- )}
-
+
{/* Detail Per Kandang Section */}
-
+
Detail Per Kandang
-
-
- {/* Table Header */}
-
-
- No
-
-
- Kandang
-
-
- Rentang BW
-
-
- Rata-Rata Bobot (Kg)
-
-
- Sisa Butir
-
-
- Sisa Kg (Telur)
-
-
- Feed (Supplier)
-
-
- DOC (Supplier)
-
-
- Rata-Rata Harga DOC
-
-
- HPP Telur (RP/KG)
-
-
- Nominal Sisa
-
-
-
- {/* Table Body - Detail Per Kandang */}
- {data.rows.map((item: HppPerKandangRow, index: number) => (
-
-
- {index + 1}
-
-
- {item.kandang?.name || '-'}
-
-
-
- {item.weight_range.weight_min.toFixed(2)} -{' '}
- {item.weight_range.weight_max.toFixed(2)}
-
-
-
- {formatNumber(item.avg_weight_kg)}
-
-
- {formatNumber(item.egg_production_pieces)}
-
-
- {formatNumber(item.egg_production_kg)}
-
-
-
- {item.feed_suppliers
- ?.map(
- (s: { alias?: string; name: string }) =>
- s.alias || s.name
- )
- .join(' | ')}
-
-
-
-
- {item.doc_suppliers
- ?.map(
- (s: { alias?: string; name: string }) =>
- s.alias || s.name
- )
- .join(' | ')}
-
-
-
- {formatCurrency(item.average_doc_price_rp)}
-
-
- {formatCurrency(item.egg_hpp_rp_per_kg)}
-
-
- {formatCurrency(item.egg_value_rp)}
-
-
- ))}
-
- {/* TOTAL Row */}
- {data.summary?.total && (
-
-
- TOTAL
-
-
- ALL
-
-
- -
-
-
-
- {formatNumber(data.summary.total.average_weight_kg)}
-
-
-
-
- {formatNumber(
- data.summary.total.total_egg_production_pieces
- )}
-
-
-
-
- {formatNumber(data.summary.total.total_egg_production_kg)}
-
-
-
-
- {data.rows
- .flatMap((row: HppPerKandangRow) =>
- row.feed_suppliers?.map(
- (s: { alias?: string; name: string }) =>
- s.alias || s.name
- )
- )
- .filter(
- (v: string, i: number, a: string[]) =>
- a.indexOf(v) === i
- )
- .join(' | ') || '-'}
-
-
-
-
- {data.rows
- .flatMap((row: HppPerKandangRow) =>
- row.doc_suppliers?.map(
- (s: { alias?: string; name: string }) =>
- s.alias || s.name
- )
- )
- .filter(
- (v: string, i: number, a: string[]) =>
- a.indexOf(v) === i
- )
- .join(' | ') || '-'}
-
-
-
-
- {formatCurrency(
- data.summary.total.total_average_doc_price_rp
- )}
-
-
-
-
- {formatCurrency(
- data.summary.total.average_egg_hpp_rp_per_kg
- )}
-
-
-
-
- {formatCurrency(data.summary.total.total_egg_value_rp)}
-
-
-
- )}
-
+