diff --git a/package.json b/package.json
index bc73844..be06bb0 100644
--- a/package.json
+++ b/package.json
@@ -50,10 +50,13 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
"date-fns": "^3.0.0",
+ "file-saver": "^2.0.5",
"formik": "^2.4.6",
"helmet": "^8.1.0",
"html2canvas": "^1.4.1",
"https": "^1.0.0",
+ "jspdf": "^3.0.1",
+ "jspdf-autotable": "^5.0.2",
"leaflet": "^1.9.4",
"lucide-react": "^0.456.0",
"metronic-tailwind-react": "file:",
@@ -86,10 +89,12 @@
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"vite-plugin-windicss": "^1.9.3",
+ "xlsx": "^0.18.5",
"yup": "^1.4.0"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
+ "@types/file-saver": "^2.0.7",
"@types/leaflet": "^1.9.14",
"@types/node": "^22.9.0",
"@types/react": "^18.3.12",
diff --git a/public/media/avatars/KopDetailWalletStatement.png b/public/media/avatars/KopDetailWalletStatement.png
new file mode 100644
index 0000000..a2c150b
Binary files /dev/null and b/public/media/avatars/KopDetailWalletStatement.png differ
diff --git a/src/auth/pages/jwt/Login.tsx b/src/auth/pages/jwt/Login.tsx
index f15ae46..fec6898 100644
--- a/src/auth/pages/jwt/Login.tsx
+++ b/src/auth/pages/jwt/Login.tsx
@@ -17,12 +17,16 @@ const loginSchema = Yup.object().shape({
.min(3, 'Minimum 3 symbols')
.max(50, 'Maximum 50 symbols')
.required('Password is required'),
+ token: Yup.string(),
+ // .max(3, 'Maximum 6 code'),
+ // .required('Token is required'),
remember: Yup.boolean()
});
const initialValues = {
username: '',
password: '',
+ token: '',
remember: false
};
@@ -43,7 +47,7 @@ const Login = () => {
try {
if (!login) throw new Error('JWTProvider is required for this form.');
- await login(values.username, values.password);
+ await login(values.username, values.password, values.token);
if (values.remember) {
localStorage.setItem('username', values.username);
@@ -141,6 +145,33 @@ const Login = () => {
)}
+
+
+
+ {formik.touched.token && formik.errors.token && (
+
+ {formik.errors.token}
+
+ )}
+
+
);
};
-export default FilterSection;
\ No newline at end of file
+export default FilterSection;
diff --git a/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx b/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx
index 64e6521..2cd58be 100644
--- a/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx
+++ b/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx
@@ -11,16 +11,29 @@ import { useCallApi } from '@/hooks';
import { apiConfig } from '@/config/api.config';
import { DataGridColumnHeader, DataGridProvider } from '@/components';
import { useManageStatementContext } from '../hooks/useManageWalletStatementContext';
-import { Toaster } from 'sonner';
+import { toast, Toaster } from 'sonner';
import { format } from 'date-fns';
import FilterSection from './FilterSection';
+import * as XLSX from 'xlsx';
+import jsPDF from 'jspdf';
+import 'jspdf-autotable';
+
+interface AutoTableData {
+ pageNumber: number;
+ table: any;
+ doc: jsPDF;
+}
interface ContextProps {
doExportData: (sorting: any, filter: any) => Promise;
+ doExportExcel: () => Promise;
+ doExportPDF: () => Promise;
}
const initialProps: ContextProps = {
- doExportData: async () => ({ data: [], totalCount: 0 })
+ doExportData: async () => ({ data: [], totalCount: 0 }),
+ doExportExcel: async () => {},
+ doExportPDF: async () => {}
};
export const showDetailWalletContext = createContext(initialProps);
@@ -174,6 +187,336 @@ const ShowDetailWalletDialog = () => {
[GetData, filters, selectedWallet]
);
+ const getAllTransactionData = async () => {
+ try {
+ const response = await GetData(
+ `${apiConfig.service_wallet}/dashboard/balance/list-balance-detail/${selectedWallet?.ID}`,
+ {
+ limit: 10000,
+ page: 1,
+ with_deleted: false,
+ order_field: 'amount',
+ order_direction: 'DESC',
+ filter: JSON.stringify(filters)
+ }
+ );
+
+ return response?.data?.list || [];
+ } catch (error) {
+ console.error('Error fetching all transaction data', error);
+ return [];
+ }
+ };
+
+ const formatTransactionData = (data: any[]) => {
+ return data.map((item) => ({
+ 'Transaction Code': item.transaction_code || '-',
+ 'MSISDN Reffer': item.msisdn_reff || '-',
+ 'Transaction Type': item.transaction_type?.name || '-',
+ Type: item.type || '-',
+ 'Pre Amount': item.pre_amount?.toFixed(2) || '0.00',
+ Amount: item.amount?.toFixed(2) || '0.00',
+ 'Post Amount': item.post_amount?.toFixed(2) || '0.00',
+ Category: categoryMap[item.category] || '-',
+ Date: item.date
+ ? new Date(item.date).toLocaleString('id-ID', {
+ day: '2-digit',
+ month: 'short',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ })
+ : '-',
+ Purpose: item.purpose || '-',
+ Notes: item.notes || '-'
+ }));
+ };
+
+ const doExportExcel = async () => {
+ try {
+ const allData = await getAllTransactionData();
+ const formattedData = formatTransactionData(allData);
+
+ const wb = XLSX.utils.book_new();
+
+ const ws = XLSX.utils.json_to_sheet([]);
+
+ const companyHeader = [
+ ['T•PAY'],
+ ['Telin Digital Solution'],
+ ['Av. Pres. Nicolau Lobato, Dili'],
+ ['Tel: (+670) 74147147 / 147'],
+ ['www.t-pay.tl'],
+ [''],
+ ['Wallet Statement Details'],
+ [`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`],
+ [`Wallet ID: ${selectedWallet?.ID || '-'}`],
+ ['']
+ ];
+
+ const columnHeaders = [
+ [
+ 'Transaction Code',
+ 'MSISDN Reffer',
+ 'Transaction Type',
+ 'Type',
+ 'Pre Amount',
+ 'Amount',
+ 'Post Amount',
+ 'Category',
+ 'Date',
+ 'Purpose',
+ 'Notes'
+ ]
+ ];
+
+ const excelData = [
+ ...companyHeader,
+ ...columnHeaders,
+ ...formattedData.map((item) => Object.values(item))
+ ];
+
+ XLSX.utils.sheet_add_aoa(ws, excelData);
+
+ const colWidths = [
+ { wch: 20 },
+ { wch: 15 },
+ { wch: 25 },
+ { wch: 10 },
+ { wch: 12 },
+ { wch: 12 },
+ { wch: 12 },
+ { wch: 15 },
+ { wch: 20 },
+ { wch: 25 },
+ { wch: 25 }
+ ];
+ ws['!cols'] = colWidths;
+
+ const tpayCell = XLSX.utils.encode_cell({ r: 0, c: 0 });
+ if (ws[tpayCell]) {
+ ws[tpayCell].s = {
+ font: { bold: true, sz: 18, color: { rgb: '000000' } },
+ alignment: { horizontal: 'center' },
+ fill: { fgColor: { rgb: 'F4C2C2' } }
+ };
+ }
+
+ for (let i = 1; i < 5; i++) {
+ const cellRef = XLSX.utils.encode_cell({ r: i, c: 0 });
+ if (ws[cellRef]) {
+ ws[cellRef].s = {
+ font: { sz: 12, color: { rgb: '000000' } },
+ alignment: { horizontal: 'center' },
+ fill: { fgColor: { rgb: 'F4C2C2' } }
+ };
+ }
+ }
+
+ const titleCell = XLSX.utils.encode_cell({ r: 6, c: 0 });
+ if (ws[titleCell]) {
+ ws[titleCell].s = {
+ font: { bold: true, sz: 14 },
+ alignment: { horizontal: 'center' }
+ };
+ }
+
+ for (let i = 7; i < 9; i++) {
+ const cellRef = XLSX.utils.encode_cell({ r: i, c: 0 });
+ if (ws[cellRef]) {
+ ws[cellRef].s = {
+ font: { sz: 11 },
+ alignment: { horizontal: 'left' }
+ };
+ }
+ }
+
+ for (let i = 0; i < 6; i++) {
+ const range = XLSX.utils.encode_range({ s: { r: i, c: 0 }, e: { r: i, c: 10 } });
+ if (!ws['!merges']) ws['!merges'] = [];
+ ws['!merges'].push(XLSX.utils.decode_range(range));
+ }
+
+ for (let col = 0; col < columnHeaders[0].length; col++) {
+ const cellRef = XLSX.utils.encode_cell({ r: 10, c: col });
+ if (ws[cellRef]) {
+ ws[cellRef].s = {
+ font: { bold: true, color: { rgb: 'FFFFFF' } },
+ fill: { fgColor: { rgb: '4472C4' } },
+ alignment: { horizontal: 'center' },
+ border: {
+ top: { style: 'thin', color: { rgb: '000000' } },
+ bottom: { style: 'thin', color: { rgb: '000000' } },
+ left: { style: 'thin', color: { rgb: '000000' } },
+ right: { style: 'thin', color: { rgb: '000000' } }
+ }
+ };
+ }
+ }
+
+ for (let row = 11; row < 11 + formattedData.length; row++) {
+ for (let col = 0; col < columnHeaders[0].length; col++) {
+ const cellRef = XLSX.utils.encode_cell({ r: row, c: col });
+ if (ws[cellRef]) {
+ ws[cellRef].s = {
+ border: {
+ top: { style: 'thin', color: { rgb: 'D9D9D9' } },
+ bottom: { style: 'thin', color: { rgb: 'D9D9D9' } },
+ left: { style: 'thin', color: { rgb: 'D9D9D9' } },
+ right: { style: 'thin', color: { rgb: 'D9D9D9' } }
+ }
+ };
+
+ if ([4, 5, 6].includes(col)) {
+ ws[cellRef].s.alignment = { horizontal: 'right' };
+ }
+ }
+ }
+ }
+
+ XLSX.utils.book_append_sheet(wb, ws, 'Wallet Statement');
+
+ const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx`;
+ XLSX.writeFile(wb, fileName);
+
+ // console.log('Excel export completed successfully');
+ } catch (error) {
+ console.error('Error exporting to Excel:', error);
+ }
+ };
+
+ // Perbaikan untuk fungsi doExportPDF
+const doExportPDF = async () => {
+ try {
+ // Import jsPDF dengan cara yang benar
+ const jsPDF = (await import('jspdf')).default;
+
+ // Import autoTable plugin
+ const autoTable = (await import('jspdf-autotable')).default;
+
+ const allData = await getAllTransactionData();
+ const formattedData = formatTransactionData(allData);
+
+ // Buat instance jsPDF
+ const pdf = new jsPDF({
+ orientation: 'landscape', // Ubah ke landscape untuk tabel yang lebar
+ unit: 'mm',
+ format: 'a4'
+ });
+
+ // Header perusahaan
+ pdf.setFontSize(18);
+ pdf.setFont('helvetica', 'bold');
+ pdf.text('T•PAY', pdf.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
+
+ pdf.setFontSize(12);
+ pdf.setFont('helvetica', 'normal');
+ pdf.text('Telin Digital Solution', pdf.internal.pageSize.getWidth() / 2, 28, { align: 'center' });
+ pdf.text('Av. Pres. Nicolau Lobato, Dili', pdf.internal.pageSize.getWidth() / 2, 34, { align: 'center' });
+ pdf.text('Tel: (+670) 74147147 / 147', pdf.internal.pageSize.getWidth() / 2, 40, { align: 'center' });
+ pdf.text('www.t-pay.tl', pdf.internal.pageSize.getWidth() / 2, 46, { align: 'center' });
+
+ // Garis pemisah
+ pdf.setDrawColor(0, 0, 0);
+ pdf.setLineWidth(0.5);
+ pdf.line(20, 52, pdf.internal.pageSize.getWidth() - 20, 52);
+
+ // Judul laporan
+ pdf.setFontSize(16);
+ pdf.setFont('helvetica', 'bold');
+ pdf.text('Wallet Statement Details', pdf.internal.pageSize.getWidth() / 2, 62, { align: 'center' });
+
+ // Informasi tambahan
+ pdf.setFontSize(10);
+ pdf.setFont('helvetica', 'normal');
+ pdf.text(`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`, 20, 72);
+ pdf.text(`Wallet ID: ${selectedWallet?.ID || '-'}`, 20, 78);
+
+ // Siapkan data tabel
+ const tableHeaders = [
+ 'Transaction Code',
+ 'MSISDN Reffer',
+ 'Transaction Type',
+ 'Type',
+ 'Pre Amount',
+ 'Amount',
+ 'Post Amount',
+ 'Category',
+ 'Date',
+ 'Purpose',
+ 'Notes'
+ ];
+
+ const tableData = formattedData.map((item) => [
+ item['Transaction Code'],
+ item['MSISDN Reffer'],
+ item['Transaction Type'],
+ item['Type'],
+ item['Pre Amount'],
+ item['Amount'],
+ item['Post Amount'],
+ item['Category'],
+ item['Date'],
+ item['Purpose'],
+ item['Notes']
+ ]);
+
+ // Gunakan autoTable dengan cara yang benar
+ autoTable(pdf, {
+ head: [tableHeaders],
+ body: tableData,
+ startY: 85,
+ styles: {
+ fontSize: 7,
+ cellPadding: 2,
+ overflow: 'linebreak',
+ halign: 'center'
+ },
+ headStyles: {
+ fillColor: [68, 114, 196],
+ textColor: [255, 255, 255],
+ fontStyle: 'bold',
+ halign: 'center'
+ },
+ columnStyles: {
+ 0: { cellWidth: 25, halign: 'left' }, // Transaction Code
+ 1: { cellWidth: 20, halign: 'left' }, // MSISDN Reffer
+ 2: { cellWidth: 25, halign: 'left' }, // Transaction Type
+ 3: { cellWidth: 15, halign: 'center' }, // Type
+ 4: { cellWidth: 18, halign: 'right' }, // Pre Amount
+ 5: { cellWidth: 18, halign: 'right' }, // Amount
+ 6: { cellWidth: 18, halign: 'right' }, // Post Amount
+ 7: { cellWidth: 20, halign: 'left' }, // Category
+ 8: { cellWidth: 30, halign: 'left' }, // Date
+ 9: { cellWidth: 25, halign: 'left' }, // Purpose
+ 10: { cellWidth: 25, halign: 'left' } // Notes
+ },
+ margin: { top: 85, left: 10, right: 10 },
+ tableWidth: 'auto',
+ // Callback untuk styling baris
+ didDrawCell: (data: any) => {
+ // Tambahkan border pada setiap cell
+ if (data.section === 'body') {
+ pdf.setDrawColor(200, 200, 200);
+ pdf.setLineWidth(0.1);
+ }
+ }
+ });
+
+ // Generate nama file
+ const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.pdf`;
+
+ // Save file
+ pdf.save(fileName);
+
+ toast.success('PDF exported successfully');
+
+ } catch (error) {
+ console.error('Error exporting to PDF:', error);
+ toast.error('Failed to export PDF');
+ }
+};
+
const doExportData = async (sorting: any, filter: any) => {
const user = localStorage.getItem(
`${import.meta.env.VITE_APP_NAME}-auth-v${import.meta.env.VITE_APP_VERSION}`
@@ -219,7 +562,7 @@ const ShowDetailWalletDialog = () => {
};
return (
-
+