From 885ff9ef484d4dac8f9b06ed88ba136cb632e826 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Thu, 24 Apr 2025 10:53:13 +0700 Subject: [PATCH 01/13] sort disbursement by execution date --- .../history-transaction/hooks/TransactionContext.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From 552d1de84c14d920f17e305c773c0abb46fc00eb Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 24 Apr 2025 11:12:00 +0700 Subject: [PATCH 02/13] dashboard view done --- .../dashboards/home/DashboardHomePage.tsx | 11 +- .../dashboards/home/blocks/MemberActivity.tsx | 114 ++++++++++++++++++ .../home/blocks/TransactionPieChart.tsx | 89 ++++++++++++++ .../home/blocks/TransactionValue.tsx | 65 +++++----- .../blocks/DetailTransaction.tsx | 5 +- 5 files changed, 250 insertions(+), 34 deletions(-) create mode 100644 src/pages/dashboards/home/blocks/MemberActivity.tsx create mode 100644 src/pages/dashboards/home/blocks/TransactionPieChart.tsx 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..bdfd43e --- /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 = responseTransactionValue?.data?.total_customer_active_percentage ?? 0; + const totalCustomer = responseTransactionValue?.data?.total_customer ?? 0; + const activeCustomer = responseTransactionValue?.data?.total_customer_active ?? 0; + + // Hitung sudut pointer + const angle = 180 - (percentage / 100) * 180; // 180° (kiri bawah) ke 0° (kanan bawah) + const radians = (angle * Math.PI) / 180; // Mengubah derajat ke radian + 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); + const y = center + pointerLength * Math.sin(radians); + + // 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/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

From 3c15508b34ea8073d23b60c9dbba5bb385606993 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 24 Apr 2025 11:32:12 +0700 Subject: [PATCH 03/13] add confirmation dialog and fix error toast message --- .../TransactionDisbursement.tsx | 192 +++++++++++++----- .../transaction/topup/TransactionTopup.tsx | 130 +++++++++--- 2 files changed, 243 insertions(+), 79 deletions(-) diff --git a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx index 72c660d..984a8ed 100644 --- a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx +++ b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx @@ -1,31 +1,47 @@ -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 } 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 { 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 [alert, setAlert] = useState({ + show: false, + message: '' + }); 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 +55,50 @@ const TransactionDisbursement = () => { fetchWallets(); }, []); - 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); + }; + return ( <> @@ -70,7 +106,9 @@ const TransactionDisbursement = () => { -

MANAGE TRANSACTION DISBURSEMENT SALDO

+

+ MANAGE TRANSACTION DISBURSEMENT SALDO +

Dashboard @@ -90,51 +128,97 @@ 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 })} - /> -
-
- -
-
+
+
+ + * + setForm({ ...form, msisdn: e.target.value })} + /> +
+
+ + * + 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/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) + )}{' '} + {' '} + ? +

