mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'feat/create-pdf-component' into 'development'
[FEAT/FE] Create PDF Table Component See merge request mbugroup/lti-web-client!270
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { View, StyleSheet } from '@react-pdf/renderer';
|
||||
import { PdfThead, PdfColumn } from './PdfThead';
|
||||
import { PdfTbody, PdfTbodyCell } from './PdfTbody';
|
||||
import { PdfTfoot, PdfTfootCell } from './PdfTfoot';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
table: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
marginBottom: 15,
|
||||
},
|
||||
});
|
||||
|
||||
interface PdfTableProps {
|
||||
columns: PdfColumn[];
|
||||
data: PdfTbodyCell[][];
|
||||
footer?: PdfTfootCell[];
|
||||
footerLabel?: string;
|
||||
firstRow?: {
|
||||
valueKey: string;
|
||||
value: number;
|
||||
align?: 'right';
|
||||
color?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const PdfTable = ({
|
||||
columns,
|
||||
data,
|
||||
footer,
|
||||
footerLabel = 'Total',
|
||||
firstRow,
|
||||
}: PdfTableProps) => {
|
||||
return (
|
||||
<View style={styles.table}>
|
||||
<PdfThead columns={columns} />
|
||||
<PdfTbody columns={columns} rows={data} firstRow={firstRow} />
|
||||
{footer && footer.length > 0 && (
|
||||
<PdfTfoot columns={columns} cells={footer} label={footerLabel} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
'use client';
|
||||
|
||||
import { Text, View, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
export interface PdfColumn {
|
||||
key: string;
|
||||
header: string;
|
||||
flex: number;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
export interface PdfTbodyCell {
|
||||
key: string;
|
||||
value: string | number | React.ReactNode;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
color?: string;
|
||||
formatAs?: 'text' | 'date' | 'currency' | 'number';
|
||||
formatDate?: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tableRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tableBorderBottom: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
},
|
||||
tableCell: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'left',
|
||||
},
|
||||
tableCellLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
borderRightWidth: 0,
|
||||
},
|
||||
tableCellRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'right',
|
||||
},
|
||||
tableCellCenter: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellNo: {
|
||||
flex: 0.5,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
interface PdfTbodyProps {
|
||||
columns: PdfColumn[];
|
||||
rows: PdfTbodyCell[][];
|
||||
firstRow?: {
|
||||
valueKey: string;
|
||||
value: number;
|
||||
align?: 'right';
|
||||
color?: string;
|
||||
};
|
||||
formatDate?: (date: string, format: string) => string;
|
||||
formatNumber?: (num: number) => string;
|
||||
formatCurrency?: (num: number) => string;
|
||||
}
|
||||
|
||||
export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
|
||||
return (
|
||||
<>
|
||||
{/* First Row */}
|
||||
{firstRow && (
|
||||
<View style={[styles.tableRow, styles.tableBorderBottom]}>
|
||||
{columns.map((column, index) => {
|
||||
const isLastColumn = index === columns.length - 1;
|
||||
const isfirstRowColumn = column.key === firstRow.valueKey;
|
||||
const align = column.align || 'center';
|
||||
|
||||
const cellStyle =
|
||||
column.key === 'no'
|
||||
? [styles.tableCellNo, { flex: column.flex }]
|
||||
: isfirstRowColumn
|
||||
? [
|
||||
styles.tableCellRight,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: firstRow.color || 'black',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: align === 'right'
|
||||
? [
|
||||
styles.tableCellRight,
|
||||
{
|
||||
flex: column.flex,
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: align === 'center'
|
||||
? [
|
||||
styles.tableCellCenter,
|
||||
{
|
||||
flex: column.flex,
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: isLastColumn
|
||||
? [
|
||||
styles.tableCellLast,
|
||||
{
|
||||
flex: column.flex,
|
||||
borderRightWidth: 0,
|
||||
},
|
||||
]
|
||||
: [styles.tableCell, { flex: column.flex }];
|
||||
|
||||
return (
|
||||
<View key={column.key} style={cellStyle}>
|
||||
<Text>{isfirstRowColumn ? firstRow.value : ''}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Data Rows */}
|
||||
{rows.map((row, rowIndex) => {
|
||||
const isLastRow = rowIndex === rows.length - 1;
|
||||
|
||||
return (
|
||||
<View
|
||||
key={rowIndex}
|
||||
style={[
|
||||
styles.tableRow,
|
||||
!isLastRow ? styles.tableBorderBottom : {},
|
||||
]}
|
||||
>
|
||||
{columns.map((column, colIndex) => {
|
||||
const cell = row.find((c) => c.key === column.key);
|
||||
const isLastColumn = colIndex === columns.length - 1;
|
||||
const align = cell?.align || column.align || 'center';
|
||||
|
||||
const cellStyle =
|
||||
column.key === 'no'
|
||||
? [styles.tableCellNo, { flex: column.flex }]
|
||||
: align === 'right'
|
||||
? [
|
||||
styles.tableCellRight,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: cell?.color || 'black',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: align === 'center'
|
||||
? [
|
||||
styles.tableCellCenter,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: cell?.color || 'black',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: isLastColumn
|
||||
? [
|
||||
styles.tableCellLast,
|
||||
{ flex: column.flex, borderRightWidth: 0 },
|
||||
]
|
||||
: [
|
||||
styles.tableCell,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: cell?.color || 'black',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<View key={column.key} style={cellStyle}>
|
||||
{cell?.value !== undefined &&
|
||||
cell?.value !== null &&
|
||||
cell?.value !== '' ? (
|
||||
typeof cell.value === 'object' ? (
|
||||
cell.value
|
||||
) : (
|
||||
<Text>{String(cell.value)}</Text>
|
||||
)
|
||||
) : (
|
||||
<Text>-</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
'use client';
|
||||
|
||||
import { Text, View, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
export interface PdfColumn {
|
||||
key: string;
|
||||
header: string;
|
||||
flex: number;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
export interface PdfTfootCell {
|
||||
key: string;
|
||||
value: string | number;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
flex?: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tableRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
summaryRow: {
|
||||
backgroundColor: '#F0F0F0',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
tableCell: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'left',
|
||||
},
|
||||
tableCellLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
borderRightWidth: 0,
|
||||
},
|
||||
tableCellRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'right',
|
||||
},
|
||||
tableCellCenter: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellNo: {
|
||||
flex: 0.5,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
interface PdfTfootProps {
|
||||
columns: PdfColumn[];
|
||||
cells: PdfTfootCell[];
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const PdfTfoot = ({
|
||||
columns,
|
||||
cells,
|
||||
label = 'Total',
|
||||
}: PdfTfootProps) => {
|
||||
return (
|
||||
<View style={[styles.tableRow, styles.summaryRow]}>
|
||||
{columns.map((column, index) => {
|
||||
const isLastColumn = index === columns.length - 1;
|
||||
const cellData = cells.find((c) => c.key === column.key);
|
||||
|
||||
const cellStyle =
|
||||
column.key === 'no'
|
||||
? [
|
||||
styles.tableCellNo,
|
||||
{ flex: column.flex, borderRightWidth: isLastColumn ? 0 : 1 },
|
||||
]
|
||||
: cellData?.align === 'right'
|
||||
? [
|
||||
styles.tableCellRight,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: cellData?.color || 'black',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: cellData?.align === 'center'
|
||||
? [
|
||||
styles.tableCellCenter,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: cellData?.color || 'black',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: isLastColumn
|
||||
? [styles.tableCellLast, { flex: column.flex }]
|
||||
: [
|
||||
styles.tableCell,
|
||||
{
|
||||
flex: column.flex,
|
||||
color: cellData?.color || 'black',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<View key={column.key} style={cellStyle}>
|
||||
<Text>{column.key === 'no' ? label : cellData?.value || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
'use client';
|
||||
|
||||
import { Text, View, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
export interface PdfColumn {
|
||||
key: string;
|
||||
header: string;
|
||||
flex: number;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tableRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tableHeader: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
},
|
||||
tableCellHeader: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellHeaderRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
textAlign: 'right',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
});
|
||||
|
||||
interface PdfTheadProps {
|
||||
columns: PdfColumn[];
|
||||
}
|
||||
|
||||
export const PdfThead = ({ columns }: PdfTheadProps) => {
|
||||
return (
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
{columns.map((column, index) => {
|
||||
const align = column.align || 'center';
|
||||
const isLastColumn = index === columns.length - 1;
|
||||
|
||||
const cellStyle =
|
||||
align === 'right'
|
||||
? [
|
||||
styles.tableCellHeaderRight,
|
||||
{
|
||||
flex: column.flex,
|
||||
textAlign: 'right' as const,
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
]
|
||||
: [
|
||||
styles.tableCellHeader,
|
||||
{
|
||||
flex: column.flex,
|
||||
textAlign: align as 'left' | 'center' | 'right',
|
||||
borderRightWidth: isLastColumn ? 0 : 1,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<View key={column.key} style={cellStyle}>
|
||||
<Text>{column.header}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export { PdfTable } from './PdfTable';
|
||||
export { PdfThead } from './PdfThead';
|
||||
export { PdfTbody } from './PdfTbody';
|
||||
export { PdfTfoot } from './PdfTfoot';
|
||||
export type { PdfColumn } from './PdfThead';
|
||||
export type { PdfTbodyCell } from './PdfTbody';
|
||||
export type { PdfTfootCell } from './PdfTfoot';
|
||||
@@ -12,6 +12,12 @@ import {
|
||||
|
||||
import { formatDate, formatCurrency, formatNumber } from '@/lib/helper';
|
||||
import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
|
||||
import {
|
||||
PdfTable,
|
||||
PdfColumn,
|
||||
PdfTbodyCell,
|
||||
PdfTfootCell,
|
||||
} from '@/components/helper/pdf/table';
|
||||
|
||||
Font.register({
|
||||
family: 'Helvetica',
|
||||
@@ -45,97 +51,6 @@ const pdfStyles = StyleSheet.create({
|
||||
marginBottom: 5,
|
||||
color: '#333333',
|
||||
},
|
||||
table: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
marginBottom: 15,
|
||||
},
|
||||
tableRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tableHeader: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
},
|
||||
tableCell: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'left',
|
||||
},
|
||||
tableCellNo: {
|
||||
flex: 0.5,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
},
|
||||
tableCellHeader: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellHeaderRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
textAlign: 'right',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
tableCellRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'right',
|
||||
},
|
||||
tableCellCenter: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableBorderBottom: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
},
|
||||
summaryRow: {
|
||||
backgroundColor: '#F0F0F0',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: '#1f74bf',
|
||||
color: '#FFFFFF',
|
||||
@@ -217,6 +132,165 @@ const getParameterText = (
|
||||
return paramsText;
|
||||
};
|
||||
|
||||
// Helper functions for PdfTable
|
||||
const getTableColumns = (): PdfColumn[] => [
|
||||
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
|
||||
{ key: 'trans_date', header: 'Tanggal DO', flex: 1.2, align: 'center' },
|
||||
{
|
||||
key: 'delivery_date',
|
||||
header: 'Tanggal Realisasi',
|
||||
flex: 1.2,
|
||||
align: 'center',
|
||||
},
|
||||
{ key: 'aging', header: 'Aging', flex: 0.8, align: 'center' },
|
||||
{ key: 'reference', header: 'Referensi', flex: 1.5, align: 'left' },
|
||||
{ key: 'vehicle_numbers', header: 'No Polisi', flex: 1.2, align: 'left' },
|
||||
{ key: 'qty', header: 'Qty', flex: 0.8, align: 'right' },
|
||||
{ key: 'weight', header: 'Berat', flex: 1, align: 'right' },
|
||||
{ key: 'average_weight', header: 'Rata-Rata', flex: 0.8, align: 'right' },
|
||||
{ key: 'unit_price', header: 'Harga/Unit', flex: 1.2, align: 'right' },
|
||||
{ key: 'final_price', header: 'Harga Akhir', flex: 1.2, align: 'right' },
|
||||
{ key: 'total_price', header: 'Total', flex: 1.2, align: 'right' },
|
||||
{ key: 'payment_amount', header: 'Pembayaran', flex: 1.2, align: 'right' },
|
||||
{ key: 'accounts_receivable', header: 'Saldo', flex: 1.2, align: 'right' },
|
||||
{ key: 'status', header: 'Keterangan', flex: 1.5, align: 'center' },
|
||||
{ key: 'pickup_info', header: 'Pengambilan', flex: 1, align: 'left' },
|
||||
{ key: 'sales_person', header: 'Sales', flex: 1.5, align: 'left' },
|
||||
];
|
||||
|
||||
const getTableData = (
|
||||
rows: CustomerPaymentReport['rows']
|
||||
): PdfTbodyCell[][] => {
|
||||
return rows.map((item, index) => [
|
||||
{ key: 'no', value: index + 1 },
|
||||
{
|
||||
key: 'trans_date',
|
||||
value: item.trans_date ? formatDate(item.trans_date, 'DD MMM YY') : '-',
|
||||
},
|
||||
{
|
||||
key: 'delivery_date',
|
||||
value: item.delivery_date
|
||||
? formatDate(item.delivery_date, 'DD MMM YY')
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
key: 'aging',
|
||||
value:
|
||||
item.aging_day != null ? `${formatNumber(item.aging_day)} hari` : '-',
|
||||
},
|
||||
{ key: 'reference', value: item.reference || '-' },
|
||||
{
|
||||
key: 'vehicle_numbers',
|
||||
value:
|
||||
Array.isArray(item.vehicle_numbers) && item.vehicle_numbers.length > 0
|
||||
? item.vehicle_numbers.join(', ')
|
||||
: '-',
|
||||
},
|
||||
{ key: 'qty', value: formatNumber(item.qty), align: 'right' },
|
||||
{ key: 'weight', value: formatNumber(item.weight), align: 'right' },
|
||||
{
|
||||
key: 'average_weight',
|
||||
value: formatNumber(item.average_weight),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'unit_price',
|
||||
value: formatCurrency(item.unit_price),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'final_price',
|
||||
value: formatCurrency(item.final_price),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'total_price',
|
||||
value: formatCurrency(item.total_price),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'payment_amount',
|
||||
value: formatCurrency(item.payment_amount),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'accounts_receivable',
|
||||
value: formatCurrency(item.accounts_receivable),
|
||||
align: 'right',
|
||||
color: item.accounts_receivable < 0 ? '#DC2626' : undefined,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
value: item.status ? (
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.badge,
|
||||
item.status === 'LUNAS'
|
||||
? pdfStyles.badgeLunas
|
||||
: pdfStyles.badgeBelumLunas,
|
||||
]}
|
||||
>
|
||||
<Text>{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}</Text>
|
||||
</View>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'pickup_info',
|
||||
value:
|
||||
Array.isArray(item.pickup_info) && item.pickup_info.length > 0
|
||||
? item.pickup_info.join(', ')
|
||||
: '-',
|
||||
},
|
||||
{ key: 'sales_person', value: item.sales_person || '-' },
|
||||
]);
|
||||
};
|
||||
|
||||
const getTableFooter = (
|
||||
summary: CustomerPaymentReport['summary']
|
||||
): PdfTfootCell[] => [
|
||||
{ key: 'no', value: 'Total' },
|
||||
{ key: 'trans_date', value: '' },
|
||||
{ key: 'delivery_date', value: '' },
|
||||
{ key: 'aging', value: '' },
|
||||
{ key: 'reference', value: '' },
|
||||
{ key: 'vehicle_numbers', value: '' },
|
||||
{ key: 'qty', value: formatNumber(summary?.total_qty || 0), align: 'right' },
|
||||
{
|
||||
key: 'weight',
|
||||
value: formatNumber(summary?.total_weight || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'average_weight', value: '' },
|
||||
{ key: 'unit_price', value: '' },
|
||||
{
|
||||
key: 'final_price',
|
||||
value: formatCurrency(summary?.total_final_amount || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'total_price',
|
||||
value: formatCurrency(summary?.total_grand_amount || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'payment_amount',
|
||||
value: formatCurrency(summary?.total_payment || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'accounts_receivable',
|
||||
value: formatCurrency(summary?.total_accounts_receivable || 0),
|
||||
align: 'right',
|
||||
color:
|
||||
(summary?.total_accounts_receivable || 0) < 0 ? '#DC2626' : undefined,
|
||||
},
|
||||
{ key: 'status', value: '' },
|
||||
{ key: 'pickup_info', value: '' },
|
||||
{ key: 'sales_person', value: '' },
|
||||
];
|
||||
|
||||
const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
|
||||
return (
|
||||
<Document>
|
||||
@@ -269,329 +343,27 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
|
||||
</View>
|
||||
|
||||
{/* Table */}
|
||||
<View style={pdfStyles.table}>
|
||||
{/* Table Header */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.5 }]}>
|
||||
<Text>No</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>Tanggal DO</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>Tanggal Realisasi</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.8 }]}>
|
||||
<Text>Aging</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
|
||||
<Text>Referensi</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>No Polisi</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
|
||||
<Text>Qty</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
|
||||
<Text>Berat</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
|
||||
<Text>Rata-Rata</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Harga/Unit</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Harga Akhir</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Total</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Pembayaran</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Saldo</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
|
||||
<Text>Keterangan</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Pengambilan</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{ flex: 1.5, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text>Sales</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Table Body */}
|
||||
<>
|
||||
{/* Initial Balance Row */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.2,
|
||||
color:
|
||||
typeof customerReport.initial_balance === 'number' &&
|
||||
customerReport.initial_balance < 0
|
||||
? 'red'
|
||||
: 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(customerReport.initial_balance || 0)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCell,
|
||||
{ flex: 1.5, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Data Rows */}
|
||||
{customerReport.rows.map((item, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
pdfStyles.tableRow,
|
||||
index < customerReport.rows.length - 1
|
||||
? pdfStyles.tableBorderBottom
|
||||
: {},
|
||||
]}
|
||||
>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text>{index + 1}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{item.trans_date
|
||||
? formatDate(item.trans_date, 'DD MMM YY')
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{item.delivery_date
|
||||
? formatDate(item.delivery_date, 'DD MMM YY')
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
|
||||
<Text>
|
||||
{item.aging_day != null
|
||||
? `${formatNumber(item.aging_day)} hari`
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text>{item.reference || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{Array.isArray(item.vehicle_numbers)
|
||||
? item.vehicle_numbers.length > 0
|
||||
? item.vehicle_numbers.join(', ')
|
||||
: '-'
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text>{formatNumber(item.qty)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text>{formatNumber(item.weight)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text>{formatNumber(item.average_weight)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(item.unit_price)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(item.final_price)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(item.total_price)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(item.payment_amount)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text style={pdfStyles.textError}>
|
||||
{formatCurrency(item.accounts_receivable)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.5 }]}>
|
||||
{item.status ? (
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.badge,
|
||||
item.status === 'LUNAS'
|
||||
? pdfStyles.badgeLunas
|
||||
: pdfStyles.badgeBelumLunas,
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text>-</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>
|
||||
{Array.isArray(item.pickup_info)
|
||||
? item.pickup_info.length > 0
|
||||
? item.pickup_info.join(', ')
|
||||
: '-'
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCell,
|
||||
{ flex: 1.5, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text>{item.sales_person || '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</>
|
||||
|
||||
{/* Summary Row */}
|
||||
{customerReport.summary && (
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.summaryRow]}>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text>Total</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 0.8 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text>{formatNumber(customerReport.summary.total_qty)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text>
|
||||
{formatNumber(customerReport.summary.total_weight)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{formatCurrency(customerReport.summary.total_final_amount)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{formatCurrency(customerReport.summary.total_grand_amount)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{formatCurrency(customerReport.summary.total_payment)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text style={pdfStyles.textError}>
|
||||
{formatCurrency(
|
||||
customerReport.summary.total_accounts_receivable
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCell,
|
||||
{ flex: 1.5, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<PdfTable
|
||||
columns={getTableColumns()}
|
||||
data={getTableData(customerReport.rows)}
|
||||
footer={
|
||||
customerReport.summary
|
||||
? getTableFooter(customerReport.summary)
|
||||
: undefined
|
||||
}
|
||||
firstRow={
|
||||
typeof customerReport.initial_balance === 'number' &&
|
||||
customerReport.initial_balance !== 0
|
||||
? {
|
||||
valueKey: 'accounts_receivable',
|
||||
value: customerReport.initial_balance,
|
||||
align: 'right',
|
||||
color:
|
||||
customerReport.initial_balance < 0 ? '#DC2626' : 'black',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Page>
|
||||
))}
|
||||
</Document>
|
||||
|
||||
@@ -11,6 +11,11 @@ import {
|
||||
} from '@react-pdf/renderer';
|
||||
import { LogisticPurchasePerSupplierReport } from '@/types/api/report/logistic-stock';
|
||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import {
|
||||
PdfTable,
|
||||
PdfColumn,
|
||||
PdfTbodyCell,
|
||||
} from '@/components/helper/pdf/table';
|
||||
|
||||
Font.register({
|
||||
family: 'Helvetica',
|
||||
@@ -39,117 +44,6 @@ const pdfStyles = StyleSheet.create({
|
||||
marginBottom: 8,
|
||||
color: '#1f74bf',
|
||||
},
|
||||
table: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
marginBottom: 15,
|
||||
},
|
||||
tableRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tableHeader: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
},
|
||||
tableCell: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'left',
|
||||
},
|
||||
tableCellNo: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
},
|
||||
tableCellHeader: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellHeaderRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
textAlign: 'right',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
tableCellHeaderLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'right',
|
||||
},
|
||||
tableCellCenter: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellCenterLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableBorderBottom: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
},
|
||||
supplierSection: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
supplierSectionBreak: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: '#1f74bf',
|
||||
color: '#FFFFFF',
|
||||
@@ -174,6 +68,12 @@ const pdfStyles = StyleSheet.create({
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: 8,
|
||||
},
|
||||
supplierSection: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
supplierSectionBreak: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
});
|
||||
|
||||
interface PurchasesPerSupplierExportParams {
|
||||
@@ -218,6 +118,85 @@ const getParameterText = (
|
||||
return paramsText;
|
||||
};
|
||||
|
||||
// Helper functions for PdfTable
|
||||
const getTableColumns = (): PdfColumn[] => [
|
||||
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
|
||||
{ key: 'receive_date', header: 'Tanggal Terima', flex: 1, align: 'center' },
|
||||
{ key: 'po_date', header: 'Tanggal PO', flex: 1, align: 'center' },
|
||||
{ key: 'po_number', header: 'Referensi', flex: 1, align: 'left' },
|
||||
{ key: 'product', header: 'Produk', flex: 1, align: 'left' },
|
||||
{ key: 'warehouse', header: 'Tujuan', flex: 1, align: 'left' },
|
||||
{ key: 'qty', header: 'Qty', flex: 0.8, align: 'right' },
|
||||
{ key: 'unit_price', header: 'Harga Beli', flex: 1.2, align: 'right' },
|
||||
{
|
||||
key: 'purchase_value',
|
||||
header: 'Nilai Pembelian',
|
||||
flex: 1.5,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'transport_price',
|
||||
header: 'Biaya Transport',
|
||||
flex: 1.2,
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'total_amount', header: 'Total', flex: 1.5, align: 'right' },
|
||||
{ key: 'expedition', header: 'Armada', flex: 1.2, align: 'center' },
|
||||
{ key: 'delivery_number', header: 'Surat Jalan', flex: 1, align: 'left' },
|
||||
];
|
||||
|
||||
const getTableData = (
|
||||
rows: LogisticPurchasePerSupplierReport['rows']
|
||||
): PdfTbodyCell[][] => {
|
||||
return rows.map((item, index) => [
|
||||
{ key: 'no', value: index + 1, align: 'center' },
|
||||
{
|
||||
key: 'receive_date',
|
||||
value: formatDate(item.receive_date, 'DD-MMM-YYYY'),
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
key: 'po_date',
|
||||
value: formatDate(item.po_date, 'DD-MMM-YYYY'),
|
||||
align: 'center',
|
||||
},
|
||||
{ key: 'po_number', value: item.po_number || '-' },
|
||||
{ key: 'product', value: item.product?.name || '-' },
|
||||
{ key: 'warehouse', value: item.warehouse?.name || '-' },
|
||||
{ key: 'qty', value: formatNumber(item.qty || 0), align: 'right' },
|
||||
{
|
||||
key: 'unit_price',
|
||||
value: formatCurrency(item.unit_price || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'purchase_value',
|
||||
value: formatCurrency(item.purchase_value || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'transport_price',
|
||||
value: formatCurrency(item.transport_unit_price || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'total_amount',
|
||||
value: formatCurrency(item.total_amount || 0),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'expedition',
|
||||
value: (
|
||||
<View style={pdfStyles.badge}>
|
||||
<Text>{item.expedition || '-'}</Text>
|
||||
</View>
|
||||
),
|
||||
align: 'center',
|
||||
},
|
||||
{ key: 'delivery_number', value: item.delivery_number || '-' },
|
||||
]);
|
||||
};
|
||||
|
||||
const createPDFDocument = (
|
||||
supplierReports: LogisticPurchasePerSupplierReport[],
|
||||
params: PurchasesPerSupplierExportParams['params']
|
||||
@@ -266,114 +245,10 @@ const createPDFDocument = (
|
||||
{supplierReport.supplier.name}
|
||||
</Text>
|
||||
|
||||
<View style={pdfStyles.table}>
|
||||
{/* Table Header */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.5 }]}>
|
||||
<Text>No</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellHeader}>
|
||||
<Text>Tanggal Terima</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellHeader}>
|
||||
<Text>Tanggal PO</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellHeader}>
|
||||
<Text>Referensi</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellHeader}>
|
||||
<Text>Produk</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellHeader}>
|
||||
<Text>Tujuan</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
|
||||
<Text>Qty</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Harga Beli</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Nilai Pembelian</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Biaya Transport</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Total</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>Armada</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellHeaderLast}>
|
||||
<Text>Surat Jalan</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Table Body */}
|
||||
{supplierReport.rows.map(
|
||||
(
|
||||
item: LogisticPurchasePerSupplierReport['rows'][number],
|
||||
index: number
|
||||
) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
pdfStyles.tableRow,
|
||||
index < supplierReport.rows.length - 1
|
||||
? pdfStyles.tableBorderBottom
|
||||
: {},
|
||||
]}
|
||||
>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text>{index + 1}</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCell}>
|
||||
<Text>
|
||||
{formatDate(item.receive_date, 'DD-MMM-YYYY')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCell}>
|
||||
<Text>{formatDate(item.po_date, 'DD-MMM-YYYY')}</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCell}>
|
||||
<Text>{item.po_number || '-'}</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCell}>
|
||||
<Text>{item.product?.name || '-'}</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCell}>
|
||||
<Text>{item.warehouse?.name || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text>{formatNumber(item.qty || 0)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(item.unit_price || 0)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
|
||||
<Text>{formatCurrency(item.purchase_value || 0)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{formatCurrency(item.transport_unit_price || 0)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
|
||||
<Text>{formatCurrency(item.total_amount || 0)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<View style={pdfStyles.badge}>
|
||||
<Text>{item.expedition || '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={pdfStyles.tableCellLast}>
|
||||
<Text>{item.delivery_number || '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
<PdfTable
|
||||
columns={getTableColumns()}
|
||||
data={getTableData(supplierReport.rows)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import Menu from '@/components/menu/Menu';
|
||||
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExport';
|
||||
import toast from 'react-hot-toast';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
const PurchasesPerSupplierTab = () => {
|
||||
// ===== STATE MANAGEMENT =====
|
||||
@@ -723,27 +724,6 @@ const PurchasesPerSupplierTab = () => {
|
||||
subtitle='Laporan > Rekapitulasi Pembelian Per Supplier'
|
||||
className={{ wrapper: 'w-full', body: 'p-1!' }}
|
||||
>
|
||||
<div className='mb-4 flex justify-end gap-2 [&_button]:px-4'>
|
||||
<Button color='primary' onClick={handleSubmit}>
|
||||
Cari
|
||||
</Button>
|
||||
<Button color='warning' onClick={resetFilters}>
|
||||
Reset
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger={
|
||||
<Button color='success' isLoading={isAnyExportLoading}>
|
||||
Export
|
||||
</Button>
|
||||
}
|
||||
align='end'
|
||||
>
|
||||
<Menu className='w-32'>
|
||||
<MenuItem title='Excel' onClick={handleExportExcel} />
|
||||
<MenuItem title='PDF' onClick={handleExportPdf} />
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className='grid md:grid-cols-3 grid-cols-1 gap-4'>
|
||||
<SelectInput
|
||||
label='Area'
|
||||
@@ -848,6 +828,34 @@ const PurchasesPerSupplierTab = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-4 flex justify-end gap-2 [&_button]:px-4'>
|
||||
<Button color='primary' onClick={handleSubmit}>
|
||||
<Icon icon='heroicons:magnifying-glass' width={20} height={20} />
|
||||
Cari
|
||||
</Button>
|
||||
<Button color='warning' onClick={resetFilters}>
|
||||
<Icon icon='heroicons-outline:refresh' width={20} height={20} />
|
||||
Reset
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger={
|
||||
<Button color='success' isLoading={isAnyExportLoading}>
|
||||
Export
|
||||
<Icon
|
||||
icon='heroicons-outline:download'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
align='end'
|
||||
>
|
||||
<Menu className='w-32'>
|
||||
<MenuItem title='Excel' onClick={handleExportExcel} />
|
||||
<MenuItem title='PDF' onClick={handleExportPdf} />
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
{!isSubmitted ? (
|
||||
<div className='mt-6 text-center text-gray-500'>
|
||||
@@ -880,18 +888,25 @@ const PurchasesPerSupplierTab = () => {
|
||||
key={supplierReport.supplier.id}
|
||||
title={supplierReport.supplier.name}
|
||||
subtitle={`Total Pembelian: ${formatCurrency(totalPurchase)}`}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
className={{
|
||||
wrapper: 'w-full rounded-2xl',
|
||||
body: 'p-0',
|
||||
title:
|
||||
'py-1.5 px-3 bg-primary text-white text-lg font-normal',
|
||||
subtitle:
|
||||
'px-3 pb-1 bg-primary text-white text-sm font-normal',
|
||||
}}
|
||||
variant='bordered'
|
||||
collapsible={true}
|
||||
>
|
||||
<Table
|
||||
data={supplierReport.rows}
|
||||
columns={tableColumns}
|
||||
pageSize={10}
|
||||
pageSize={supplierReport.rows.length}
|
||||
renderFooter={supplierReport.rows.length > 0}
|
||||
className={{
|
||||
containerClassName: 'w-full',
|
||||
tableWrapperClassName: 'overflow-x-auto mt-4',
|
||||
containerClassName: 'w-full mb-0!',
|
||||
tableWrapperClassName: 'overflow-x-auto',
|
||||
tableClassName: 'w-full table-auto text-sm',
|
||||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||
headerColumnClassName:
|
||||
|
||||
@@ -15,6 +15,12 @@ import {
|
||||
HppPerKandangPerWeightRange,
|
||||
} from '@/types/api/report/hpp-per-kandang';
|
||||
import { formatDate, formatNumber, formatCurrency } from '@/lib/helper';
|
||||
import {
|
||||
PdfTable,
|
||||
PdfColumn,
|
||||
PdfTbodyCell,
|
||||
PdfTfootCell,
|
||||
} from '@/components/helper/pdf/table';
|
||||
|
||||
Font.register({
|
||||
family: 'Helvetica',
|
||||
@@ -43,85 +49,6 @@ const pdfStyles = StyleSheet.create({
|
||||
marginBottom: 8,
|
||||
color: '#1f74bf',
|
||||
},
|
||||
table: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
marginBottom: 15,
|
||||
},
|
||||
tableRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tableHeader: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
},
|
||||
tableCell: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'left',
|
||||
},
|
||||
tableCellHeader: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellHeaderRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#F5F5F5',
|
||||
textAlign: 'right',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
tableCellRight: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'right',
|
||||
},
|
||||
tableCellCenter: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableBorderBottom: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
},
|
||||
supplierSection: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
supplierSectionBreak: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
parameterBadge: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
color: '#333333',
|
||||
@@ -136,6 +63,9 @@ const pdfStyles = StyleSheet.create({
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: 8,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
});
|
||||
|
||||
interface HppPerKandangExportParams {
|
||||
@@ -192,6 +122,215 @@ const getParameterText = (params: HppPerKandangExportParams['params']) => {
|
||||
return paramsText;
|
||||
};
|
||||
|
||||
// Helper functions for PdfTable - Rekapitulasi
|
||||
const getRekapitulasiColumns = (): PdfColumn[] => [
|
||||
{ key: 'rentang_bw', header: 'Rentang BW', flex: 1.2, align: 'center' },
|
||||
{ key: 'sisa_butir', header: 'Sisa Butir', flex: 1, align: 'right' },
|
||||
{ key: 'sisa_kg', header: 'Sisa Kg', flex: 1, align: 'right' },
|
||||
{
|
||||
key: 'rata_rata_bobot',
|
||||
header: 'Rata-Rata Bobot (Kg)',
|
||||
flex: 1.2,
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'feed_supplier', header: 'Feed (Supplier)', flex: 1.5, align: 'left' },
|
||||
{ key: 'doc_supplier', header: 'DOC (Supplier)', flex: 1.2, align: 'left' },
|
||||
{
|
||||
key: 'rata_harga_doc',
|
||||
header: 'Rata-Rata Harga DOC',
|
||||
flex: 1.2,
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'hpp_telur', header: 'HPP Telur (RP/KG)', flex: 1.2, align: 'right' },
|
||||
{ key: 'nominal_sisa', header: 'Nominal Sisa', flex: 1.2, align: 'right' },
|
||||
];
|
||||
|
||||
const getRekapitulasiData = (
|
||||
perWeightRange: HppPerKandangPerWeightRange[]
|
||||
): PdfTbodyCell[][] => {
|
||||
return perWeightRange.map((group) => [
|
||||
{ key: 'rentang_bw', value: group.label, align: 'center' },
|
||||
{
|
||||
key: 'sisa_butir',
|
||||
value: formatNumber(group.egg_production_pieces),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'sisa_kg',
|
||||
value: formatNumber(group.egg_production_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'rata_rata_bobot',
|
||||
value: formatNumber(group.avg_weight_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'feed_supplier',
|
||||
value:
|
||||
group.feed_suppliers
|
||||
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
|
||||
.join(' | ') || '-',
|
||||
},
|
||||
{
|
||||
key: 'doc_supplier',
|
||||
value:
|
||||
group.doc_suppliers
|
||||
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
|
||||
.join(' | ') || '-',
|
||||
},
|
||||
{
|
||||
key: 'rata_harga_doc',
|
||||
value: formatCurrency(group.average_doc_price_rp),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'hpp_telur',
|
||||
value: formatCurrency(group.egg_hpp_rp_per_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'nominal_sisa',
|
||||
value: formatCurrency(group.egg_value_rp),
|
||||
align: 'right',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// Helper functions for PdfTable - Detail Per Kandang
|
||||
const getDetailColumns = (): PdfColumn[] => [
|
||||
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
|
||||
{ key: 'kandang', header: 'Kandang', flex: 1.5, align: 'left' },
|
||||
{ key: 'rentang_bw', header: 'Rentang BW', flex: 1, align: 'left' },
|
||||
{
|
||||
key: 'rata_rata_bobot',
|
||||
header: 'Rata-Rata Bobot (Kg)',
|
||||
flex: 1,
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'sisa_butir', header: 'Sisa Butir', flex: 0.8, align: 'right' },
|
||||
{ key: 'sisa_kg', header: 'Sisa Kg (Telur)', flex: 0.8, align: 'right' },
|
||||
{ key: 'feed_supplier', header: 'Feed (Supplier)', flex: 1.2, align: 'left' },
|
||||
{ key: 'doc_supplier', header: 'DOC (Supplier)', flex: 1, align: 'left' },
|
||||
{
|
||||
key: 'rata_harga_doc',
|
||||
header: 'Rata-Rata Harga DOC',
|
||||
flex: 1.2,
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'hpp_telur', header: 'HPP Telur (RP/KG)', flex: 1, align: 'right' },
|
||||
{ key: 'nominal_sisa', header: 'Nominal Sisa', flex: 1.2, align: 'right' },
|
||||
];
|
||||
|
||||
const getDetailData = (rows: HppPerKandangRow[]): PdfTbodyCell[][] => {
|
||||
return rows.map((item, index) => [
|
||||
{ key: 'no', value: index + 1, align: 'center' },
|
||||
{ key: 'kandang', value: item.kandang?.name || '-' },
|
||||
{
|
||||
key: 'rentang_bw',
|
||||
value: `${item.weight_range.weight_min.toFixed(2)} - ${item.weight_range.weight_max.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
key: 'rata_rata_bobot',
|
||||
value: formatNumber(item.avg_weight_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'sisa_butir',
|
||||
value: formatNumber(item.egg_production_pieces),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'sisa_kg',
|
||||
value: formatNumber(item.egg_production_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'feed_supplier',
|
||||
value:
|
||||
item.feed_suppliers
|
||||
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
|
||||
.join(' | ') || '-',
|
||||
},
|
||||
{
|
||||
key: 'doc_supplier',
|
||||
value:
|
||||
item.doc_suppliers
|
||||
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
|
||||
.join(' | ') || '-',
|
||||
},
|
||||
{
|
||||
key: 'rata_harga_doc',
|
||||
value: formatCurrency(item.average_doc_price_rp),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'hpp_telur',
|
||||
value: formatCurrency(item.egg_hpp_rp_per_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'nominal_sisa',
|
||||
value: formatCurrency(item.egg_value_rp),
|
||||
align: 'right',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const getDetailFooter = (
|
||||
summary: HppPerKandangReport['summary']
|
||||
): PdfTfootCell[] => {
|
||||
if (!summary?.total) return [];
|
||||
|
||||
const allFeedSuppliers =
|
||||
summary.total.feed_suppliers
|
||||
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
|
||||
.join(' | ') || '-';
|
||||
|
||||
const allDocSuppliers =
|
||||
summary.total.doc_suppliers
|
||||
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
|
||||
.join(' | ') || '-';
|
||||
|
||||
return [
|
||||
{ key: 'no', value: 'TOTAL' },
|
||||
{ key: 'kandang', value: 'ALL' },
|
||||
{ key: 'rentang_bw', value: '-' },
|
||||
{
|
||||
key: 'rata_rata_bobot',
|
||||
value: formatNumber(summary.total.average_weight_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'sisa_butir',
|
||||
value: formatNumber(summary.total.total_egg_production_pieces),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'sisa_kg',
|
||||
value: formatNumber(summary.total.total_egg_production_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{ key: 'feed_supplier', value: allFeedSuppliers },
|
||||
{ key: 'doc_supplier', value: allDocSuppliers },
|
||||
{
|
||||
key: 'rata_harga_doc',
|
||||
value: formatCurrency(summary.total.total_average_doc_price_rp),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'hpp_telur',
|
||||
value: formatCurrency(summary.total.average_egg_hpp_rp_per_kg),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
key: 'nominal_sisa',
|
||||
value: formatCurrency(summary.total.total_egg_value_rp),
|
||||
align: 'right',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const createPDFDocument = (
|
||||
data: HppPerKandangExportParams['data'],
|
||||
params: HppPerKandangExportParams['params']
|
||||
@@ -216,404 +355,23 @@ const createPDFDocument = (
|
||||
</View>
|
||||
|
||||
{/* Rekapitulasi Section */}
|
||||
<View style={pdfStyles.supplierSection}>
|
||||
<View style={pdfStyles.section}>
|
||||
<Text style={pdfStyles.supplierTitle}>Rekapitulasi</Text>
|
||||
|
||||
<View style={pdfStyles.table}>
|
||||
{/* Table Header */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>Rentang BW</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
|
||||
<Text>Sisa Butir</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
|
||||
<Text>Sisa Kg</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Rata-Rata Bobot (Kg)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
|
||||
<Text>Feed (Supplier)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>DOC (Supplier)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Rata-Rata Harga DOC</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>HPP Telur (RP/KG)</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{ flex: 1.2, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text>Nominal Sisa</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Table Body - Rekapitulasi */}
|
||||
{rekapitulasiByWeightRange.map(
|
||||
(group: HppPerKandangPerWeightRange, index: number) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
pdfStyles.tableRow,
|
||||
index < rekapitulasiByWeightRange.length - 1
|
||||
? pdfStyles.tableBorderBottom
|
||||
: {},
|
||||
]}
|
||||
>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||
<Text>{group.label}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text>{formatNumber(group.egg_production_pieces)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text>{formatNumber(group.egg_production_kg)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatNumber(group.avg_weight_kg)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text>
|
||||
{group.feed_suppliers
|
||||
?.map(
|
||||
(s: { alias?: string; name: string }) =>
|
||||
s.alias || s.name
|
||||
)
|
||||
.join(' | ') || '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{group.doc_suppliers
|
||||
?.map(
|
||||
(s: { alias?: string; name: string }) =>
|
||||
s.alias || s.name
|
||||
)
|
||||
.join(' | ') || '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(group.average_doc_price_rp)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(group.egg_hpp_rp_per_kg)}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{ flex: 1.2, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(group.egg_value_rp)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
<PdfTable
|
||||
columns={getRekapitulasiColumns()}
|
||||
data={getRekapitulasiData(rekapitulasiByWeightRange)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Detail Per Kandang Section */}
|
||||
<View style={pdfStyles.supplierSectionBreak}>
|
||||
<View style={pdfStyles.section}>
|
||||
<Text style={pdfStyles.supplierTitle}>Detail Per Kandang</Text>
|
||||
|
||||
<View style={pdfStyles.table}>
|
||||
{/* Table Header */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.5 }]}>
|
||||
<Text>No</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
|
||||
<Text>Kandang</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Rentang BW</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
|
||||
<Text>Rata-Rata Bobot (Kg)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
|
||||
<Text>Sisa Butir</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
|
||||
<Text>Sisa Kg (Telur)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>Feed (Supplier)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>DOC (Supplier)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
|
||||
<Text>Rata-Rata Harga DOC</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
|
||||
<Text>HPP Telur (RP/KG)</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{ flex: 1.2, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text>Nominal Sisa</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Table Body - Detail Per Kandang */}
|
||||
{data.rows.map((item: HppPerKandangRow, index: number) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}
|
||||
>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.5 }]}>
|
||||
<Text>{index + 1}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text>{item.kandang?.name || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>
|
||||
{item.weight_range.weight_min.toFixed(2)} -{' '}
|
||||
{item.weight_range.weight_max.toFixed(2)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text>{formatNumber(item.avg_weight_kg)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text>{formatNumber(item.egg_production_pieces)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||
<Text>{formatNumber(item.egg_production_kg)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text>
|
||||
{item.feed_suppliers
|
||||
?.map(
|
||||
(s: { alias?: string; name: string }) =>
|
||||
s.alias || s.name
|
||||
)
|
||||
.join(' | ')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>
|
||||
{item.doc_suppliers
|
||||
?.map(
|
||||
(s: { alias?: string; name: string }) =>
|
||||
s.alias || s.name
|
||||
)
|
||||
.join(' | ')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||
<Text>{formatCurrency(item.average_doc_price_rp)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||
<Text>{formatCurrency(item.egg_hpp_rp_per_kg)}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{ flex: 1.2, borderRightWidth: 0 },
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(item.egg_value_rp)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{/* TOTAL Row */}
|
||||
{data.summary?.total && (
|
||||
<View style={pdfStyles.tableRow}>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{
|
||||
flex: 0.5,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>TOTAL</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{
|
||||
flex: 1.5,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>ALL</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{
|
||||
flex: 1,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>-</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{
|
||||
flex: 1,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatNumber(data.summary.total.average_weight_kg)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{
|
||||
flex: 0.8,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatNumber(
|
||||
data.summary.total.total_egg_production_pieces
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{
|
||||
flex: 0.8,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatNumber(data.summary.total.total_egg_production_kg)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{
|
||||
flex: 1.2,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{data.rows
|
||||
.flatMap((row: HppPerKandangRow) =>
|
||||
row.feed_suppliers?.map(
|
||||
(s: { alias?: string; name: string }) =>
|
||||
s.alias || s.name
|
||||
)
|
||||
)
|
||||
.filter(
|
||||
(v: string, i: number, a: string[]) =>
|
||||
a.indexOf(v) === i
|
||||
)
|
||||
.join(' | ') || '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{
|
||||
flex: 1,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{data.rows
|
||||
.flatMap((row: HppPerKandangRow) =>
|
||||
row.doc_suppliers?.map(
|
||||
(s: { alias?: string; name: string }) =>
|
||||
s.alias || s.name
|
||||
)
|
||||
)
|
||||
.filter(
|
||||
(v: string, i: number, a: string[]) =>
|
||||
a.indexOf(v) === i
|
||||
)
|
||||
.join(' | ') || '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{
|
||||
flex: 1.2,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(
|
||||
data.summary.total.total_average_doc_price_rp
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{
|
||||
flex: 1,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(
|
||||
data.summary.total.average_egg_hpp_rp_per_kg
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeaderRight,
|
||||
{
|
||||
flex: 1.2,
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderBottomWidth: 0,
|
||||
borderRightWidth: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(data.summary.total.total_egg_value_rp)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<PdfTable
|
||||
columns={getDetailColumns()}
|
||||
data={getDetailData(data.rows)}
|
||||
footer={data.summary ? getDetailFooter(data.summary) : undefined}
|
||||
footerLabel='TOTAL'
|
||||
/>
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
|
||||
Reference in New Issue
Block a user