mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor ProductionResultReportPDF to use reusable PDF
components
This commit is contained in:
@@ -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 (
|
||||
<View style={styles.grid}>
|
||||
{rows.map(([label, value], idx) => {
|
||||
const isLast = idx === rows.length - 1;
|
||||
return (
|
||||
<View
|
||||
key={label}
|
||||
style={[styles.gridRow, ...(isLast ? [styles.gridRowLast] : [])]}
|
||||
>
|
||||
<Text style={styles.gridCellLabel}>{label}</Text>
|
||||
<Text style={styles.gridCellValue}>{value}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<View>
|
||||
{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 (
|
||||
<View
|
||||
key={`${pr.project_flock?.id ?? 'pf'}-${idx}`}
|
||||
style={{ marginTop: idx === 0 ? 0 : 10 }}
|
||||
wrap={false}
|
||||
>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>{headerLeft}</Text>
|
||||
<Text style={styles.sectionSubtitle}>{headerRight}</Text>
|
||||
</View>
|
||||
// ========================================
|
||||
// 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' },
|
||||
];
|
||||
|
||||
<ProductionResultGrid pr={pr} />
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<Document>
|
||||
<Page style={styles.page} size='A4'>
|
||||
{/* Header */}
|
||||
<View>
|
||||
<View style={styles.companyInfoHeader}>
|
||||
<Image style={styles.companyLogo} src='/assets/img/lti-logo.png' />
|
||||
<Text style={styles.companyInfoHeaderDate}>
|
||||
{formatDate(Date.now(), 'DD MMMM YYYY')}
|
||||
</Text>
|
||||
{mappedProductionResults.length === 0 ? (
|
||||
<Page style={styles.page} size='A4'>
|
||||
{/* Title and Parameters */}
|
||||
<View style={styles.titleSection}>
|
||||
<PdfTypography size='h1' variant='primary'>
|
||||
Laporan > Production Result
|
||||
</PdfTypography>
|
||||
<View style={styles.parameterContainer}>
|
||||
<PdfParamBadge>
|
||||
Tanggal: {formatDate(Date.now(), 'DD MMMM YYYY')}
|
||||
</PdfParamBadge>
|
||||
<PdfParamBadge>
|
||||
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||
</PdfParamBadge>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text style={styles.companyName}>PT LUMBUNG TELUR INDONESIA</Text>
|
||||
<Text style={styles.companyAddress}>
|
||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
||||
</Text>
|
||||
|
||||
<View style={styles.doubleDivider} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.title}>Laporan Production Result</Text>
|
||||
|
||||
{/* Sections per ProjectFlockKandang */}
|
||||
{mappedProductionResults.length === 0 ? (
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<Text style={styles.emptyText}>Tidak ada data.</Text>
|
||||
</View>
|
||||
) : (
|
||||
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}`;
|
||||
<PdfPageNumber />
|
||||
</Page>
|
||||
) : (
|
||||
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 (
|
||||
<View
|
||||
key={`pfk-${pfk?.id ?? idx}`}
|
||||
style={styles.section}
|
||||
break={idx > 0} // each kandang starts on a new page for clarity
|
||||
>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{projectName
|
||||
? `${projectName} • ${kandangName}`
|
||||
: kandangName}
|
||||
</Text>
|
||||
<Text style={styles.sectionSubtitle}>
|
||||
{[areaName, locationName].filter(Boolean).join(' • ')}
|
||||
</Text>
|
||||
const areaName = pfk?.project_flock?.area?.name ?? '';
|
||||
|
||||
const hasData =
|
||||
item.productionResult && item.productionResult.length > 0;
|
||||
|
||||
return (
|
||||
<Page key={`pfk-${pfk?.id ?? idx}`} style={styles.page} size='A4'>
|
||||
{/* Title and Parameters */}
|
||||
<View style={styles.titleSection}>
|
||||
<PdfTypography size='h1' variant='primary'>
|
||||
Laporan > Production Result
|
||||
</PdfTypography>
|
||||
<View style={styles.parameterContainer}>
|
||||
<PdfParamBadge>
|
||||
Tanggal: {formatDate(Date.now(), 'DD MMMM YYYY')}
|
||||
</PdfParamBadge>
|
||||
<PdfParamBadge>
|
||||
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||
</PdfParamBadge>
|
||||
</View>
|
||||
|
||||
{item.productionResult && item.productionResult.length > 0 ? (
|
||||
<ProductionResultList
|
||||
productionResults={item.productionResult}
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.emptyText}>
|
||||
Tidak ada production result untuk kandang ini.
|
||||
</Text>
|
||||
)}
|
||||
<PdfTypography size='h2' variant='primary'>
|
||||
{projectName
|
||||
? `${projectName} • ${kandangName}`
|
||||
: kandangName}
|
||||
</PdfTypography>
|
||||
<PdfTypography size='label'>
|
||||
{[areaName, locationName].filter(Boolean).join(' • ')}
|
||||
</PdfTypography>
|
||||
</View>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<View style={styles.footer} fixed>
|
||||
<Text
|
||||
render={({ pageNumber, totalPages }) =>
|
||||
`${pageNumber} / ${totalPages}`
|
||||
}
|
||||
fixed
|
||||
/>
|
||||
</View>
|
||||
</Page>
|
||||
{hasData ? (
|
||||
<>
|
||||
{/* Table 1: WOA & BW */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>1. WOA & Body Weight</Text>
|
||||
<PdfTable
|
||||
columns={getBwTableColumns()}
|
||||
data={getBwTableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Table 2: Deplesi */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>2. Deplesi</Text>
|
||||
<PdfTable
|
||||
columns={getDepTableColumns()}
|
||||
data={getDepTableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Table 3: Butiran */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>3. Butiran</Text>
|
||||
<PdfTable
|
||||
columns={getButiranTableColumns()}
|
||||
data={getButiranTableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Table 4: Berat (Kg) */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>4. Berat (Kg)</Text>
|
||||
<PdfTable
|
||||
columns={getKgTableColumns()}
|
||||
data={getKgTableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Table 5: Persentase */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>5. Persentase</Text>
|
||||
<PdfTable
|
||||
columns={getPersenTableColumns()}
|
||||
data={getPersenTableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Table 6: Produksi (HD, FI, EM, EW) */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>
|
||||
6. Produksi (HD, FI, EM, EW)
|
||||
</Text>
|
||||
<PdfTable
|
||||
columns={getProduksi1TableColumns()}
|
||||
data={getProduksi1TableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Table 7: Produksi (FCR, HH) */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>7. Produksi (FCR, HH)</Text>
|
||||
<PdfTable
|
||||
columns={getProduksi2TableColumns()}
|
||||
data={getProduksi2TableData(item.productionResult!)}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<Text style={styles.emptyText}>
|
||||
Tidak ada production result untuk kandang ini.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<PdfPageNumber />
|
||||
</Page>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user