diff --git a/public/media/file-templates/upload-batch-template.xlsx b/public/media/file-templates/upload-batch-template.xlsx new file mode 100644 index 0000000..2af339b Binary files /dev/null and b/public/media/file-templates/upload-batch-template.xlsx differ diff --git a/src/auth/_models.ts b/src/auth/_models.ts index a580966..8bf87ed 100644 --- a/src/auth/_models.ts +++ b/src/auth/_models.ts @@ -9,6 +9,7 @@ export interface AuthModel { company: any; user: any; // api_token: string; + statusbalance: string; } export interface UserModel { diff --git a/src/auth/providers/JWTProvider.tsx b/src/auth/providers/JWTProvider.tsx index 934d228..8cba25e 100644 --- a/src/auth/providers/JWTProvider.tsx +++ b/src/auth/providers/JWTProvider.tsx @@ -43,24 +43,6 @@ const AuthProvider = ({ children }: PropsWithChildren) => { const [auth, setAuth] = useState(authHelper.getAuth()); const [currentUser, setCurrentUser] = useState(); - const verify = async () => { - if (auth) { - try { - const { data: user } = await getUser(); - const createCacheUser = { - name: user.name, - email: user.email, - username: user.username, - role_name: auth.role_name - }; - localStorage.setItem('user', JSON.stringify(createCacheUser)); - } catch { - saveAuth(undefined); - setCurrentUser(undefined); - } - } - }; - const saveAuth = (auth: AuthModel | undefined) => { setAuth(auth); if (auth) { @@ -73,11 +55,20 @@ const AuthProvider = ({ children }: PropsWithChildren) => { const login = async (username: string, password: string) => { try { const { data: auth } = await axios - .post(LOGIN_URL, { username, password }) // , { headers: { 'Access-Control-Allow-Origin': "*" }} + .post(LOGIN_URL, { username, password }) .then((response) => response.data); - saveAuth({ ...auth.token, id: auth.user.id, role_name: auth.role_name, user: auth.user }); + const enhancedAuth: AuthModel = { + ...auth.token, + id: auth.user.id, + role_name: auth.role_name, + user: auth.user, + statusbalance: auth.role?.status_balance ?? null // SAFE ACCESS + }; + + saveAuth(enhancedAuth); setCurrentUser(auth.user); + const createActivity = { module: 'Login', description: `Login`, @@ -85,6 +76,7 @@ const AuthProvider = ({ children }: PropsWithChildren) => { }; doSaveLogActivity(createActivity); } catch (error: any) { + console.error('Login error:', error); throw error; } }; @@ -108,6 +100,33 @@ const AuthProvider = ({ children }: PropsWithChildren) => { return { data: _axios }; }; + const verify = async () => { + if (auth) { + try { + const { data: user } = await getUser(); + + // Perbarui auth yang sekarang dengan statusbalance + saveAuth({ + ...auth, + statusbalance: user.role.status_balance + }); + + const createCacheUser = { + name: user.name, + email: user.email, + username: user.username, + role_name: auth.role_name, + statusbalance: user.role.status_balance + }; + + localStorage.setItem('user', JSON.stringify(createCacheUser)); + } catch { + saveAuth(undefined); + setCurrentUser(undefined); + } + } + }; + const logout = async () => { const createActivity = { module: 'Logout', @@ -130,7 +149,6 @@ const AuthProvider = ({ children }: PropsWithChildren) => { currentUser, setCurrentUser, login, - // register, requestPasswordResetLink, changePassword, getUser, @@ -143,4 +161,4 @@ const AuthProvider = ({ children }: PropsWithChildren) => { ); }; -export { AuthContext, AuthProvider }; +export { AuthContext, AuthProvider }; \ No newline at end of file diff --git a/src/pages/dashboards/home/DashboardHomePage.tsx b/src/pages/dashboards/home/DashboardHomePage.tsx index b10125f..a42faee 100644 --- a/src/pages/dashboards/home/DashboardHomePage.tsx +++ b/src/pages/dashboards/home/DashboardHomePage.tsx @@ -23,6 +23,7 @@ import { apiConfig } from '@/config/api.config'; import TransactionValue from './blocks/TransactionValue'; import TransactionPieChart from './blocks/TransactionPieChart'; import MemberActivity from './blocks/MemberActivity'; +import BankSaldo from './blocks/BankSaldo'; // sum -> nominal, count-> total type CountType = 'sum' | 'count'; @@ -57,6 +58,8 @@ const DashboardHomePage = () => { const API_URL = apiConfig.api_dashboard; const { GetData } = useCallApi(); + const API_URL_BANK = apiConfig.service_wallet; + const [responseStatisticCard, setResponseStatisticCard] = useState(null); const fetchData = async () => { @@ -64,10 +67,21 @@ const DashboardHomePage = () => { setResponseStatisticCard(res); }; + const [bankaccount, setbankaccount] = useState(null); + + const fetchDataBankAccount = async () => { + const res = await GetData(`${API_URL_BANK}/dashboard/balance/account/${getAuth()?.user?.customer?.id}`, {}); + setbankaccount(res); + }; + useEffect(() => { fetchData(); }, []); + useEffect(() => { + fetchDataBankAccount(); + }, []); + const [responseGraphic, setresponseGraphic] = useState(null); useEffect(() => { @@ -253,8 +267,24 @@ const DashboardHomePage = () => { ) : null} +
+ {bankaccount?.data && bankaccount?.data.length > 0 && getAuth()?.statusbalance=='Y' ? ( + bankaccount?.data.map((bankaccountdatas: { amount: string, creditlimit: string, monthlylimit: string; wallet: string; }, index: number) => ( + + )) + ) : ( + "" + )} + +
+ {/* Cards */} -
+
{ + return ( +
+
+
+
+

{title}

+ +
+
+ {new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(parseFloat(balance))} +
+
+
+ Credit Limit: + {creditLimit} +
+
+ Monthly Limit: + {monthlyLimit} +
+
+
+
+ ); +}; + +interface AccountCardsProps { + accounts: AccountCardProps[]; +} + +export const AccountCards = ({ accounts }: AccountCardsProps) => { + return ( +
+ {accounts.map((account, index) => ( + + ))} +
+ ); +}; + +export default BankSaldo diff --git a/src/pages/dashboards/home/blocks/MemberActivity.tsx b/src/pages/dashboards/home/blocks/MemberActivity.tsx index 79c998d..badc8b0 100644 --- a/src/pages/dashboards/home/blocks/MemberActivity.tsx +++ b/src/pages/dashboards/home/blocks/MemberActivity.tsx @@ -10,12 +10,37 @@ interface Props { enddate: string; } +const polarToCartesian = (cx: number, cy: number, radius: number, angleInDegrees: number) => { + const angleInRadians = (angleInDegrees * Math.PI) / 180.0; + return { + x: cx + radius * Math.cos(angleInRadians), + y: cy + radius * Math.sin(angleInRadians), + }; +}; + +const describeArc = ( + x: number, + y: number, + radius: number, + startAngle: number, + endAngle: number +) => { + const start = polarToCartesian(x, y, radius, endAngle); + const end = polarToCartesian(x, y, radius, startAngle); + const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"; + + return [ + "M", start.x, start.y, + "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y, + ].join(" "); +}; + const MemberActivity = ({ startdate, enddate }: Props) => { const { GetData } = useCallApi(); const [responseTransactionValue, setResponseTransactionValue] = useState(null); useEffect(() => { - const fetchDataTransactionValue = async () => { + const fetchData = async () => { try { const res = await GetData(`${API_URL}/active-user`, { date_from: startdate, @@ -23,43 +48,33 @@ const MemberActivity = ({ startdate, enddate }: Props) => { }); setResponseTransactionValue(res); } catch (error) { - console.error('Error fetching transaction value:', error); + console.error('Error fetching data:', error); } }; - fetchDataTransactionValue(); + fetchData(); }, [startdate, enddate, GetData]); - const percentage = parseFloat((responseTransactionValue?.data?.total_customer_active_percentage ?? 0).toFixed(2)) ?? 0; + const percentage = parseFloat((responseTransactionValue?.data?.total_customer_active_percentage ?? 0).toFixed(2)); const totalCustomer = responseTransactionValue?.data?.total_customer ?? 0; const activeCustomer = responseTransactionValue?.data?.total_customer_active ?? 0; const reguler = responseTransactionValue?.data?.reguler ?? 0; const premium = responseTransactionValue?.data?.premium ?? 0; const agent = responseTransactionValue?.data?.agent ?? 0; - // Hitung sudut pointer - const angle = (percentage / 100) * 180; // 0° (kiri) ke 180° (kanan) - const radians = (angle * Math.PI) / 180; - const radius = 40; // Radius dari setengah lingkaran - const center = 50; // Titik pusat lingkaran - const pointerLength = 25; // Panjang pointer - - const x = center + pointerLength * Math.cos(radians - Math.PI); // offset agar mulai dari kiri - const y = center + pointerLength * Math.sin(radians - Math.PI); - - // Hitung titik akhir untuk arc aktif - const arcAngle = (Math.PI * percentage) / 100; - const arcX = 50 + radius * Math.cos(Math.PI - arcAngle); - const arcY = 50 - radius * Math.sin(arcAngle); + // Calculate pointer angle + const angle = (percentage / 100) * 180; + const pointerX = 50 + 25 * Math.cos((angle - 180) * Math.PI / 180); + const pointerY = 50 + 25 * Math.sin((angle - 180) * Math.PI / 180); return ( -
-
+
+

Member Activity

- {/* Sidebar */} + {/* Sidebar Info */}
  • @@ -70,15 +85,15 @@ const MemberActivity = ({ startdate, enddate }: Props) => { Active Customer: {activeCustomer}
  • - + Reguler: {reguler}
  • - + Premium: {premium}
  • - + Agent: {agent}
@@ -88,7 +103,7 @@ const MemberActivity = ({ startdate, enddate }: Props) => {

Active User

- + {/* Background arc */} { strokeWidth="10" /> {/* Active arc */} - 50 ? 1 : 0} 1 ${arcX} ${arcY}`} - fill="none" - stroke="#34d399" - strokeWidth="10" - /> + {percentage > 0 && ( + + )} {/* Pointer */} -
+
{percentage}% 100%
diff --git a/src/pages/dashboards/home/blocks/TransactionPieChart.tsx b/src/pages/dashboards/home/blocks/TransactionPieChart.tsx index 904b962..d5b0aae 100644 --- a/src/pages/dashboards/home/blocks/TransactionPieChart.tsx +++ b/src/pages/dashboards/home/blocks/TransactionPieChart.tsx @@ -43,7 +43,11 @@ const TransactionPieChart = ({ startdate, enddate }: Props) => { { name: 'Purchase', value: parseFloat((responseTransactionValue?.data?.P ?? 0).toFixed(2)), color: '#baf7c5' }, { name: 'Withdraw', value: parseFloat((responseTransactionValue?.data?.W ?? 0).toFixed(2)), color: '#f56565' }, { name: 'Top Up Partner', value: parseFloat((responseTransactionValue?.data?.N ?? 0).toFixed(2)), color: '#f7fa52' }, - { name: 'Reward', value: parseFloat((responseTransactionValue?.data?.E ?? 0).toFixed(2)), color: '#f7fa52' }, + { name: 'Reward', value: parseFloat((responseTransactionValue?.data?.E ?? 0).toFixed(2)), color: '#f50a19' }, + { name: 'Purchase Loja', value: parseFloat((responseTransactionValue?.data?.L ?? 0).toFixed(2)), color: '#F0A04B' }, + { name: 'Top Up P24', value: parseFloat((responseTransactionValue?.data?.B ?? 0).toFixed(2)), color: '#FADA7A' }, + { name: 'Transfer Agent', value: parseFloat((responseTransactionValue?.data?.A ?? 0).toFixed(2)), color: '#B1C29E' }, + { name: 'Withdrawal Agent', value: parseFloat((responseTransactionValue?.data?.M ?? 0).toFixed(2)), color: '#FCE7C8' }, ]; return ( diff --git a/src/pages/dashboards/home/blocks/TransactionValue.tsx b/src/pages/dashboards/home/blocks/TransactionValue.tsx index 451decc..3892b15 100644 --- a/src/pages/dashboards/home/blocks/TransactionValue.tsx +++ b/src/pages/dashboards/home/blocks/TransactionValue.tsx @@ -67,6 +67,18 @@ const TransactionValue = ({ startdate, enddate }: Props) => { case "E": type = "Reward"; break; + case "L": + type = "Purchase Loja" + break; + case "B": + type ="Top Up P24"; + break; + case "A": + type = " Transfer Agent"; + break; + case "M": + type = "Withdrawal Agent"; + break; default: type = "Unknown"; break; 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/ListToolbar.tsx b/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx index 1c6d73b..3ad1afd 100644 --- a/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx +++ b/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx @@ -9,23 +9,20 @@ const ListToolbar = () => { const { table, reload } = useDataGrid(); const { handleUploadBatchDialog } = useTransactionContext(); - // Set the initial state for trxDate const [trxDate, settrxDate] = useState({ from: '', to: '' }); - // Function to format date to YYYY-MM-DD const formatDate = (date: Date): string => { return date.toISOString().split('T')[0]; }; - // useEffect to set the default date values useEffect(() => { const today = new Date(); const nextWeek = new Date(today); nextWeek.setDate(today.getDate() + 7); settrxDate({ - from: formatDate(today), // Set 'from' to today - to: formatDate(nextWeek) // Set 'to' to 7 days later + from: formatDate(today), + to: formatDate(nextWeek) }); }, []); @@ -40,7 +37,7 @@ const ListToolbar = () => { const handleExportData = () => { const link = document.createElement('a'); - link.href = toAbsoluteUrl('/media/file-templates/batch-sample.xlsx'); + link.href = toAbsoluteUrl('/media/file-templates/upload-batch-template.xlsx'); link.download = 'upload-batch-template.xlsx'; document.body.appendChild(link); link.click(); diff --git a/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx b/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx index e6ccabf..688d4ac 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,48 @@ 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({ type: "D" }) + }); + + const records: TransferType[] = response?.data?.list ?? []; + setTransferTypes(records); + } catch (error) { + console.error('Failed to fetch transfer types', error); + } + }; + + fetchTransferTypes(); }, [showUploadBatchDialog]); return ( - handleUploadBatchDialog(open)}> + @@ -129,7 +157,6 @@ const UploadBatchDialog = () => {

Upload Batch

-
{

{alert.message}

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