diff --git a/src/components/pages/report/production-result/ProductionResultReportPDF.tsx b/src/components/pages/report/production-result/ProductionResultReportPDF.tsx
index 9bc27c4b..139f4640 100644
--- a/src/components/pages/report/production-result/ProductionResultReportPDF.tsx
+++ b/src/components/pages/report/production-result/ProductionResultReportPDF.tsx
@@ -1,18 +1,19 @@
'use client';
import React from 'react';
-import {
- Document,
- Page,
- StyleSheet,
- Text,
- View,
- Image,
-} from '@react-pdf/renderer';
+import { Document, Page, StyleSheet, View, Text } from '@react-pdf/renderer';
import { formatDate, formatNumber } from '@/lib/helper';
import { BaseProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { ProductionResult } from '@/types/api/report/production-result';
+import { PdfTypography } from '@/components/helper/pdf/typography/PdfTypography';
+import { PdfParamBadge } from '@/components/helper/pdf/badge/PdfParamBadge';
+import { PdfPageNumber } from '@/components/helper/pdf/layout/PdfPageNumber';
+import {
+ PdfTable,
+ PdfColumn,
+ PdfTbodyCell,
+} from '@/components/helper/pdf/table';
type MappedProductionResultsItem = {
projectFlockKandang: BaseProjectFlockKandang;
@@ -25,132 +26,28 @@ interface ProductionResultReportPDFProps {
const styles = StyleSheet.create({
page: {
- paddingTop: 24,
- paddingBottom: 52,
- paddingHorizontal: 16,
- },
-
- companyInfoHeader: {
- width: '100%',
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'flex-start',
- marginBottom: 8,
- },
- companyLogo: {
- width: 64,
- height: 'auto',
- },
- companyInfoHeaderDate: {
- paddingTop: 8,
fontSize: 10,
+ fontFamily: 'Helvetica',
+ padding: 20,
+ backgroundColor: '#FFFFFF',
},
- companyName: {
- fontSize: 12,
- fontWeight: 'bold',
- marginBottom: 4,
- },
- companyAddress: {
- fontSize: 8,
- maxWidth: 420,
+ titleSection: {
marginBottom: 10,
},
- doubleDivider: {
- width: '100%',
- height: 6,
- borderTopWidth: 2,
- borderTopColor: '#000',
- borderBottomWidth: 2,
- borderBottomColor: '#000',
- },
-
- title: {
- marginTop: 14,
- fontSize: 14,
- lineHeight: '150%',
- textAlign: 'center',
- fontFamily: 'Times-Roman',
- fontWeight: 'bold',
- },
-
- footer: {
- width: '100%',
- display: 'flex',
+ parameterContainer: {
flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- paddingHorizontal: 16,
- position: 'absolute',
- fontSize: 8,
- bottom: 22,
- left: 0,
- right: 0,
- textAlign: 'center',
- color: 'grey',
+ flexWrap: 'wrap',
+ marginBottom: 8,
},
-
- section: {
- marginTop: 12,
- borderWidth: 1,
- borderColor: '#000',
- padding: 8,
+ tableSection: {
+ marginBottom: 12,
},
-
- sectionHeader: {
- marginBottom: 6,
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'baseline',
- },
- sectionTitle: {
+ tableTitle: {
fontSize: 10,
fontWeight: 'bold',
+ marginBottom: 6,
+ color: '#333',
},
- sectionSubtitle: {
- fontSize: 8,
- color: '#444',
- },
-
- // Simple grid table (label/value pairs)
- grid: {
- width: '100%',
- borderWidth: 1,
- borderColor: '#000',
- },
- gridRow: {
- flexDirection: 'row',
- borderBottomWidth: 1,
- borderBottomColor: '#000',
- },
- gridRowLast: {
- borderBottomWidth: 0,
- },
- gridCellLabel: {
- width: '40%',
- paddingVertical: 3,
- paddingHorizontal: 6,
- fontSize: 8,
- borderRightWidth: 1,
- borderRightColor: '#000',
- fontWeight: 'bold',
- },
- gridCellValue: {
- width: '60%',
- paddingVertical: 3,
- paddingHorizontal: 6,
- fontSize: 8,
- textAlign: 'right',
- },
-
- // Subsection headings
- groupTitle: {
- marginTop: 8,
- marginBottom: 4,
- fontSize: 9,
- fontWeight: 'bold',
- },
-
emptyText: {
fontSize: 8,
color: '#666',
@@ -169,125 +66,243 @@ function valueText(v: unknown) {
return String(v);
}
-/**
- * Render label/value table for one ProductionResult.
- * Uses a compact grid to keep page readable.
- */
-function ProductionResultGrid({ pr }: { pr: ProductionResult }) {
- const rows: Array<[string, string]> = [
- ['WOA', valueText(pr.woa)],
+// ========================================
+// TABLE 1: WOA & BW
+// ========================================
+const getBwTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'woa', header: 'WOA', flex: 0.8, align: 'center' },
+ { key: 'bw', header: 'BW', flex: 1, align: 'right' },
+ { key: 'std_bw', header: 'Std BW', flex: 1, align: 'right' },
+ { key: 'uniformity', header: 'Uniformity', flex: 1.2, align: 'right' },
+ {
+ key: 'std_uniformity',
+ header: 'Std Uniformity',
+ flex: 1.3,
+ align: 'right',
+ },
+];
- // BW
- ['BW', valueText(pr.bw)],
- ['Std BW', valueText(pr.std_bw)],
- ['Uniformity', valueText(pr.uniformity)],
- ['Std Uniformity', valueText(pr.std_uniformity)],
+const getBwTableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ { key: 'woa', value: valueText(pr.woa) },
+ { key: 'bw', value: valueText(pr.bw), align: 'right' },
+ { key: 'std_bw', value: valueText(pr.std_bw), align: 'right' },
+ { key: 'uniformity', value: valueText(pr.uniformity), align: 'right' },
+ {
+ key: 'std_uniformity',
+ value: valueText(pr.std_uniformity),
+ align: 'right',
+ },
+ ];
+ });
+};
- // Dep
- ['Dep Kum', valueText(pr.dep_kum)],
- ['Dep Std', valueText(pr.dep_std)],
+// ========================================
+// TABLE 2: DEPLESI
+// ========================================
+const getDepTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'dep_kum', header: 'Dep Kum', flex: 1.5, align: 'right' },
+ { key: 'dep_std', header: 'Dep Std', flex: 1.5, align: 'right' },
+];
- // Butiran
- ['Butiran Utuh', valueText(pr.butiran_utuh)],
- ['Butiran Putih', valueText(pr.butiran_putih)],
- ['Butiran Retak', valueText(pr.butiran_retak)],
- ['Butiran Pecah', valueText(pr.butiran_pecah)],
- ['Butiran Jumlah', valueText(pr.butiran_jumlah)],
- ['Total Butir', valueText(pr.total_butir)],
+const getDepTableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ { key: 'dep_kum', value: valueText(pr.dep_kum), align: 'right' },
+ { key: 'dep_std', value: valueText(pr.dep_std), align: 'right' },
+ ];
+ });
+};
- // Kg
- ['Kg Utuh', valueText(pr.kg_utuh)],
- ['Kg Putih', valueText(pr.kg_putih)],
- ['Kg Retak', valueText(pr.kg_retak)],
- ['Kg Pecah', valueText(pr.kg_pecah)],
- ['Kg Jumlah', valueText(pr.kg_jumlah)],
- ['Total Kg', valueText(pr.total_kg)],
+// ========================================
+// TABLE 3: BUTIRAN
+// ========================================
+const getButiranTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'butiran_utuh', header: 'Utuh', flex: 1.2, align: 'right' },
+ { key: 'butiran_putih', header: 'Putih', flex: 1.2, align: 'right' },
+ { key: 'butiran_retak', header: 'Retak', flex: 1.2, align: 'right' },
+ { key: 'butiran_pecah', header: 'Pecah', flex: 1.2, align: 'right' },
+ { key: 'butiran_jumlah', header: 'Jumlah', flex: 1.2, align: 'right' },
+ { key: 'total_butir', header: 'Total Butir', flex: 1.3, align: 'right' },
+];
- // %
- ['% Utuh', valueText(pr.persen_utuh)],
- ['% Putih', valueText(pr.persen_putih)],
- ['% Retak', valueText(pr.persen_retak)],
- ['% Pecah', valueText(pr.persen_pecah)],
+const getButiranTableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ {
+ key: 'butiran_utuh',
+ value: valueText(pr.butiran_utuh),
+ align: 'right',
+ },
+ {
+ key: 'butiran_putih',
+ value: valueText(pr.butiran_putih),
+ align: 'right',
+ },
+ {
+ key: 'butiran_retak',
+ value: valueText(pr.butiran_retak),
+ align: 'right',
+ },
+ {
+ key: 'butiran_pecah',
+ value: valueText(pr.butiran_pecah),
+ align: 'right',
+ },
+ {
+ key: 'butiran_jumlah',
+ value: valueText(pr.butiran_jumlah),
+ align: 'right',
+ },
+ {
+ key: 'total_butir',
+ value: valueText(pr.total_butir),
+ align: 'right',
+ },
+ ];
+ });
+};
- // Produksi
- ['HD', valueText(pr.hd)],
- ['HD Std', valueText(pr.hd_std)],
- ['FI', valueText(pr.fi)],
- ['FI Std', valueText(pr.fi_std)],
- ['EM', valueText(pr.em)],
- ['EM Std', valueText(pr.em_std)],
- ['EW', valueText(pr.ew)],
- ['EW Std', valueText(pr.ew_std)],
- ['FCR', valueText(pr.fcr)],
- ['FCR Std', valueText(pr.fcr_std)],
- ['HH', valueText(pr.hh)],
- ['HH Std', valueText(pr.hh_std)],
- ];
+// ========================================
+// TABLE 4: BERAT (KG)
+// ========================================
+const getKgTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'kg_utuh', header: 'Utuh (Kg)', flex: 1.2, align: 'right' },
+ { key: 'kg_putih', header: 'Putih (Kg)', flex: 1.2, align: 'right' },
+ { key: 'kg_retak', header: 'Retak (Kg)', flex: 1.2, align: 'right' },
+ { key: 'kg_pecah', header: 'Pecah (Kg)', flex: 1.2, align: 'right' },
+ { key: 'kg_jumlah', header: 'Jumlah (Kg)', flex: 1.3, align: 'right' },
+ { key: 'total_kg', header: 'Total (Kg)', flex: 1.3, align: 'right' },
+];
- return (
-
- {rows.map(([label, value], idx) => {
- const isLast = idx === rows.length - 1;
- return (
-
- {label}
- {value}
-
- );
- })}
-
- );
-}
+const getKgTableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ { key: 'kg_utuh', value: valueText(pr.kg_utuh), align: 'right' },
+ { key: 'kg_putih', value: valueText(pr.kg_putih), align: 'right' },
+ { key: 'kg_retak', value: valueText(pr.kg_retak), align: 'right' },
+ { key: 'kg_pecah', value: valueText(pr.kg_pecah), align: 'right' },
+ { key: 'kg_jumlah', value: valueText(pr.kg_jumlah), align: 'right' },
+ { key: 'total_kg', value: valueText(pr.total_kg), align: 'right' },
+ ];
+ });
+};
-/**
- * If there are multiple ProductionResult entries for a kandang,
- * we show them sequentially with a small header per result.
- *
- * You can later change this to render only the latest WOA, or group by week.
- */
-function ProductionResultList({
- productionResults,
-}: {
- productionResults: ProductionResult[];
-}) {
- return (
-
- {productionResults.map((pr, idx) => {
- const kandangName =
- pr.project_flock?.kandang?.name ||
- pr.project_flock?.kandang?.id?.toString() ||
- '';
+// ========================================
+// TABLE 5: PERSENTASE
+// ========================================
+const getPersenTableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'persen_utuh', header: '% Utuh', flex: 1.5, align: 'right' },
+ { key: 'persen_putih', header: '% Putih', flex: 1.5, align: 'right' },
+ { key: 'persen_retak', header: '% Retak', flex: 1.5, align: 'right' },
+ { key: 'persen_pecah', header: '% Pecah', flex: 1.5, align: 'right' },
+];
- // Optional: show a compact subheader
- const headerLeft = `Data #${idx + 1}`;
- const headerRight =
- kandangName && pr.woa !== undefined
- ? `${kandangName} • WOA ${safeNum(pr.woa)}`
- : pr.woa !== undefined
- ? `WOA ${safeNum(pr.woa)}`
- : '';
+const getPersenTableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ {
+ key: 'persen_utuh',
+ value: valueText(pr.persen_utuh),
+ align: 'right',
+ },
+ {
+ key: 'persen_putih',
+ value: valueText(pr.persen_putih),
+ align: 'right',
+ },
+ {
+ key: 'persen_retak',
+ value: valueText(pr.persen_retak),
+ align: 'right',
+ },
+ {
+ key: 'persen_pecah',
+ value: valueText(pr.persen_pecah),
+ align: 'right',
+ },
+ ];
+ });
+};
- return (
-
-
- {headerLeft}
- {headerRight}
-
+// ========================================
+// TABLE 6: PRODUKSI (HD, FI, EM, EW)
+// ========================================
+const getProduksi1TableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'hd', header: 'HD', flex: 0.8, align: 'right' },
+ { key: 'hd_std', header: 'HD Std', flex: 1, align: 'right' },
+ { key: 'fi', header: 'FI', flex: 0.8, align: 'right' },
+ { key: 'fi_std', header: 'FI Std', flex: 1, align: 'right' },
+ { key: 'em', header: 'EM', flex: 0.8, align: 'right' },
+ { key: 'em_std', header: 'EM Std', flex: 1, align: 'right' },
+ { key: 'ew', header: 'EW', flex: 0.8, align: 'right' },
+ { key: 'ew_std', header: 'EW Std', flex: 1, align: 'right' },
+];
-
-
- );
- })}
-
- );
-}
+const getProduksi1TableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ { key: 'hd', value: valueText(pr.hd), align: 'right' },
+ { key: 'hd_std', value: valueText(pr.hd_std), align: 'right' },
+ { key: 'fi', value: valueText(pr.fi), align: 'right' },
+ { key: 'fi_std', value: valueText(pr.fi_std), align: 'right' },
+ { key: 'em', value: valueText(pr.em), align: 'right' },
+ { key: 'em_std', value: valueText(pr.em_std), align: 'right' },
+ { key: 'ew', value: valueText(pr.ew), align: 'right' },
+ { key: 'ew_std', value: valueText(pr.ew_std), align: 'right' },
+ ];
+ });
+};
+
+// ========================================
+// TABLE 7: PRODUKSI (FCR, HH)
+// ========================================
+const getProduksi2TableColumns = (): PdfColumn[] => [
+ { key: 'no', header: 'No', flex: 0.5, align: 'center' },
+ { key: 'fcr', header: 'FCR', flex: 1, align: 'right' },
+ { key: 'fcr_std', header: 'FCR Std', flex: 1.2, align: 'right' },
+ { key: 'hh', header: 'HH', flex: 1, align: 'right' },
+ { key: 'hh_std', header: 'HH Std', flex: 1.2, align: 'right' },
+];
+
+const getProduksi2TableData = (
+ productionResults: ProductionResult[]
+): PdfTbodyCell[][] => {
+ return productionResults.map((pr, index) => {
+ return [
+ { key: 'no', value: index + 1 },
+ { key: 'fcr', value: valueText(pr.fcr), align: 'right' },
+ { key: 'fcr_std', value: valueText(pr.fcr_std), align: 'right' },
+ { key: 'hh', value: valueText(pr.hh), align: 'right' },
+ { key: 'hh_std', value: valueText(pr.hh_std), align: 'right' },
+ ];
+ });
+};
/**
* ✅ Main PDF Component
@@ -297,90 +312,148 @@ const ProductionResultReportPDF = ({
}: ProductionResultReportPDFProps) => {
return (
-
- {/* Header */}
-
-
-
-
- {formatDate(Date.now(), 'DD MMMM YYYY')}
-
+ {mappedProductionResults.length === 0 ? (
+
+ {/* Title and Parameters */}
+
+
+ Laporan > Production Result
+
+
+
+ Tanggal: {formatDate(Date.now(), 'DD MMMM YYYY')}
+
+
+ Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
+
+
-
- PT LUMBUNG TELUR INDONESIA
-
- SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
- Cipedes, Kec. Sukajadi, Kota Bandung 40162
-
-
-
-
-
-
- Laporan Production Result
-
- {/* Sections per ProjectFlockKandang */}
- {mappedProductionResults.length === 0 ? (
Tidak ada data.
- ) : (
- mappedProductionResults.map((item, idx) => {
- const pfk = item.projectFlockKandang;
- // Try to display meaningful identifiers.
- // Adjust these fields based on your real BaseProjectFlockKandang structure.
- const kandangName =
- pfk?.kandang?.name ?? `Kandang #${pfk?.kandang_id ?? idx + 1}`;
+
+
+ ) : (
+ mappedProductionResults.map((item, idx) => {
+ const pfk = item.projectFlockKandang;
- const projectName = pfk?.project_flock?.name ?? '';
+ const kandangName =
+ pfk?.kandang?.name ?? `Kandang #${pfk?.kandang_id ?? idx + 1}`;
- const locationName = pfk?.project_flock?.location?.name ?? '';
+ const projectName = pfk?.project_flock?.name ?? '';
- const areaName = pfk?.project_flock?.area?.name ?? '';
+ const locationName = pfk?.project_flock?.location?.name ?? '';
- return (
- 0} // each kandang starts on a new page for clarity
- >
-
-
- {projectName
- ? `${projectName} • ${kandangName}`
- : kandangName}
-
-
- {[areaName, locationName].filter(Boolean).join(' • ')}
-
+ const areaName = pfk?.project_flock?.area?.name ?? '';
+
+ const hasData =
+ item.productionResult && item.productionResult.length > 0;
+
+ return (
+
+ {/* Title and Parameters */}
+
+
+ Laporan > Production Result
+
+
+
+ Tanggal: {formatDate(Date.now(), 'DD MMMM YYYY')}
+
+
+ Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
+
-
- {item.productionResult && item.productionResult.length > 0 ? (
-
- ) : (
-
- Tidak ada production result untuk kandang ini.
-
- )}
+
+ {projectName
+ ? `${projectName} • ${kandangName}`
+ : kandangName}
+
+
+ {[areaName, locationName].filter(Boolean).join(' • ')}
+
- );
- })
- )}
- {/* Footer */}
-
-
- `${pageNumber} / ${totalPages}`
- }
- fixed
- />
-
-
+ {hasData ? (
+ <>
+ {/* Table 1: WOA & BW */}
+
+ 1. WOA & Body Weight
+
+
+
+ {/* Table 2: Deplesi */}
+
+ 2. Deplesi
+
+
+
+ {/* Table 3: Butiran */}
+
+ 3. Butiran
+
+
+
+ {/* Table 4: Berat (Kg) */}
+
+ 4. Berat (Kg)
+
+
+
+ {/* Table 5: Persentase */}
+
+ 5. Persentase
+
+
+
+ {/* Table 6: Produksi (HD, FI, EM, EW) */}
+
+
+ 6. Produksi (HD, FI, EM, EW)
+
+
+
+
+ {/* Table 7: Produksi (FCR, HH) */}
+
+ 7. Produksi (FCR, HH)
+
+
+ >
+ ) : (
+
+ Tidak ada production result untuk kandang ini.
+
+ )}
+
+
+
+ );
+ })
+ )}
);
};