+
+ + +
+
+
+ )} From a9eb67c0cf1ad40de494219dadc1bc354c1adfdb Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 24 Apr 2025 11:40:03 +0700 Subject: [PATCH 04/13] update --- .../blocks/ApprovalDialog.tsx | 30 +++++++++++++++++-- .../blocks/DetailApprovalTransaction.tsx | 1 + 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 03f8069..270bef9 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,25 @@ 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 */}
From 74966c3dbeb085801bf244de9edd9753988da050 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 24 Apr 2025 11:41:31 +0700 Subject: [PATCH 05/13] update pin in approval transaction --- .../transaction/approval-transaction/blocks/ApprovalDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 270bef9..5eac1cb 100644 --- a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx @@ -175,6 +175,7 @@ const ApprovalDialog = () => {
Date: Thu, 24 Apr 2025 12:06:10 +0700 Subject: [PATCH 06/13] update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) 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", From 9001d75f20f1daa07eacf01778a07259c883c2c2 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Thu, 24 Apr 2025 13:15:31 +0700 Subject: [PATCH 07/13] removed delete on currency, conversion, and wallet rule --- .../master/conversion/hooks/ManageConversionContext.tsx | 6 +++--- src/pages/master/currency/hooks/ManageCurrencyContext.tsx | 6 +++--- .../master/walletRule/hooks/ManageWalletRuleContext.tsx | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) 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' } } From 908d96382951ad47633f0076480d08087f61587e Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 24 Apr 2025 14:07:33 +0700 Subject: [PATCH 08/13] update --- .../transaction/approval-transaction/blocks/ApprovalDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 5eac1cb..016fd45 100644 --- a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx @@ -176,7 +176,7 @@ const ApprovalDialog = () => {
Date: Thu, 24 Apr 2025 14:19:43 +0700 Subject: [PATCH 09/13] add dropdown msisdn on disbursement module - set limit 50, filter implemented --- .../TransactionDisbursement.tsx | 110 ++++++++++++++++-- 1 file changed, 101 insertions(+), 9 deletions(-) diff --git a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx index 984a8ed..208b3ae 100644 --- a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx +++ b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx @@ -4,7 +4,7 @@ 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'; @@ -24,17 +24,57 @@ const TransactionDisbursement = () => { 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_CUSTOMER = apiConfig.service_customer; + const dropdownRef = useRef(null); + const [alert, setAlert] = useState({ show: false, message: '' }); + const fetchCustomerMsisdn = async (sorting: any, filterValue: string) => { + setIsLoading(true); + const filter: any = + filterValue.trim().length === 0 ? {} : { msisdn: { like: `%${filterValue}%` } }; + + const query: any = { + limit: 50, + 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 { @@ -53,6 +93,18 @@ 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 doPostData = async (form: typeof initialForm) => { @@ -99,6 +151,22 @@ const TransactionDisbursement = () => { setShowConfirmation(false); }; + const handleMsisdnSearch = (e: React.ChangeEvent) => { + setSearchTerm(e.target.value); + setDropdownOpen(true); + fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], e.target.value); + }; + + 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 ( <> @@ -147,16 +215,40 @@ const TransactionDisbursement = () => { )} {/* form */} -
- +
+ * - setForm({ ...form, msisdn: e.target.value })} - /> +
+ setDropdownOpen(true)} + /> + {dropdownOpen && ( +
+ {filteredMsisdn.length > 0 ? ( + filteredMsisdn.map((item, index) => ( +
handleMsisdnSelect(item.value)} + > + {item.label} +
+ )) + ) : ( +
+ {isLoading ? 'Loading...' : 'No results found'} +
+ )} +
+ )} +
+
* From ebd21c089a8ca6570a587f180e8b8f34ff31edcb Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 24 Apr 2025 14:31:27 +0700 Subject: [PATCH 10/13] update graph in dashboard --- src/pages/dashboards/home/blocks/MemberActivity.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/dashboards/home/blocks/MemberActivity.tsx b/src/pages/dashboards/home/blocks/MemberActivity.tsx index bdfd43e..172c019 100644 --- a/src/pages/dashboards/home/blocks/MemberActivity.tsx +++ b/src/pages/dashboards/home/blocks/MemberActivity.tsx @@ -30,19 +30,19 @@ const MemberActivity = ({ startdate, enddate }: Props) => { fetchDataTransactionValue(); }, [startdate, enddate, GetData]); - const percentage = responseTransactionValue?.data?.total_customer_active_percentage ?? 0; + 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 = 180 - (percentage / 100) * 180; // 180° (kiri bawah) ke 0° (kanan bawah) - const radians = (angle * Math.PI) / 180; // Mengubah derajat ke radian + 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); - const y = center + pointerLength * Math.sin(radians); + 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; From 77831a2dec0e278b7c0e459192169dab367a61ce Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 24 Apr 2025 14:33:08 +0700 Subject: [PATCH 11/13] add debounce for searching msisdn on disbursement saldo --- .../disbursement-saldo/TransactionDisbursement.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx index 208b3ae..dcc6842 100644 --- a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx +++ b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx @@ -43,12 +43,11 @@ const TransactionDisbursement = () => { }); const fetchCustomerMsisdn = async (sorting: any, filterValue: string) => { - setIsLoading(true); const filter: any = filterValue.trim().length === 0 ? {} : { msisdn: { like: `%${filterValue}%` } }; const query: any = { - limit: 50, + limit: 1, page: 1, with_deleted: false, order_field: sorting[0].id, @@ -152,9 +151,13 @@ const TransactionDisbursement = () => { }; const handleMsisdnSearch = (e: React.ChangeEvent) => { + setIsLoading(true); setSearchTerm(e.target.value); setDropdownOpen(true); - fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], e.target.value); + const timer = setTimeout(() => { + fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], e.target.value); + }, 500); + return () => clearTimeout(timer); }; const handleMsisdnSelect = (msisdn: string) => { From 132861fcb5777e09e4f46d6f7f32a11ae430908f Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 24 Apr 2025 14:33:54 +0700 Subject: [PATCH 12/13] fix limit to 25 --- .../transaction/disbursement-saldo/TransactionDisbursement.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx index dcc6842..c6a6ff6 100644 --- a/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx +++ b/src/pages/transaction/disbursement-saldo/TransactionDisbursement.tsx @@ -47,7 +47,7 @@ const TransactionDisbursement = () => { filterValue.trim().length === 0 ? {} : { msisdn: { like: `%${filterValue}%` } }; const query: any = { - limit: 1, + limit: 25, page: 1, with_deleted: false, order_field: sorting[0].id, From a52bd9e951c9da40a67508307f892748a992262e Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Thu, 24 Apr 2025 14:35:19 +0700 Subject: [PATCH 13/13] added msisdn on table --- .../hooks/ManageKycDeletionContext.tsx | 9 +++++++++ src/pages/members/kyc/Columns.tsx | 14 ++++++++++++++ 2 files changed, 23 insertions(+) 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 }) => {