From ff333b8f98678185f5f5457aef19cf7c24ca501e Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Mon, 19 May 2025 19:32:21 +0700 Subject: [PATCH] disbursement fixing --- .../blocks/DetailTransaction.tsx | 317 ++++++++++++------ .../blocks/DetailTransactionLog.tsx | 262 +++++++++------ .../blocks/UploadBatchDialog.tsx | 100 ++++-- .../hooks/TransactionContext.tsx | 59 ++-- 4 files changed, 489 insertions(+), 249 deletions(-) diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx index 65c070c..b288d0a 100644 --- a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx @@ -5,15 +5,15 @@ import { useCallApi } from '@/hooks'; import { apiConfig } from '@/config/api.config'; import { useEffect, useState } from 'react'; import moment from 'moment'; +import { getAuth } from '@/auth'; +import { toast } from 'sonner'; import { Dialog, DialogBody, DialogContent, - DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import TransactionLogViewer from './DetailTransactionLog'; const API_URL = apiConfig.service_disbursement; @@ -57,6 +57,7 @@ const DetailTransaction = () => { const [transactionDetails, setTransactionDetails] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [isExporting, setIsExporting] = useState(false); useEffect(() => { const fetchTransactionDetails = async () => { @@ -69,7 +70,6 @@ const DetailTransaction = () => { id: selectedTransactionId } ); - // console.log(response?.data); setTransactionDetails(response?.data); } catch (error) { console.error('Error fetching transaction', error); @@ -84,8 +84,6 @@ const DetailTransaction = () => { } }, [showDetailDialog, selectedTransactionId, GetData]); - const [activeTab, setActiveTab] = useState('detail'); // 'detail', 'log', 'approve' - const resetForm = () => { setTransactionDetails(null); }; @@ -96,109 +94,230 @@ const DetailTransaction = () => { } }, [showDetailDialog]); +const handleExport = async () => { + if (!selectedTransactionId || !transactionDetails) return; + + setIsExporting(true); + try { + const response = await fetch( + `${API_URL}/transaction/export?id_disbursment=${selectedTransactionId}`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${getAuth()?.access_token}` + } + } + ); + + if (!response.ok) { + throw new Error('Failed to fetch file'); + } + + const blob = await response.blob(); + const contentDisposition = response.headers.get('content-disposition'); + + const executionDate = transactionDetails.execution_date; + const formattedDate = executionDate + ? moment(executionDate).format('YYYYMMDD_HHmm') + : moment().format('YYYYMMDD_HHmm'); + + let filename = `transaction_${formattedDate}.xlsx`; + + if (contentDisposition) { + const filenameMatch = contentDisposition.match(/filename\*?=(?:UTF-8'')?"?([^;"\n]*)"?/); + if (filenameMatch && filenameMatch[1]) { + filename = decodeURIComponent(filenameMatch[1]); + } + } + + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + + toast.success('Export successful'); + } catch (error) { + console.error('Error exporting transaction:', error); + toast.error('Failed to export transaction'); + } finally { + setIsExporting(false); + } +}; + + return ( - - - Transaction Details + + + Transaction Details - {/* Tab Content */} -
-
-
- {isLoading ? ( -
-
-
-
-
-
-
-
+
+ {/* Summary Info */} + {transactionDetails && ( +
+
+

Filename Upload

+

{transactionDetails.file_name ?? '-'}

+
+
+

Total Amount

+

{transactionDetails.amount ?? '-'}

+
+
+

Total Record

+

+ {transactionDetails.total_record ?? '-'} +

+
+
+

Success Record

+

+ {transactionDetails.total_success ?? '-'} +

+
+
+

Fail Record

+

{transactionDetails.total_fail ?? '-'}

+
+
+

Pending Record

+

+ {transactionDetails.total_pending ?? '-'} +

+
+
+

Status

+

+ {renderStatusBadge(transactionDetails.status)} +

+
+
+

Execution Date

+

+ {transactionDetails.execution_date + ? moment(transactionDetails.execution_date).format('DD/MM/YYYY HH:mm') + : '-'} +

+
+
+

Done Date

+

+ {transactionDetails.done_date + ? moment(transactionDetails.done_date).format('DD/MM/YYYY HH:mm') + : '-'} +

+
+
+ )} + + {/* Log Table */} +
+ {isLoading ? ( +
+
+
+
+
+
+
-

Loading Logs Details...

- ) : ( - - - - - - - - - - - - - - {transactionDetails?.log && transactionDetails.log.length > 0 ? ( - transactionDetails.log.map( - ( - log: { - id: number; - customer: any; - amount: number; - remark: string; - reference: string; - request_date: string; - payment_response: string; - status: string; - }, - index: number - ) => ( - - - - - - - - - - ) - ) - ) : ( - - + + + )} + +
UsernameFullnameAmountStatusProcess Date - Invoice Number - Actions
- {log.customer?.username ?? 'Not Found'} - - {log.customer?.fullname ?? 'Not Found'} - - {log.amount ?? '-'} - - {renderStatusBadge(log.status) ?? '-'} - - {log.request_date && moment(log.request_date).isValid() - ? moment(log.request_date).format('DD/MM/YYYY HH:mm:ss') - : '-'} - - {log.reference ?? '-'} - -
- -
-
- No logs available +

Loading Logs Details...

+ + ) : ( + + + + + + + + + + + + + + + + + {transactionDetails?.log && transactionDetails.log.length > 0 ? ( + transactionDetails.log.map((log: any, index: number) => ( + + + + + + + + + + + - )} - -
UsernameFullnameAmountStatusProcess DateInvoice NumberRemark 1Remark 2Remark 3Actions
+ {log.customer?.username ?? 'Not Found'} + + {log.customer?.fullname ?? 'Not Found'} + {log.amount ?? '-'} + {renderStatusBadge(log.status) ?? '-'} + + {log.request_date && moment(log.request_date).isValid() + ? moment(log.request_date).format('DD/MM/YYYY HH:mm') + : '-'} + + {log.reference ?? '-'} + {log.remark_1 ?? '-'}{log.remark_2 ?? '-'}{log.remark_3 ?? '-'} +
+ )) + ) : ( +
+ No logs available +
+ )} +
+ + {/* Export Button - Moved to bottom right after table */} +
+
+
@@ -207,4 +326,4 @@ const DetailTransaction = () => { ); }; -export default DetailTransaction; +export default DetailTransaction; \ No newline at end of file diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx index 3c24c1a..00c58ab 100644 --- a/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx @@ -1,21 +1,13 @@ -import React, { useState } from 'react'; +import React from 'react'; import moment from 'moment'; import { useTransactionContext } from '../hooks/useTransactionContext'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody } from '@/components/ui/dialog'; - -interface LogType { - id: string; - amount: number; - remark: string; - reference: string; - response_date: string; - status: string; - customer?: { - username: string; - fullname: string; - }; - payment_response?: string; -} +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogBody +} from '@/components/ui/dialog'; type StatusCode = 'W' | 'O' | 'F' | 'D'; @@ -29,97 +21,171 @@ const statusMap: Record = { W: { label: 'Waiting Schedule', bg: 'bg-yellow-100', text: 'text-yellow-600' }, O: { label: 'On Process', bg: 'bg-blue-100', text: 'text-blue-600' }, F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' }, - D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' }, + D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' } }; export const renderStatusBadge = (statusRaw: string | null | undefined) => { - const status = statusRaw as StatusCode; - const { label, bg, text } = statusMap[status] ?? { - label: 'Unknown', - bg: 'bg-gray-100', - text: 'text-gray-600', - }; - - return ( - - {label} - - ); + const status = statusRaw as StatusCode; + const { label, bg, text } = statusMap[status] ?? { + label: 'Unknown', + bg: 'bg-gray-100', + text: 'text-gray-600' }; + return ( + {label} + ); +}; + const TransactionLogViewer = () => { - const { - showDetailLogDialog, - setShowDetailLogDialog, - detailLogData - } = useTransactionContext(); + const { showDetailLogDialog, setShowDetailLogDialog, detailLogData } = useTransactionContext(); return ( - - - Detail Record - - - {/* Tab Content */} - {detailLogData && detailLogData != null ? ( -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID{detailLogData.customer.id}
Username{detailLogData.customer.username}
Name{detailLogData.customer.fullname}
Amount{detailLogData.amount}
Remark{detailLogData.remark}
Payment Request
{detailLogData.payment_request}
Payment Response
{detailLogData.payment_response}
Status{renderStatusBadge(detailLogData.status)}
Prosess Date{detailLogData.request_date && moment(detailLogData.request_date).isValid() ? moment(detailLogData.request_date).format('DD/MM/YYYY HH:mm:ss') : '-'}
Response Date{detailLogData.response_date && moment(detailLogData.response_date).isValid() ? moment(detailLogData.response_date).format('DD/MM/YYYY HH:mm:ss') : '-'}
Reference Number{detailLogData.reference}
-
-
-
- ) : (
)} -
-
+ + + Detail Record + + + {detailLogData ? ( +
+
+
+ + + + + + + {detailLogData.customer && ( + <> + + + + + + + + + + + + + {detailLogData.customer.email && ( + + + + + )} + {detailLogData.customer.bank_name && ( + + + + + )} + {detailLogData.customer.bank_account && ( + + + + + )} + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Log ID{detailLogData.id}
Customer ID + {detailLogData.customer.id} +
Username + {detailLogData.customer.username} +
Fullname + {detailLogData.customer.fullname} +
Email + {detailLogData.customer.email} +
Bank Name + {detailLogData.customer.bank_name} +
Bank Account + {detailLogData.customer.bank_account} +
Amount{detailLogData.amount}
Remark{detailLogData.remark}
Remark 1 + {detailLogData.remark_1} +
Remark 2 + {detailLogData.remark_2} +
Remark 3 + {detailLogData.remark_3} +
Reference Number + {detailLogData.reference ?? '-'} +
Payment Request +
+                            {detailLogData.payment_request ?? '-'}
+                          
+
Payment Response +
+                            {detailLogData.payment_response ?? '-'}
+                          
+
Status + {renderStatusBadge(detailLogData.status)} +
Request Date + {detailLogData.request_date && + moment(detailLogData.request_date).isValid() + ? moment(detailLogData.request_date).format('DD/MM/YYYY HH:mm:ss') + : '-'} +
Response Date + {detailLogData.response_date && + moment(detailLogData.response_date).isValid() + ? moment(detailLogData.response_date).format('DD/MM/YYYY HH:mm:ss') + : '-'} +
+
+
+
+ ) : ( +
No data available
+ )} +
+
); }; diff --git a/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx b/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx index e6ccabf..15bc730 100644 --- a/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx +++ b/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx @@ -27,23 +27,28 @@ import clsx from 'clsx'; import { RefreshCw } from 'lucide-react'; const API_URL = apiConfig.service_disbursement; +const API_URL_TRANSACTION = apiConfig.service_transaction; +interface TransferType { + id: string; + name: string; + type: string; +} const UploadBatchDialog = () => { const parentRef = useRef(null); const { showUploadBatchDialog, handleUploadBatchDialog } = useTransactionContext(); const { reload } = useDataGrid(); - const { PostData, PostDataFile, GetData } = useCallApi(); + const { PostDataFile, GetData } = useCallApi(); + const [isSubmitting, setIsSubmitting] = useState(false); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); - const initialState: { - execution_date: string; - file: File | null; - } = { + const [alert, setAlert] = useState({ show: false, message: '' }); + + const [transferTypes, setTransferTypes] = useState([]); + + const initialState = { execution_date: '', - file: null + file: null as File | null, + id_transaction_type: '' }; const [formField, setFormField] = useState(initialState); @@ -52,14 +57,17 @@ const UploadBatchDialog = () => { setAlert({ show: false, message: '' }); }; - /* actions */ const doUploadBatch = useCallback( async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); const formData = new FormData(); - formData.append('execution_date', formField.execution_date); + const localDate = new Date(formField.execution_date); + const newDate = localDate.toISOString(); + + formData.append('execution_date', newDate); + formData.append('id_transaction_type', formField.id_transaction_type); if (formField.file) { formData.append('file', formField.file); @@ -67,9 +75,7 @@ const UploadBatchDialog = () => { try { const response = await PostDataFile(`${API_URL}/upload-excel`, formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } + headers: { 'Content-Type': 'multipart/form-data' } }); if (response?.status) { @@ -102,26 +108,52 @@ const UploadBatchDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - console.log('Form data before submit:', formField); - - if (formField.execution_date.trim() === '' || formField.file === null) { + if ( + formField.execution_date.trim() === '' || + formField.file === null || + formField.id_transaction_type.trim() === '' + ) { setAlert({ show: true, message: 'Please fill in all required fields.' }); return; } doUploadBatch(e); - // console.log(formField); setAlert({ show: false, message: '' }); }; useEffect(() => { if (showUploadBatchDialog === false) { resetForm(); + return; } + + const fetchTransferTypes = async () => { + try { + const response = await GetData(`${API_URL_TRANSACTION}/transactiontype/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: 'id', + order_direction: 'ASC', + filter: JSON.stringify({}) + }); + + const validTypes = ['DM', 'DA', 'DE']; + const records: TransferType[] = response?.data?.list ?? []; + + const filtered = records.filter((item) => validTypes.includes(item.type)); + + setTransferTypes(filtered); + } catch (error) { + console.error('Failed to fetch transfer types', error); + } + }; + + fetchTransferTypes(); }, [showUploadBatchDialog]); return ( - handleUploadBatchDialog(open)}> + @@ -129,7 +161,6 @@ const UploadBatchDialog = () => {

Upload Batch

-
{

{alert.message}

)} -
+
@@ -182,6 +213,31 @@ const UploadBatchDialog = () => { />
+
+
+ + +
+