diff --git a/src/components/helper/pdf/layout/PdfContainer.tsx b/src/components/helper/pdf/layout/PdfContainer.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/helper/pdf/table/PdfTable.tsx b/src/components/helper/pdf/table/PdfTable.tsx
new file mode 100644
index 00000000..46724ef6
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfTable.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+import { View, StyleSheet } from '@react-pdf/renderer';
+import { PdfColumn } from './PdfThead';
+
+const styles = StyleSheet.create({
+ table: {
+ borderWidth: 1,
+ borderColor: '#000000',
+ marginBottom: 15,
+ },
+});
+
+interface PdfTableProps {
+ columns: PdfColumn[];
+ children: React.ReactNode;
+}
+
+export const PdfTable = ({ columns, children }: PdfTableProps) => {
+ return {children};
+};
diff --git a/src/components/helper/pdf/table/PdfTbody.tsx b/src/components/helper/pdf/table/PdfTbody.tsx
new file mode 100644
index 00000000..9a23ab4b
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfTbody.tsx
@@ -0,0 +1,236 @@
+'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',
+ },
+ badge: {
+ paddingVertical: 2,
+ paddingHorizontal: 4,
+ borderRadius: 12,
+ fontSize: 5,
+ fontWeight: 'bold',
+ borderWidth: 1,
+ textAlign: 'center',
+ whiteSpace: 'nowrap',
+ },
+});
+
+interface PdfTbodyProps {
+ columns: PdfColumn[];
+ rows: PdfTbodyCell[][];
+ initialBalanceRow?: {
+ 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,
+ initialBalanceRow,
+}: PdfTbodyProps) => {
+ return (
+ <>
+ {/* Initial Balance Row */}
+ {initialBalanceRow && (
+
+ {columns.map((column, index) => {
+ const isLastColumn = index === columns.length - 1;
+ const isInitialBalanceColumn =
+ column.key === initialBalanceRow.valueKey;
+ const align = column.align || 'center';
+
+ const cellStyle =
+ column.key === 'no'
+ ? [styles.tableCellNo, { flex: column.flex }]
+ : isInitialBalanceColumn
+ ? [
+ styles.tableCellRight,
+ {
+ flex: column.flex,
+ color: initialBalanceRow.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 (
+
+
+ {isInitialBalanceColumn ? initialBalanceRow.value : ''}
+
+
+ );
+ })}
+
+ )}
+
+ {/* Data Rows */}
+ {rows.map((row, rowIndex) => {
+ const isLastRow = rowIndex === rows.length - 1;
+
+ return (
+
+ {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 (
+
+ {cell?.value !== undefined &&
+ cell?.value !== null &&
+ cell?.value !== '' ? (
+ typeof cell.value === 'object' ? (
+ cell.value // For custom React element (badge, etc)
+ ) : (
+ {String(cell.value)}
+ )
+ ) : (
+ -
+ )}
+
+ );
+ })}
+
+ );
+ })}
+ >
+ );
+};
diff --git a/src/components/helper/pdf/table/PdfTfoot.tsx b/src/components/helper/pdf/table/PdfTfoot.tsx
new file mode 100644
index 00000000..900673e0
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfTfoot.tsx
@@ -0,0 +1,124 @@
+'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 (
+
+ {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 }]
+ : cellData?.align === 'right'
+ ? [
+ styles.tableCellRight,
+ {
+ flex: column.flex,
+ color: cellData?.color || 'black',
+ },
+ ]
+ : cellData?.align === 'center'
+ ? [
+ styles.tableCellCenter,
+ {
+ flex: column.flex,
+ color: cellData?.color || 'black',
+ },
+ ]
+ : isLastColumn
+ ? [styles.tableCellLast, { flex: column.flex }]
+ : [
+ styles.tableCell,
+ {
+ flex: column.flex,
+ color: cellData?.color || 'black',
+ },
+ ];
+
+ return (
+
+
+ {column.key === 'no' ? label : cellData?.value || ''}
+
+
+ );
+ })}
+
+ );
+};
diff --git a/src/components/helper/pdf/table/PdfThead.tsx b/src/components/helper/pdf/table/PdfThead.tsx
new file mode 100644
index 00000000..89037216
--- /dev/null
+++ b/src/components/helper/pdf/table/PdfThead.tsx
@@ -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 (
+
+ {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 (
+
+ {column.header}
+
+ );
+ })}
+
+ );
+};