mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor ExpensePDF component for improved readability
This commit is contained in:
@@ -1,212 +1,154 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import React from 'react';
|
||||||
Document,
|
import { Document, Page, StyleSheet, View, Text } from '@react-pdf/renderer';
|
||||||
Image,
|
|
||||||
Link,
|
|
||||||
Page,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from '@react-pdf/renderer';
|
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
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 {
|
interface ExpensePDFProps {
|
||||||
expense?: Expense;
|
expense?: Expense;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpensePDFStyle = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
page: {
|
page: {
|
||||||
paddingTop: 24,
|
fontSize: 10,
|
||||||
paddingBottom: 64,
|
fontFamily: 'Helvetica',
|
||||||
paddingHorizontal: 32,
|
padding: 20,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
},
|
},
|
||||||
|
titleSection: {
|
||||||
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,
|
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
|
parameterContainer: {
|
||||||
title: {
|
|
||||||
marginTop: 16,
|
|
||||||
fontSize: 16,
|
|
||||||
lineHeight: '150%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontFamily: 'Times-Roman',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
|
|
||||||
footer: {
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
flexWrap: 'wrap',
|
||||||
alignItems: 'center',
|
marginBottom: 8,
|
||||||
paddingHorizontal: 32,
|
},
|
||||||
|
infoTableSection: {
|
||||||
position: 'absolute',
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
infoTableTitle: {
|
||||||
fontSize: 10,
|
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',
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 6,
|
||||||
|
color: '#333',
|
||||||
},
|
},
|
||||||
generalInfoTableValueText: {},
|
tableSection: {
|
||||||
|
marginBottom: 12,
|
||||||
// expense detail table
|
|
||||||
expenseDetailContainer: {
|
|
||||||
width: '100%',
|
|
||||||
marginTop: 12,
|
|
||||||
},
|
},
|
||||||
expenseDetailTitle: {
|
tableTitle: {
|
||||||
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: {
|
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 6,
|
||||||
|
color: '#333',
|
||||||
},
|
},
|
||||||
kandangExpenseTableFooterColTotalExpenseCaption: {
|
emptyText: {
|
||||||
width: '40%',
|
fontSize: 8,
|
||||||
paddingVertical: 6,
|
color: '#666',
|
||||||
paddingHorizontal: 8,
|
fontStyle: 'italic',
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
kandangExpenseTableFooterColTotalExpenseValue: {
|
|
||||||
width: '60%',
|
|
||||||
paddingVertical: 6,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
// utils
|
|
||||||
doubleDivider: {
|
|
||||||
width: '100%',
|
|
||||||
height: 6,
|
|
||||||
borderTop: '2px solid black',
|
|
||||||
borderBottom: '2px solid black',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
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 =
|
const isLatestApprovalRejected =
|
||||||
expense?.latest_approval?.action === 'REJECTED';
|
expense?.latest_approval?.action === 'REJECTED';
|
||||||
const isExpenseRealized =
|
const isExpenseRealized =
|
||||||
expense?.latest_approval?.step_number &&
|
expense?.latest_approval?.step_number &&
|
||||||
expense?.latest_approval.step_number >= 5;
|
expense?.latest_approval.step_number >= 5;
|
||||||
|
|
||||||
const realizationStatus = isExpenseRealized
|
const realizationStatus = isExpenseRealized
|
||||||
? 'Sudah Realisasi'
|
? 'Sudah Realisasi'
|
||||||
: 'Belum Realisasi';
|
: 'Belum Realisasi';
|
||||||
|
|
||||||
const rows = [
|
return [
|
||||||
{ label: 'Nomor PO', value: expense?.po_number },
|
{ label: 'Nomor PO', value: expense?.po_number || '-' },
|
||||||
{ label: 'Nomor Referensi', value: expense?.reference_number },
|
{ label: 'Nomor Referensi', value: expense?.reference_number || '-' },
|
||||||
{
|
{
|
||||||
label: 'Kategori',
|
label: 'Kategori',
|
||||||
value:
|
value:
|
||||||
@@ -214,9 +156,9 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
? 'Biaya Operasional'
|
? 'Biaya Operasional'
|
||||||
: expense?.category === 'NON-BOP'
|
: expense?.category === 'NON-BOP'
|
||||||
? 'Non Biaya Operasional'
|
? 'Non Biaya Operasional'
|
||||||
: '',
|
: '-',
|
||||||
},
|
},
|
||||||
{ label: 'Lokasi', value: expense?.location.name },
|
{ label: 'Lokasi', value: expense?.location?.name || '-' },
|
||||||
{
|
{
|
||||||
label: 'Kandang',
|
label: 'Kandang',
|
||||||
value:
|
value:
|
||||||
@@ -227,7 +169,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
.join(', ')
|
.join(', ')
|
||||||
: '-',
|
: '-',
|
||||||
},
|
},
|
||||||
{ label: 'Vendor', value: expense?.supplier.name },
|
{ label: 'Vendor', value: expense?.supplier?.name || '-' },
|
||||||
{
|
{
|
||||||
label: 'Tanggal Transaksi',
|
label: 'Tanggal Transaksi',
|
||||||
value: formatDate(expense?.transaction_date, 'DD MMMM YYYY'),
|
value: formatDate(expense?.transaction_date, 'DD MMMM YYYY'),
|
||||||
@@ -238,12 +180,12 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
? formatDate(expense?.realization_date, 'DD MMMM YYYY')
|
? 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',
|
label: 'Nominal Biaya',
|
||||||
value: formatCurrency(
|
value: formatCurrency(
|
||||||
expense?.latest_approval.step_number === 5 ||
|
expense?.latest_approval?.step_number === 5 ||
|
||||||
expense?.latest_approval.step_number === 6
|
expense?.latest_approval?.step_number === 6
|
||||||
? (expense?.total_realisasi ?? 0)
|
? (expense?.total_realisasi ?? 0)
|
||||||
: (expense?.total_pengajuan ?? 0)
|
: (expense?.total_pengajuan ?? 0)
|
||||||
),
|
),
|
||||||
@@ -263,401 +205,136 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
label: 'Status Biaya',
|
label: 'Status Biaya',
|
||||||
value: isLatestApprovalRejected
|
value: isLatestApprovalRejected
|
||||||
? 'Ditolak'
|
? '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 (
|
return (
|
||||||
<Document>
|
<Document>
|
||||||
<Page style={ExpensePDFStyle.page}>
|
<Page style={styles.page} size='A4'>
|
||||||
<View>
|
{/* Title Section */}
|
||||||
<View style={ExpensePDFStyle.companyInfoHeader}>
|
<View style={styles.titleSection}>
|
||||||
<Image
|
<PdfTypography size='h1' variant='primary'>
|
||||||
style={ExpensePDFStyle.companyLogo}
|
Laporan{' '}
|
||||||
src='/assets/img/lti-logo.png'
|
{expense?.category === 'BOP'
|
||||||
/>
|
? 'Biaya Operasional'
|
||||||
|
: 'Non-Biaya Operasional'}
|
||||||
<Text style={ExpensePDFStyle.companyInfoHeaderDate}>
|
</PdfTypography>
|
||||||
{formatDate(Date.now(), 'DD MMMM YYYY')}
|
<PdfTypography size='h2'>{expense?.po_number || '-'}</PdfTypography>
|
||||||
</Text>
|
<View style={styles.parameterContainer}>
|
||||||
</View>
|
<PdfParamBadge>
|
||||||
|
Tanggal: {formatDate(Date.now(), 'DD MMMM YYYY')}
|
||||||
<View>
|
</PdfParamBadge>
|
||||||
<Text style={ExpensePDFStyle.companyName}>
|
<PdfParamBadge>
|
||||||
PT LUMBUNG TELUR INDONESIA
|
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||||
</Text>
|
</PdfParamBadge>
|
||||||
<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>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={ExpensePDFStyle.title}>
|
{/* Info Table Section */}
|
||||||
Laporan{' '}
|
<View style={styles.infoTableSection}>
|
||||||
{expense?.category === 'BOP'
|
<Text style={styles.infoTableTitle}>Informasi Biaya</Text>
|
||||||
? 'Biaya Operasional'
|
<PdfTable columns={getInfoTableColumns()} data={infoRows} />
|
||||||
: '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>
|
|
||||||
</View>
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTableColSeparator}>
|
|
||||||
<Text>:</Text>
|
|
||||||
</View>
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTableColValue}>
|
|
||||||
<Text style={ExpensePDFStyle.generalInfoTableValueText}>
|
|
||||||
{row.value}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Detail expense request */}
|
{/* Rincian Pengajuan Section */}
|
||||||
<View
|
<View style={styles.tableSection}>
|
||||||
minPresenceAhead={80}
|
<Text style={styles.tableTitle}>1. Rincian Pengajuan Biaya</Text>
|
||||||
style={ExpensePDFStyle.expenseDetailContainer}
|
{kandangs.length === 0 ? (
|
||||||
>
|
<Text style={styles.emptyText}>Tidak ada data pengajuan.</Text>
|
||||||
<Text style={ExpensePDFStyle.expenseDetailTitle}>
|
) : (
|
||||||
Rincian Pengajuan Biaya Operasional
|
kandangs.map((kandang, idx) => {
|
||||||
</Text>
|
const pengajuans = kandang.pengajuans || [];
|
||||||
|
const kandangName =
|
||||||
|
kandang.kandang_id && kandang.name
|
||||||
|
? kandang.name
|
||||||
|
: expense?.location?.name || 'Umum';
|
||||||
|
|
||||||
{expense?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
|
return (
|
||||||
let expenseRequestTotal = 0;
|
<View key={idx} style={{ marginBottom: 12 }}>
|
||||||
|
<PdfTypography size='h3' style={{ paddingLeft: 12 }}>
|
||||||
kandangExpense.pengajuans?.forEach(
|
{idx + 1}) {kandangName}
|
||||||
(item) => (expenseRequestTotal += item.qty * item.price)
|
</PdfTypography>
|
||||||
);
|
{pengajuans.length > 0 ? (
|
||||||
|
<PdfTable
|
||||||
return (
|
columns={getPengajuanColumns()}
|
||||||
<View
|
data={pengajuans}
|
||||||
key={kandangExpenseIdx}
|
showFooter={true}
|
||||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
footerLabel='Total'
|
||||||
>
|
/>
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
) : (
|
||||||
{kandangExpense.kandang_id && kandangExpense.name
|
<Text style={styles.emptyText}>
|
||||||
? `Biaya ${kandangExpense.name}`
|
Tidak ada item pengajuan untuk kandang ini.
|
||||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
</Text>
|
||||||
</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>
|
);
|
||||||
);
|
})
|
||||||
})}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Detail expense realization */}
|
{/* Rincian Realisasi Section */}
|
||||||
<View
|
<View style={styles.tableSection}>
|
||||||
minPresenceAhead={80}
|
<Text style={styles.tableTitle}>2. Rincian Realisasi Biaya</Text>
|
||||||
style={ExpensePDFStyle.expenseDetailContainer}
|
{kandangs.length === 0 ? (
|
||||||
>
|
<Text style={styles.emptyText}>Tidak ada data realisasi.</Text>
|
||||||
<Text style={ExpensePDFStyle.expenseDetailTitle}>
|
) : (
|
||||||
Rincian Realisasi Biaya Operasional
|
kandangs.map((kandang, idx) => {
|
||||||
</Text>
|
const realisasi = kandang.realisasi || [];
|
||||||
|
const kandangName =
|
||||||
|
kandang.kandang_id && kandang.name
|
||||||
|
? kandang.name
|
||||||
|
: expense?.location?.name || 'Umum';
|
||||||
|
|
||||||
{expense?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
|
return (
|
||||||
let expenseRealizationTotal = 0;
|
<View key={idx} style={{ marginBottom: 12 }}>
|
||||||
|
<PdfTypography size='h3' style={{ paddingLeft: 12 }}>
|
||||||
kandangExpense.realisasi?.forEach(
|
{idx + 1}) {kandangName}
|
||||||
(item) => (expenseRealizationTotal += item.qty * item.price)
|
</PdfTypography>
|
||||||
);
|
{realisasi.length > 0 ? (
|
||||||
|
<PdfTable
|
||||||
return (
|
columns={getRealisasiColumns()}
|
||||||
<View
|
data={realisasi}
|
||||||
key={kandangExpenseIdx}
|
showFooter={true}
|
||||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
footerLabel='Total'
|
||||||
>
|
/>
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
) : (
|
||||||
{kandangExpense.kandang_id && kandangExpense.name
|
<Text style={styles.emptyText}>
|
||||||
? `Biaya ${kandangExpense.name}`
|
Tidak ada item realisasi untuk kandang ini.
|
||||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
</Text>
|
||||||
</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>
|
</View>
|
||||||
|
|
||||||
<View style={ExpensePDFStyle.footer} fixed>
|
<PdfPageNumber />
|
||||||
<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>
|
|
||||||
</Page>
|
</Page>
|
||||||
</Document>
|
</Document>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user