diff --git a/package.json b/package.json index 7745b80..89c60d4 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "react-query": "^3.39.3", "react-router": "^6.28.0", "react-router-dom": "^6.28.0", + "recharts": "^2.15.3", "sonner": "^1.7.0", "styled-components": "^6.1.13", "stylis": "^4.3.4", diff --git a/src/pages/dashboards/home/DashboardHomePage.tsx b/src/pages/dashboards/home/DashboardHomePage.tsx index c2c53b2..c591184 100644 --- a/src/pages/dashboards/home/DashboardHomePage.tsx +++ b/src/pages/dashboards/home/DashboardHomePage.tsx @@ -21,6 +21,8 @@ import { getAuth } from '@/auth'; import { useCallApi } from '@/hooks'; import { apiConfig } from '@/config/api.config'; import TransactionValue from './blocks/TransactionValue'; +import TransactionPieChart from './blocks/TransactionPieChart'; +import MemberActivity from './blocks/MemberActivity'; // sum -> nominal, count-> total type CountType = 'sum' | 'count'; @@ -339,10 +341,11 @@ const DashboardHomePage = () => { - +
+ + + +
diff --git a/src/pages/dashboards/home/blocks/MemberActivity.tsx b/src/pages/dashboards/home/blocks/MemberActivity.tsx new file mode 100644 index 0000000..172c019 --- /dev/null +++ b/src/pages/dashboards/home/blocks/MemberActivity.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import { UsersIcon, UserCheckIcon } from "lucide-react"; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; + +const API_URL = apiConfig.api_dashboard; + +interface Props { + startdate: string; + enddate: string; +} + +const MemberActivity = ({ startdate, enddate }: Props) => { + const { GetData } = useCallApi(); + const [responseTransactionValue, setResponseTransactionValue] = useState(null); + + useEffect(() => { + const fetchDataTransactionValue = async () => { + try { + const res = await GetData(`${API_URL}/active-user`, { + date_from: startdate, + date_to: enddate, + }); + setResponseTransactionValue(res); + } catch (error) { + console.error('Error fetching transaction value:', error); + } + }; + + fetchDataTransactionValue(); + }, [startdate, enddate, GetData]); + + const percentage = parseFloat((responseTransactionValue?.data?.total_customer_active_percentage ?? 0).toFixed(2)) ?? 0; + const totalCustomer = responseTransactionValue?.data?.total_customer ?? 0; + const activeCustomer = responseTransactionValue?.data?.total_customer_active ?? 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); + + return ( +
+
+

Member Activity

+
+ +
+ {/* Sidebar */} +
    +
  • + + Total Customer: {totalCustomer} +
  • +
  • + + Active Customer: {activeCustomer} +
  • +
+ + {/* Gauge Chart */} +
+
+

Active User

+
+ + {/* Background arc */} + + {/* Active arc */} + 50 ? 1 : 0} 1 ${arcX} ${arcY}`} + fill="none" + stroke="#34d399" + strokeWidth="10" + /> + {/* Pointer */} + + +
+ {percentage}% + 100% +
+
+
+
+
+
+ ); +}; + +export default MemberActivity; diff --git a/src/pages/dashboards/home/blocks/TransactionPieChart.tsx b/src/pages/dashboards/home/blocks/TransactionPieChart.tsx new file mode 100644 index 0000000..d7b768d --- /dev/null +++ b/src/pages/dashboards/home/blocks/TransactionPieChart.tsx @@ -0,0 +1,89 @@ +import React, { useEffect, useState } from 'react'; +import { + PieChart, + Pie, + Cell, + ResponsiveContainer, +} from 'recharts'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; + +const API_URL = apiConfig.api_dashboard; + +interface Props { + startdate: string; + enddate: string; +} + +const TransactionPieChart = ({ startdate, enddate }: Props) => { + + const { GetData } = useCallApi(); + const [responseTransactionValue, setResponseTransactionValue] = useState(null); + + useEffect(() => { + const fetchDataTransactionValue = async () => { + try { + const res = await GetData(`${API_URL}/transaction-chart`, { + date_from: startdate, + date_to: enddate, + }); + setResponseTransactionValue(res); + } catch (error) { + console.error('Error fetching transaction value:', error); + } + }; + + fetchDataTransactionValue(); + }, [startdate, enddate, GetData]); + + const data = [ + { name: 'Transfer', value: parseFloat((responseTransactionValue?.data?.T ?? 0).toFixed(2)), color: '#3490dc' }, + { name: 'Return', value: parseFloat((responseTransactionValue?.data?.R ?? 0).toFixed(2)), color: '#a0aec0' }, + { name: 'Topup', value: parseFloat((responseTransactionValue?.data?.U ?? 0).toFixed(2)), color: '#9f7aea' }, + { name: 'Purchase', value: parseFloat((responseTransactionValue?.data?.P ?? 0).toFixed(2)), color: '#baf7c5' }, + { name: 'Withdraw', value: parseFloat((responseTransactionValue?.data?.W ?? 0).toFixed(2)), color: '#f56565' }, + ]; + + return ( +
+
+

Transaction Chart

+
+ +
+
+ + + + {data.map((entry, index) => ( + + ))} + + + +
+ +
+
Active Transaction
+ {data.map((entry, index) => ( +
+
+
+ {entry.name} +
+ {entry.value}% +
+ ))} +
+
+
+ ); +}; + +export default TransactionPieChart; diff --git a/src/pages/dashboards/home/blocks/TransactionValue.tsx b/src/pages/dashboards/home/blocks/TransactionValue.tsx index 63c43d5..bb6c53d 100644 --- a/src/pages/dashboards/home/blocks/TransactionValue.tsx +++ b/src/pages/dashboards/home/blocks/TransactionValue.tsx @@ -10,7 +10,7 @@ interface Props { } const TransactionValue = ({ startdate, enddate }: Props) => { - const { GetData } = useCallApi(); + const { GetData } = useCallApi(); const [responseTransactionValue, setResponseTransactionValue] = useState(null); useEffect(() => { @@ -30,21 +30,22 @@ const TransactionValue = ({ startdate, enddate }: Props) => { }, [startdate, enddate, GetData]); let transactionData: any[] = []; - if (responseTransactionValue?.data.length > 0) { - for (let i = 0; i < responseTransactionValue?.data.length; i++) { - let type = ""; + if (responseTransactionValue?.data?.length > 0) { + for (let i = 0; i < responseTransactionValue.data.length; i++) { + let type = ""; let unit = ""; - if(responseTransactionValue?.data[i].total_amount>=1000 && responseTransactionValue?.data[i].total_amount<1000000){ - unit = "K" - }else if(responseTransactionValue?.data[i].total_amount>=1000000 && responseTransactionValue?.data[i].total_amount<1000000000){ - unit = "M" - } - else if(responseTransactionValue?.data[i].total_amount>=1000000000){ - unit = "B" + const amount = responseTransactionValue.data[i].total_amount; + + if (amount >= 1000 && amount < 1000000) { + unit = "K"; + } else if (amount >= 1000000 && amount < 1000000000) { + unit = "M"; + } else if (amount >= 1000000000) { + unit = "B"; } - switch (responseTransactionValue?.data[i].kind) { + switch (responseTransactionValue.data[i].kind) { case "P": type = "Purchase"; break; @@ -61,13 +62,13 @@ const TransactionValue = ({ startdate, enddate }: Props) => { type = "Return"; break; default: - type = "Unknown"; + type = "Unknown"; break; } transactionData.push({ label: type, - value: responseTransactionValue?.data[i].total_amount, + value: amount, unit: unit }); } @@ -76,26 +77,32 @@ const TransactionValue = ({ startdate, enddate }: Props) => { const maxValue = transactionData.length > 0 ? Math.max(...transactionData.map(item => item.value)) : 1; return ( -
+

Transaction Value

-
- {transactionData.map((item, index) => ( -
- {item.label} -
-
-
+ {transactionData.length > 0 ? ( +
+ {transactionData.map((item, index) => ( +
+ {item.label} +
+
+
+
+ {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(item.value)}{item.unit}
- {item.value}{item.unit} -
- ))} -
+ ))} +
+ ) : ( +
+ No Data Available +
+ )}
); }; diff --git a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx index 8c4862b..c4418cd 100644 --- a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx @@ -248,7 +248,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { limit, page: page + 1, with_deleted: false, - order_field: "id", + order_field: "execution_date", order_direction: 'DESC', filter: JSON.stringify(formattedFilter) }); @@ -286,7 +286,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { pagination={{ size: 10 }} toolbar={} layout={{ card: true }} - sorting={[{ id: 'id', desc: false }]} + sorting={[{ id: 'execution_date', desc: false }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => getTransactionLists(pageIndex, pageSize, sorting, columnFilters) diff --git a/src/pages/master/conversion/hooks/ManageConversionContext.tsx b/src/pages/master/conversion/hooks/ManageConversionContext.tsx index 730bae1..eceb4fa 100644 --- a/src/pages/master/conversion/hooks/ManageConversionContext.tsx +++ b/src/pages/master/conversion/hooks/ManageConversionContext.tsx @@ -137,16 +137,16 @@ const ManageConversionContextProvider = ({ children }: { children: React.ReactNo > - + */} ); }, - meta: { headerClassName: 'w-[100px]', cellClassName: 'text-center' } + meta: { headerClassName: 'w-[100px] text-center', cellClassName: 'text-center' } } ], [handleEditDialog, handleDeleteDialog] diff --git a/src/pages/master/currency/hooks/ManageCurrencyContext.tsx b/src/pages/master/currency/hooks/ManageCurrencyContext.tsx index f025f87..2795014 100644 --- a/src/pages/master/currency/hooks/ManageCurrencyContext.tsx +++ b/src/pages/master/currency/hooks/ManageCurrencyContext.tsx @@ -124,16 +124,16 @@ const ManageCurrencyContextProvider = ({ children }: { children: React.ReactNode > - + */} ); }, - meta: { headerClassName: 'w-[100px]', cellClassName: 'text-center' } + meta: { headerClassName: 'w-[100px] text-center', cellClassName: 'text-center' } } ], [handleEditDialog, handleDeleteDialog] diff --git a/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx b/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx index 14e66c3..eb25709 100644 --- a/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx +++ b/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx @@ -192,17 +192,17 @@ const ManageWalletRuleContextProvider = ({ children }: { children: React.ReactNo > - + */} ); }, meta: { - headerClassName: 'w-[100px]', + headerClassName: 'w-[100px] text-center', cellClassName: 'text-center' } } diff --git a/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx index 48e4608..e36ab10 100644 --- a/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx +++ b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx @@ -209,6 +209,15 @@ const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactN headerClassName: 'w-[350px]' } }, + { + accessorFn: (row) => row.msisdn, + id: 'msdisdn', + header: ({ column }) => , + enableSorting: true, + meta: { + headerClassName: 'w-[350px]' + } + }, { accessorFn: (row) => row.registered_email, id: 'email', diff --git a/src/pages/members/kyc/Columns.tsx b/src/pages/members/kyc/Columns.tsx index 55d3d28..5002030 100644 --- a/src/pages/members/kyc/Columns.tsx +++ b/src/pages/members/kyc/Columns.tsx @@ -70,6 +70,20 @@ export const getColumns = (handleUpdate: (data: any) => void): ColumnDef { + return ( + + ); + } + }, { accessorKey: 'group_name', header: ({ column }) => { diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 03f8069..016fd45 100644 --- a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx @@ -8,6 +8,7 @@ import { DialogContent, DialogHeader, DialogTitle, + DialogDescription, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { @@ -26,7 +27,7 @@ const API_URL = apiConfig.transaction; const ApprovalDialog = () => { const { GetData, PostData } = useCallApi(); - const { reload } = useDataGrid(); + const { reload } = useDataGrid(); const { showApprovalDialog, @@ -40,6 +41,7 @@ const ApprovalDialog = () => { transaction_code: '', status: '', notes: '', + pin: '' }); const [alert, setAlert] = useState({ @@ -63,8 +65,9 @@ const ApprovalDialog = () => { const response = await PostData(`${API_URL}/transaction/set-approval`, { id_transaction: transactionDetails.id, - status: formField.status, notes: formField.notes, + status: formField.status, + pin: formField.pin, }); if (response?.status === false) { @@ -97,8 +100,9 @@ const ApprovalDialog = () => { if (showApprovalDialog) { setFormField({ transaction_code: '', - status: '', notes: '', + status: '', + pin:'' }); setTransactionDetails(null); setAlert({ show: false, message: '' }); @@ -142,6 +146,7 @@ const ApprovalDialog = () => { Approval Transaction +
@@ -165,6 +170,26 @@ const ApprovalDialog = () => {
+
+ + +
+ + setFormField((prev) => ({ + ...prev, + pin: e.target.value, + })) + } + /> +
+
{formField.status === 'N' && (
diff --git a/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx b/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx index 7ddb01c..6a15c17 100644 --- a/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx +++ b/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx @@ -58,6 +58,7 @@ const DetailApprovalTransaction = () => { Transaction Details + {/* Tabs Navigation */}
diff --git a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx index 72c660d..c6a6ff6 100644 --- a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx +++ b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx @@ -1,31 +1,86 @@ -import { Container, DataGridInner } from '@/components'; +import { Alert, Container, DataGridInner } from '@/components'; import { TransactionDisbursementProvider } from './hooks/TransactionDisbursementContext'; import { Breadcrumbs, Link } from '@mui/material'; import { Helmet } from 'react-helmet'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; -import { useState ,useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useCallApi } from '@/hooks'; import { apiConfig } from '@/config/api.config'; import { toast } from 'sonner'; import { getAuth } from '@/auth'; +import { RefreshCw } from 'lucide-react'; const TransactionDisbursement = () => { - const [form, setForm] = useState({ + const initialForm: { + msisdn: string; + amount: string; + pin: string; + } = { msisdn: '', amount: '', pin: '' - }); + }; + + const [form, setForm] = useState(initialForm); const [wallets, setWallets] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [customerMsisdn, setCustomerMsisdn] = useState<{ value: string; label: string }[]>([]); + const [searchTerm, setSearchTerm] = useState(''); + const [dropdownOpen, setDropdownOpen] = useState(false); const { GetData, PostData } = useCallApi(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); const parsedUser = getAuth()?.user; const API_URL = apiConfig.transaction; - const API_URL_WALLET = apiConfig.service_wallet + const API_URL_WALLET = apiConfig.service_wallet; + const API_URL_CUSTOMER = apiConfig.service_customer; + const dropdownRef = useRef(null); + + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const fetchCustomerMsisdn = async (sorting: any, filterValue: string) => { + const filter: any = + filterValue.trim().length === 0 ? {} : { msisdn: { like: `%${filterValue}%` } }; + + const query: any = { + limit: 25, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + }; + + if (filter && Object.keys(filter).length > 0) { + query.filter = JSON.stringify(filter); + // query.page = page + 1; + } + + try { + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, query); + setCustomerMsisdn( + response?.data.list.map((item: any) => ({ + value: item.msisdn, + label: `${item.msisdn} - ${item.fullname}` + })) + ); + } catch (error) { + toast.error('Failed to fetch customer msisdn'); + } finally { + setIsLoading(false); + } + }; useEffect(() => { const fetchWallets = async () => { try { - const response = await GetData(`${API_URL_WALLET}/dashboard/balance/account/${parsedUser.customer.id}`, {}); + const response = await GetData( + `${API_URL_WALLET}/dashboard/balance/account/${parsedUser.customer.id}`, + {} + ); if (response?.status === true) { setWallets(response.data || []); } else { @@ -37,32 +92,84 @@ const TransactionDisbursement = () => { }; fetchWallets(); + fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], ''); + + const handleClickOutside = (event: any) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; }, []); - const handleSubmit = async (e: any) => { - e.preventDefault(); - console.log('Submitted Data:', form); - if (form.amount == '' || form.msisdn == '' || form.pin == '') { - toast.warning('Please fill in all required fields.') - return - } + const doPostData = async (form: typeof initialForm) => { + setIsSubmitting(true); + try { - let requestTopup = await PostData(`${API_URL}/transaction/topup-downline`, { + let response = await PostData(`${API_URL}/transaction/topup-downline`, { msisdn_destination: form.msisdn, amount: form.amount, pin: form.pin - }) - if (requestTopup?.status == true) { - toast.success('Success Request Topup') + }); + if (response?.status == true) { + toast.success('Success Request Topup'); } else { - toast.warning(`${requestTopup?.message}`) + toast.error(`${response?.message?.message}`); } - } catch (error) { - toast.warning('Failed') + } catch (error: any) { + const errorMessage = + error?.response?.data?.message || error?.message || 'Something went wrong'; + toast.error(errorMessage); + setAlert({ show: true, message: errorMessage }); + } finally { + setIsSubmitting(false); + setShowConfirmation(false); } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (form.amount == '' || form.msisdn == '' || form.pin == '') { + setAlert({ + show: true, + message: 'Please fill in all required fields.' + }); + return; + } + setAlert({ show: false, message: '' }); + setShowConfirmation(true); // TODO: Kirim ke backend atau proses lainnya }; + const handleCancelSubmit = () => { + setShowConfirmation(false); + }; + + const handleMsisdnSearch = (e: React.ChangeEvent) => { + setIsLoading(true); + setSearchTerm(e.target.value); + setDropdownOpen(true); + const timer = setTimeout(() => { + fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], e.target.value); + }, 500); + return () => clearTimeout(timer); + }; + + const handleMsisdnSelect = (msisdn: string) => { + setForm({ ...form, msisdn }); + setDropdownOpen(false); + setSearchTerm(msisdn); + }; + + const filteredMsisdn = customerMsisdn + .filter((item) => item.label.toLowerCase().includes(searchTerm.toLowerCase())) + .slice(0, 10); + return ( <> @@ -70,7 +177,9 @@ const TransactionDisbursement = () => { -

MANAGE TRANSACTION DISBURSEMENT SALDO

+

+ MANAGE TRANSACTION DISBURSEMENT SALDO +

Dashboard @@ -90,51 +199,121 @@ const TransactionDisbursement = () => {

{wallet.wallet}

- {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(wallet.amount)} + {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format( + wallet.amount + )}

))}
+
+ {alert.show && ( + +

{alert.message}

+
+ )} {/* form */} - -
- * - setForm({ ...form, msisdn: e.target.value })} - /> -
-
- * - setForm({ ...form, amount: e.target.value })} - /> -
-
- * - setForm({ ...form, pin: e.target.value })} - /> -
-
- -
- +
+
+ + * +
+ setDropdownOpen(true)} + /> + {dropdownOpen && ( +
+ {filteredMsisdn.length > 0 ? ( + filteredMsisdn.map((item, index) => ( +
handleMsisdnSelect(item.value)} + > + {item.label} +
+ )) + ) : ( +
+ {isLoading ? 'Loading...' : 'No results found'} +
+ )} +
+ )} +
+
+ +
+ + * + setForm({ ...form, amount: e.target.value })} + /> +
+
+ + * + setForm({ ...form, pin: e.target.value })} + /> +
+
+ +
+
+ + {showConfirmation && ( +
+
+

Confirm Transaction

+

+ Are you sure you want to disbursement saldo of{' '} + + {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format( + Number(form.amount) + )}{' '} + + ? +

+
+ + +
+
+
+ )} diff --git a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx index 199a8bc..39f5b07 100644 --- a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx @@ -45,6 +45,8 @@ const DetailTransaction = () => { id: selectedTransactionId }); setTransactionDetails(response?.data); + console.log(response?.data); + console.log(selectedTransactionId); } catch (error) { console.error('Error fetching transaction', error); } @@ -72,6 +74,7 @@ const DetailTransaction = () => { Transaction Details + {/* Tabs Navigation */}
@@ -207,7 +210,7 @@ const DetailTransaction = () => {

Description

-

{transactionDetails?.description}

+

{transactionDetails?.description || '-'}

Name

diff --git a/src/pages/transaction/topup/TransactionTopup.tsx b/src/pages/transaction/topup/TransactionTopup.tsx index a05fb95..1b339a0 100644 --- a/src/pages/transaction/topup/TransactionTopup.tsx +++ b/src/pages/transaction/topup/TransactionTopup.tsx @@ -1,31 +1,46 @@ -import { Container, DataGridInner } from '@/components'; +import { Alert, Container, DataGridInner } from '@/components'; import { TransactionTopupProvider } from './hooks/TransactionTopupContext'; import { Breadcrumbs, Link } from '@mui/material'; import { Helmet } from 'react-helmet'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { useCallApi } from '@/hooks'; import { apiConfig } from '@/config/api.config'; import { toast } from 'sonner'; import { getAuth } from '@/auth'; +import { RefreshCw } from 'lucide-react'; const TransactionTopup = () => { - const [form, setForm] = useState({ + const initialState: { + topupAmount: string; + pin: string; + } = { topupAmount: '', pin: '' + }; + + const [form, setForm] = useState(initialState); + const [alert, setAlert] = useState({ + show: false, + message: '' }); const [wallets, setWallets] = useState([]); const { GetData, PostData } = useCallApi(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); const parsedUser = getAuth()?.user; const API_URL = apiConfig.transaction; - const API_URL_WALLET = apiConfig.service_wallet + const API_URL_WALLET = apiConfig.service_wallet; useEffect(() => { const fetchWallets = async () => { try { - const response = await GetData(`${API_URL_WALLET}/dashboard/balance/account/${parsedUser.customer.id}`, {}); + const response = await GetData( + `${API_URL_WALLET}/dashboard/balance/account/${parsedUser.customer.id}`, + {} + ); if (response?.status === true) { setWallets(response.data || []); } else { @@ -39,30 +54,49 @@ const TransactionTopup = () => { fetchWallets(); }, []); - const handleSubmit = async (e: any) => { - e.preventDefault(); - console.log('Submitted Data:', form); + const doPostData = async (form: typeof initialState) => { + setIsSubmitting(true); - if (form.pin == '' || form.topupAmount == '') { - toast.warning('Please fill in all required fields.') - return - } try { - let requestTopup = await PostData(`${API_URL}/transaction/request-topup`, { + let response = await PostData(`${API_URL}/transaction/request-topup`, { amount: form.topupAmount, pin: form.pin - }) - if (requestTopup?.status == true) { - toast.success('Success Request Topup') + }); + console.log(response); + if (response?.status == true) { + toast.success('Success Request Topup'); } else { - toast.warning(`${requestTopup?.message}`) + toast.warning(`${response?.message}`); } - } catch (error) { - toast.warning('Failed') + } catch (error: any) { + const errorMessage = + error?.response?.data?.message || error?.message || 'Something went wrong'; + toast.error(errorMessage); + } finally { + setIsSubmitting(false); + setShowConfirmation(false); } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + // console.log('Submitted Data:', form); + + if (form.pin == '' || form.topupAmount == '') { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + setAlert({ show: false, message: '' }); + setShowConfirmation(true); + // TODO: Kirim ke backend atau proses lainnya }; + const handleCancelSubmit = () => { + setShowConfirmation(false); + }; + return ( <> @@ -70,7 +104,9 @@ const TransactionTopup = () => { -

MANAGE TRANSACTION TOPUP REQUEST

+

+ MANAGE TRANSACTION TOPUP REQUEST +

Dashboard @@ -82,6 +118,7 @@ const TransactionTopup = () => { Topup + {/* Wallet Section */}

Your Wallets

@@ -90,7 +127,9 @@ const TransactionTopup = () => {

{wallet.wallet}

- {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(wallet.amount)} + {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format( + wallet.amount + )}

))} @@ -99,10 +138,16 @@ const TransactionTopup = () => {
+ {alert.show && ( + +

{alert.message}

+
+ )} {/* form */} -
+
- * + + * { />
- * + + * { />
- +
+ {showConfirmation && ( +
+
+

Confirm Transaction

+

+ Are you sure you want to request a topup of{' '} + + {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format( + Number(form.topupAmount) + )}{' '} + {' '} + ? +

+
+ + +
+
+
+ )}