mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
refactor(FE): Refactor ExpensePDF component for improved readability
This commit is contained in:
@@ -1,212 +1,154 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Document,
|
||||
Image,
|
||||
Link,
|
||||
Page,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from '@react-pdf/renderer';
|
||||
import React from 'react';
|
||||
import { Document, Page, StyleSheet, View, Text } from '@react-pdf/renderer';
|
||||
|
||||
import { Expense } from '@/types/api/expense';
|
||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
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 } from '@/components/helper/pdf/table';
|
||||
|
||||
interface ExpensePDFProps {
|
||||
expense?: Expense;
|
||||
}
|
||||
|
||||
const ExpensePDFStyle = StyleSheet.create({
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 64,
|
||||
paddingHorizontal: 32,
|
||||
fontSize: 10,
|
||||
fontFamily: 'Helvetica',
|
||||
padding: 20,
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
|
||||
companyInfoHeader: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 8,
|
||||
},
|
||||
companyLogo: {
|
||||
width: 64,
|
||||
height: 'auto',
|
||||
},
|
||||
companyInfoHeaderDate: {
|
||||
paddingTop: 8,
|
||||
fontSize: 12,
|
||||
},
|
||||
companyName: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 4,
|
||||
},
|
||||
companyAddress: {
|
||||
fontSize: 8,
|
||||
maxWidth: 400,
|
||||
titleSection: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
|
||||
title: {
|
||||
marginTop: 16,
|
||||
fontSize: 16,
|
||||
lineHeight: '150%',
|
||||
textAlign: 'center',
|
||||
fontFamily: 'Times-Roman',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
|
||||
footer: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
parameterContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 32,
|
||||
|
||||
position: 'absolute',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: 8,
|
||||
},
|
||||
infoTableSection: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
infoTableTitle: {
|
||||
fontSize: 10,
|
||||
bottom: 30,
|
||||
left: 0,
|
||||
right: 0,
|
||||
textAlign: 'center',
|
||||
color: 'grey',
|
||||
},
|
||||
|
||||
// wrapper
|
||||
generalInfoTable: {
|
||||
width: '100%',
|
||||
marginTop: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
borderBottomWidth: 0,
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
generalInfoTableRow: {
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
},
|
||||
|
||||
// columns
|
||||
generalInfoTableColLabel: {
|
||||
width: '30%',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
generalInfoTableColSeparator: {
|
||||
width: '3%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 6,
|
||||
},
|
||||
generalInfoTableColValue: {
|
||||
width: '67%',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
|
||||
generalInfoTableLabelText: {
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 6,
|
||||
color: '#333',
|
||||
},
|
||||
generalInfoTableValueText: {},
|
||||
|
||||
// expense detail table
|
||||
expenseDetailContainer: {
|
||||
width: '100%',
|
||||
marginTop: 12,
|
||||
tableSection: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
expenseDetailTitle: {
|
||||
fontSize: 14,
|
||||
lineHeight: '150%',
|
||||
fontFamily: 'Times-Roman',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
kandangExpenseContainer: {
|
||||
width: '100%',
|
||||
marginTop: 8,
|
||||
},
|
||||
kandangExpenseTitle: {
|
||||
fontSize: 14,
|
||||
lineHeight: '150%',
|
||||
fontFamily: 'Times-Roman',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
kandangExpenseTable: {
|
||||
width: '100%',
|
||||
marginTop: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
borderBottomWidth: 0,
|
||||
fontSize: 12,
|
||||
},
|
||||
kandangExpenseTableRow: {
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
},
|
||||
kandangExpenseTableColLabel: {
|
||||
width: '20%',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
kandangExpenseTableColLabelBorderRight: {
|
||||
borderRight: '1px solid #000000',
|
||||
},
|
||||
kandangExpenseTableColNonstock: {
|
||||
width: '20%',
|
||||
},
|
||||
kandangExpenseTableColNote: {
|
||||
width: '40%',
|
||||
},
|
||||
kandangExpenseHeaderLabelText: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
kandangExpenseLabelText: {
|
||||
tableTitle: {
|
||||
fontSize: 10,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 6,
|
||||
color: '#333',
|
||||
},
|
||||
kandangExpenseTableFooterColTotalExpenseCaption: {
|
||||
width: '40%',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 8,
|
||||
textAlign: 'right',
|
||||
},
|
||||
kandangExpenseTableFooterColTotalExpenseValue: {
|
||||
width: '60%',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
|
||||
// utils
|
||||
doubleDivider: {
|
||||
width: '100%',
|
||||
height: 6,
|
||||
borderTop: '2px solid black',
|
||||
borderBottom: '2px solid black',
|
||||
emptyText: {
|
||||
fontSize: 8,
|
||||
color: '#666',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
});
|
||||
|
||||
const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
type ExpenseKandang = Expense['kandangs'][number];
|
||||
type PengajuanItem = NonNullable<ExpenseKandang['pengajuans']>[number];
|
||||
type RealisasiItem = NonNullable<ExpenseKandang['realisasi']>[number];
|
||||
|
||||
const valueText = (v: unknown) => {
|
||||
if (v === null || v === undefined) return '-';
|
||||
if (typeof v === 'number') return formatNumber(v);
|
||||
return String(v);
|
||||
};
|
||||
|
||||
const getPengajuanColumns = (): PdfColumn<PengajuanItem>[] => [
|
||||
{
|
||||
key: 'no',
|
||||
header: 'No',
|
||||
flex: 0.5,
|
||||
align: 'center',
|
||||
cell: ({ index }) => index + 1,
|
||||
},
|
||||
{
|
||||
key: 'nonstock',
|
||||
header: 'Nonstock',
|
||||
flex: 1.5,
|
||||
cell: ({ row }) => row.nonstock.name,
|
||||
},
|
||||
{
|
||||
key: 'qty',
|
||||
header: 'Kuantitas',
|
||||
flex: 1,
|
||||
align: 'right',
|
||||
cell: ({ row }) => valueText(row.qty),
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
header: 'Harga Satuan',
|
||||
flex: 1.2,
|
||||
align: 'right',
|
||||
cell: ({ row }) => formatCurrency(row.price),
|
||||
},
|
||||
{
|
||||
key: 'notes',
|
||||
header: 'Catatan',
|
||||
flex: 1.5,
|
||||
cell: ({ row }) => row.notes || '-',
|
||||
},
|
||||
];
|
||||
|
||||
const getRealisasiColumns = (): PdfColumn<RealisasiItem>[] => [
|
||||
{
|
||||
key: 'no',
|
||||
header: 'No',
|
||||
flex: 0.5,
|
||||
align: 'center',
|
||||
cell: ({ index }) => index + 1,
|
||||
},
|
||||
{
|
||||
key: 'nonstock',
|
||||
header: 'Nonstock',
|
||||
flex: 1.5,
|
||||
cell: ({ row }) => row.nonstock.name,
|
||||
},
|
||||
{
|
||||
key: 'qty',
|
||||
header: 'Kuantitas',
|
||||
flex: 1,
|
||||
align: 'right',
|
||||
cell: ({ row }) => valueText(row.qty),
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
header: 'Harga Satuan',
|
||||
flex: 1.2,
|
||||
align: 'right',
|
||||
cell: ({ row }) => formatCurrency(row.price),
|
||||
},
|
||||
{
|
||||
key: 'notes',
|
||||
header: 'Catatan',
|
||||
flex: 1.5,
|
||||
cell: ({ row }) => row.notes || '-',
|
||||
},
|
||||
];
|
||||
|
||||
const getInfoTableRows = (expense?: Expense) => {
|
||||
const isLatestApprovalRejected =
|
||||
expense?.latest_approval?.action === 'REJECTED';
|
||||
const isExpenseRealized =
|
||||
expense?.latest_approval?.step_number &&
|
||||
expense?.latest_approval.step_number >= 5;
|
||||
|
||||
const realizationStatus = isExpenseRealized
|
||||
? 'Sudah Realisasi'
|
||||
: 'Belum Realisasi';
|
||||
|
||||
const rows = [
|
||||
{ label: 'Nomor PO', value: expense?.po_number },
|
||||
{ label: 'Nomor Referensi', value: expense?.reference_number },
|
||||
return [
|
||||
{ label: 'Nomor PO', value: expense?.po_number || '-' },
|
||||
{ label: 'Nomor Referensi', value: expense?.reference_number || '-' },
|
||||
{
|
||||
label: 'Kategori',
|
||||
value:
|
||||
@@ -214,9 +156,9 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
? 'Biaya Operasional'
|
||||
: expense?.category === 'NON-BOP'
|
||||
? 'Non Biaya Operasional'
|
||||
: '',
|
||||
: '-',
|
||||
},
|
||||
{ label: 'Lokasi', value: expense?.location.name },
|
||||
{ label: 'Lokasi', value: expense?.location?.name || '-' },
|
||||
{
|
||||
label: 'Kandang',
|
||||
value:
|
||||
@@ -227,7 +169,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
.join(', ')
|
||||
: '-',
|
||||
},
|
||||
{ label: 'Vendor', value: expense?.supplier.name },
|
||||
{ label: 'Vendor', value: expense?.supplier?.name || '-' },
|
||||
{
|
||||
label: 'Tanggal Transaksi',
|
||||
value: formatDate(expense?.transaction_date, 'DD MMMM YYYY'),
|
||||
@@ -238,12 +180,12 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
? formatDate(expense?.realization_date, 'DD MMMM YYYY')
|
||||
: '-',
|
||||
},
|
||||
{ label: 'Nama Pengaju', value: expense?.created_user.name },
|
||||
{ label: 'Nama Pengaju', value: expense?.created_user?.name || '-' },
|
||||
{
|
||||
label: 'Nominal Biaya',
|
||||
value: formatCurrency(
|
||||
expense?.latest_approval.step_number === 5 ||
|
||||
expense?.latest_approval.step_number === 6
|
||||
expense?.latest_approval?.step_number === 5 ||
|
||||
expense?.latest_approval?.step_number === 6
|
||||
? (expense?.total_realisasi ?? 0)
|
||||
: (expense?.total_pengajuan ?? 0)
|
||||
),
|
||||
@@ -263,401 +205,136 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
label: 'Status Biaya',
|
||||
value: isLatestApprovalRejected
|
||||
? 'Ditolak'
|
||||
: expense?.latest_approval?.step_name,
|
||||
: expense?.latest_approval?.step_name || '-',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
interface InfoRow {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const getInfoTableColumns = (): PdfColumn<InfoRow>[] => [
|
||||
{
|
||||
key: 'label',
|
||||
header: 'Field',
|
||||
flex: 1,
|
||||
cell: ({ row }) => row.label,
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
header: 'Value',
|
||||
flex: 2,
|
||||
cell: ({ row }) => row.value,
|
||||
},
|
||||
];
|
||||
|
||||
const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||
const kandangs = expense?.kandangs || [];
|
||||
const infoRows = getInfoTableRows(expense);
|
||||
|
||||
return (
|
||||
<Document>
|
||||
<Page style={ExpensePDFStyle.page}>
|
||||
<View>
|
||||
<View style={ExpensePDFStyle.companyInfoHeader}>
|
||||
<Image
|
||||
style={ExpensePDFStyle.companyLogo}
|
||||
src='/assets/img/lti-logo.png'
|
||||
/>
|
||||
|
||||
<Text style={ExpensePDFStyle.companyInfoHeaderDate}>
|
||||
{formatDate(Date.now(), 'DD MMMM YYYY')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text style={ExpensePDFStyle.companyName}>
|
||||
PT LUMBUNG TELUR INDONESIA
|
||||
</Text>
|
||||
<Text style={ExpensePDFStyle.companyAddress}>
|
||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
||||
</Text>
|
||||
|
||||
<View style={ExpensePDFStyle.doubleDivider} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={ExpensePDFStyle.title}>
|
||||
<Page style={styles.page} size='A4'>
|
||||
{/* Title Section */}
|
||||
<View style={styles.titleSection}>
|
||||
<PdfTypography size='h1' variant='primary'>
|
||||
Laporan{' '}
|
||||
{expense?.category === 'BOP'
|
||||
? 'Biaya Operasional'
|
||||
: 'Non-Biaya Operasional'}{' '}
|
||||
{expense?.po_number}
|
||||
</Text>
|
||||
|
||||
{/* General info table */}
|
||||
<View style={ExpensePDFStyle.generalInfoTable}>
|
||||
{rows.map((row) => (
|
||||
<View style={ExpensePDFStyle.generalInfoTableRow} key={row.label}>
|
||||
<View style={ExpensePDFStyle.generalInfoTableColLabel}>
|
||||
<Text style={ExpensePDFStyle.generalInfoTableLabelText}>
|
||||
{row.label}
|
||||
</Text>
|
||||
: 'Non-Biaya Operasional'}
|
||||
</PdfTypography>
|
||||
<PdfTypography size='h2'>{expense?.po_number || '-'}</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 style={ExpensePDFStyle.generalInfoTableColSeparator}>
|
||||
<Text>:</Text>
|
||||
</View>
|
||||
<View style={ExpensePDFStyle.generalInfoTableColValue}>
|
||||
<Text style={ExpensePDFStyle.generalInfoTableValueText}>
|
||||
{row.value}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Detail expense request */}
|
||||
<View
|
||||
minPresenceAhead={80}
|
||||
style={ExpensePDFStyle.expenseDetailContainer}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.expenseDetailTitle}>
|
||||
Rincian Pengajuan Biaya Operasional
|
||||
</Text>
|
||||
{/* Info Table Section */}
|
||||
<View style={styles.infoTableSection}>
|
||||
<Text style={styles.infoTableTitle}>Informasi Biaya</Text>
|
||||
<PdfTable columns={getInfoTableColumns()} data={infoRows} />
|
||||
</View>
|
||||
|
||||
{expense?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
|
||||
let expenseRequestTotal = 0;
|
||||
|
||||
kandangExpense.pengajuans?.forEach(
|
||||
(item) => (expenseRequestTotal += item.qty * item.price)
|
||||
);
|
||||
{/* Rincian Pengajuan Section */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>1. Rincian Pengajuan Biaya</Text>
|
||||
{kandangs.length === 0 ? (
|
||||
<Text style={styles.emptyText}>Tidak ada data pengajuan.</Text>
|
||||
) : (
|
||||
kandangs.map((kandang, idx) => {
|
||||
const pengajuans = kandang.pengajuans || [];
|
||||
const kandangName =
|
||||
kandang.kandang_id && kandang.name
|
||||
? kandang.name
|
||||
: expense?.location?.name || 'Umum';
|
||||
|
||||
return (
|
||||
<View
|
||||
key={kandangExpenseIdx}
|
||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
||||
{kandangExpense.kandang_id && kandangExpense.name
|
||||
? `Biaya ${kandangExpense.name}`
|
||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
||||
</Text>
|
||||
|
||||
<View style={ExpensePDFStyle.kandangExpenseTable}>
|
||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Nonstock
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Kuantitas
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Harga Satuan
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Catatan
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{kandangExpense.pengajuans?.map((pengajuan, pengajuanIdx) => (
|
||||
<View
|
||||
key={pengajuanIdx}
|
||||
style={ExpensePDFStyle.kandangExpenseTableRow}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{pengajuan.nonstock.name}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{formatNumber(pengajuan.qty)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{formatCurrency(pengajuan.price)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{pengajuan.notes}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseCaption,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Total Biaya Keseluruhan
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseValue,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
{formatCurrency(expenseRequestTotal)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* Detail expense realization */}
|
||||
<View
|
||||
minPresenceAhead={80}
|
||||
style={ExpensePDFStyle.expenseDetailContainer}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.expenseDetailTitle}>
|
||||
Rincian Realisasi Biaya Operasional
|
||||
</Text>
|
||||
|
||||
{expense?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
|
||||
let expenseRealizationTotal = 0;
|
||||
|
||||
kandangExpense.realisasi?.forEach(
|
||||
(item) => (expenseRealizationTotal += item.qty * item.price)
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
key={kandangExpenseIdx}
|
||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
||||
{kandangExpense.kandang_id && kandangExpense.name
|
||||
? `Biaya ${kandangExpense.name}`
|
||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
||||
</Text>
|
||||
|
||||
<View style={ExpensePDFStyle.kandangExpenseTable}>
|
||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Nonstock
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Kuantitas
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Harga Satuan
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Catatan
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{kandangExpense.realisasi?.map((realisasi, realisasiIdx) => (
|
||||
<View
|
||||
key={realisasiIdx}
|
||||
style={ExpensePDFStyle.kandangExpenseTableRow}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{realisasi.nonstock.name}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{formatNumber(realisasi.qty)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{formatCurrency(realisasi.price)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
||||
]}
|
||||
>
|
||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
||||
{realisasi.notes}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseCaption,
|
||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
Total Biaya Keseluruhan
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseValue,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
||||
>
|
||||
{formatCurrency(expenseRealizationTotal)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
<View style={ExpensePDFStyle.footer} fixed>
|
||||
<Link
|
||||
src={`${process.env.NEXT_PUBLIC_LTI_URL}expense/detail?expenseId=${expense?.id}`}
|
||||
>
|
||||
{expense?.po_number}
|
||||
</Link>
|
||||
|
||||
<Text
|
||||
render={({ pageNumber, totalPages }) =>
|
||||
`${pageNumber} / ${totalPages}`
|
||||
}
|
||||
fixed
|
||||
<View key={idx} style={{ marginBottom: 12 }}>
|
||||
<PdfTypography size='h3' style={{ paddingLeft: 12 }}>
|
||||
{idx + 1}) {kandangName}
|
||||
</PdfTypography>
|
||||
{pengajuans.length > 0 ? (
|
||||
<PdfTable
|
||||
columns={getPengajuanColumns()}
|
||||
data={pengajuans}
|
||||
showFooter={true}
|
||||
footerLabel='Total'
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.emptyText}>
|
||||
Tidak ada item pengajuan untuk kandang ini.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Rincian Realisasi Section */}
|
||||
<View style={styles.tableSection}>
|
||||
<Text style={styles.tableTitle}>2. Rincian Realisasi Biaya</Text>
|
||||
{kandangs.length === 0 ? (
|
||||
<Text style={styles.emptyText}>Tidak ada data realisasi.</Text>
|
||||
) : (
|
||||
kandangs.map((kandang, idx) => {
|
||||
const realisasi = kandang.realisasi || [];
|
||||
const kandangName =
|
||||
kandang.kandang_id && kandang.name
|
||||
? kandang.name
|
||||
: expense?.location?.name || 'Umum';
|
||||
|
||||
return (
|
||||
<View key={idx} style={{ marginBottom: 12 }}>
|
||||
<PdfTypography size='h3' style={{ paddingLeft: 12 }}>
|
||||
{idx + 1}) {kandangName}
|
||||
</PdfTypography>
|
||||
{realisasi.length > 0 ? (
|
||||
<PdfTable
|
||||
columns={getRealisasiColumns()}
|
||||
data={realisasi}
|
||||
showFooter={true}
|
||||
footerLabel='Total'
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.emptyText}>
|
||||
Tidak ada item realisasi untuk kandang ini.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</View>
|
||||
|
||||
<PdfPageNumber />
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user