From 901a0743cf461fce9b0b3f7b1d56f83485c7f817 Mon Sep 17 00:00:00 2001 From: Abdul Rahman Reza Date: Thu, 29 May 2025 17:35:30 +0900 Subject: [PATCH 1/8] add logic [skip ci] --- .woodpecker.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.woodpecker.yml b/.woodpecker.yml index a6f593a..949665f 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,3 +1,11 @@ +when: + commit: + message: + exclude: + - '\[skip ci\]' + - '\[ci skip\]' + - '\[no ci\]' + steps: - name: telegram start notify image: appleboy/drone-telegram From 5c6d834c199d1ebd446af14c6209cdae3adeb88d Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 29 May 2025 16:49:10 +0700 Subject: [PATCH 2/8] [skip ci] add feature withdrawl emoney --- .../withdrawl-emoney/WithdrawalEmoney.tsx | 36 +++ .../withdrawl-emoney/blocks/WithdrawInput.tsx | 300 ++++++++++++++++++ .../TransactionWithdrawalEmoneyContext.tsx | 59 ++++ .../hooks/useTransactionWithdrawalEmoney.tsx | 12 + .../hooks/useTransactionWithdrawContext.tsx | 4 +- src/routing/AppRoutingSetup.tsx | 2 + 6 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 src/pages/transaction/withdrawl-emoney/WithdrawalEmoney.tsx create mode 100644 src/pages/transaction/withdrawl-emoney/blocks/WithdrawInput.tsx create mode 100644 src/pages/transaction/withdrawl-emoney/hooks/TransactionWithdrawalEmoneyContext.tsx create mode 100644 src/pages/transaction/withdrawl-emoney/hooks/useTransactionWithdrawalEmoney.tsx diff --git a/src/pages/transaction/withdrawl-emoney/WithdrawalEmoney.tsx b/src/pages/transaction/withdrawl-emoney/WithdrawalEmoney.tsx new file mode 100644 index 0000000..d4476a4 --- /dev/null +++ b/src/pages/transaction/withdrawl-emoney/WithdrawalEmoney.tsx @@ -0,0 +1,36 @@ +import { Helmet } from 'react-helmet'; +import { TransactionWithdrawEmoneyProvider } from './hooks/TransactionWithDrawalEmoneyContext'; +import { Container } from '@/components'; +import { Breadcrumbs, Link } from '@mui/material'; +import WithdrawInput from './blocks/WithdrawInput'; + +const WithdrawalEmoney = () => { + return ( + <> + + TPAY | Withdrawal Emoney + + + +

+ TRANSACTION WITHDRAWAL EMONEY +

+ + + Dashboard + + + Transaction + + + Withdraw Saldo + + + +
+
+ + ); +}; + +export default WithdrawalEmoney; diff --git a/src/pages/transaction/withdrawl-emoney/blocks/WithdrawInput.tsx b/src/pages/transaction/withdrawl-emoney/blocks/WithdrawInput.tsx new file mode 100644 index 0000000..b68aef1 --- /dev/null +++ b/src/pages/transaction/withdrawl-emoney/blocks/WithdrawInput.tsx @@ -0,0 +1,300 @@ +import { getAuth } from '@/auth'; +import { Alert, Container } from '@/components'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { RefreshCw } from 'lucide-react'; +import { useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; + +const WithdrawInput = () => { + const initialForm: { + msisdn: string; + amount: string; + pin: string; + purpose: string; + } = { + msisdn: '', + amount: '', + pin: '', + purpose: '' + }; + + const [form, setForm] = useState(initialForm); + 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 ResetForm = () => { + setForm(initialForm); + setAlert({ show: false, message: '' }); + setSearchTerm(''); + }; + + const fetchCustomerMsisdn = async (sorting: any, filterValue: string) => { + const filter: any = + filterValue.trim().length === 0 + ? {} + : { + or: [ + { msisdn: { like: `%${filterValue}%` } }, + { fullname: { like: `%${filterValue}%` } } + ] + }; + + const query: any = { + limit: 100, + 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); + } + }; + + const doPostData = async (form: typeof initialForm) => { + setIsSubmitting(true); + // console.log(getAuth()?.user.customer.id); + + try { + let response = await PostData(`${API_URL}/transaction/transfer`, { + msisdn_destination: form.msisdn, + amount: form.amount, + pin: form.pin, + purpose: form.purpose, + id_transaction_type: '380f270b-1f44-406f-a196-d6f784d68eb4', + id_origin_customer: getAuth()?.user.customer.id, + type: 'S' + }); + if (response?.status == true) { + toast.success('Success Request Topup'); + } else { + toast.error(`${response?.message?.message}`); + } + } 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); + ResetForm(); + } + }; + + 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); + }; + + 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); + }; + + useEffect(() => { + 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 filteredMsisdn = customerMsisdn + .filter((item) => item.label.toLowerCase().includes(searchTerm.toLowerCase())) + .slice(0, 10); + + return ( + <> + +
+
+ {alert.show && ( + +

{alert.message}

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

Confirm Transaction

+

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

+
+ + +
+
+
+ )} + + ); +}; + +export default WithdrawInput; diff --git a/src/pages/transaction/withdrawl-emoney/hooks/TransactionWithdrawalEmoneyContext.tsx b/src/pages/transaction/withdrawl-emoney/hooks/TransactionWithdrawalEmoneyContext.tsx new file mode 100644 index 0000000..9850401 --- /dev/null +++ b/src/pages/transaction/withdrawl-emoney/hooks/TransactionWithdrawalEmoneyContext.tsx @@ -0,0 +1,59 @@ +import { apiConfig } from '@/config/api.config'; +import { createContext } from 'react'; + +interface TransactionWithdrawProps { + id: string; + customers_id: string; + group_id: string; + username: string; + fullname: string; + email: string; + status: string; + created_at: Date; +} + +interface ContextProps {} + +const initialProps: ContextProps = {}; + +const TranscationWithdrawEmoneyContext = createContext(initialProps); +const API_URL = apiConfig.service_customer; + +type StatusCode = 'W' | 'Y' | 'N' | 'T'; + +interface StatusInfo { + label: string; + bg: string; + text: string; +} + +const statusMap: Record = { + W: { label: 'Waiting Approval', bg: 'bg-yellow-100', text: 'text-yellow-600' }, + T: { label: 'No Need', bg: 'bg-blue-100', text: 'text-blue-600' }, + N: { label: 'Reject', bg: 'bg-red-100', text: 'text-red-600' }, + Y: { label: 'Approve', 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 TransactionWithdrawEmoneyProvider = ({ children }: { children: React.ReactNode }) => { + return ( + +
{children}
+
+ ); +}; + +export { TransactionWithdrawEmoneyProvider, TranscationWithdrawEmoneyContext }; +export type { TransactionWithdrawProps }; diff --git a/src/pages/transaction/withdrawl-emoney/hooks/useTransactionWithdrawalEmoney.tsx b/src/pages/transaction/withdrawl-emoney/hooks/useTransactionWithdrawalEmoney.tsx new file mode 100644 index 0000000..93c361d --- /dev/null +++ b/src/pages/transaction/withdrawl-emoney/hooks/useTransactionWithdrawalEmoney.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { TransactionWithdrawContext } from '../../withdrawl-saldo/hooks'; + +const useTransactionWithdrawalEmoney = () => { + const context = useContext(TransactionWithdrawContext); + + if (!context) throw new Error('useTransactionWithdrawalEmoney must be used within AuthProvider'); + + return context; +}; + +export { useTransactionWithdrawalEmoney }; diff --git a/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx b/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx index 64fe126..67e8288 100644 --- a/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx +++ b/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx @@ -1,8 +1,8 @@ import { useContext } from 'react'; -import { TransactionWithdrawContext } from './TransactionWithdrawContext'; +import { TranscationWithdrawEmoneyContext } from '../../withdrawl-emoney/hooks/TransactionWithDrawalEmoneyContext'; const useTransactionWithdrawContext = () => { - const context = useContext(TransactionWithdrawContext); + const context = useContext(TranscationWithdrawEmoneyContext); if (!context) throw new Error('useTransactionWithdrawContext must be used within AuthProvider'); diff --git a/src/routing/AppRoutingSetup.tsx b/src/routing/AppRoutingSetup.tsx index 1da4e34..ab42475 100644 --- a/src/routing/AppRoutingSetup.tsx +++ b/src/routing/AppRoutingSetup.tsx @@ -49,6 +49,7 @@ import AgentBalance from '@/pages/members/agent-balance/AgentBalance'; // DISBURSEMENT import HistoryTransactionDisbursement from '@/pages/disbursement/history-transaction/HistoryTransaction'; +import WithdrawalEmoney from '@/pages/transaction/withdrawl-emoney/WithdrawalEmoney'; // DISBURSEMENT @@ -110,6 +111,7 @@ const AppRoutingSetup = (): ReactElement => { } /> } /> } /> + } /> } /> } /> } /> From c5073b88be0122f8188d9c37c4b69fcd8182eef8 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Thu, 29 May 2025 17:12:59 +0700 Subject: [PATCH 3/8] [skip ci] Change filter on transfer type to button --- .../transfertype/blocks/ListToolBar.tsx | 121 ++++++++++-------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/src/pages/transfer/transfertype/blocks/ListToolBar.tsx b/src/pages/transfer/transfertype/blocks/ListToolBar.tsx index 832c484..8c92dbf 100644 --- a/src/pages/transfer/transfertype/blocks/ListToolBar.tsx +++ b/src/pages/transfer/transfertype/blocks/ListToolBar.tsx @@ -52,41 +52,15 @@ const ListToolbar = () => { const { handleAddDialog } = useManageTransferTypeContext(); const [transferTypes, setTransferTypes] = useState([]); const { GetData } = useCallApi(); - const [statusKind, setStatusKind] = useState(''); - const [searchValue, setSearchValue] = useState(''); - const [statusTypes, setStatusTypes] = useState(''); - const [approval, setApproval] = useState(''); + + const [tempSearchValue, setTempSearchValue] = useState(''); + const [tempStatusTypes, setTempStatusTypes] = useState(''); + const [tempApproval, setTempApproval] = useState(''); + const [tempStatusKind, setTempStatusKind] = useState(''); const unique = (arr: string[]) => Array.from(new Set(arr)); const types = unique(transferTypes.map((t) => t.type)); - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { -table.getColumn('name')?.setFilterValue(`%${searchValue}%`); - table.setPageIndex(0); - } - }; - - useEffect(() => { - const timer = setTimeout(() => { -table.getColumn('name')?.setFilterValue(`%${searchValue}%`); - table.setPageIndex(0); - }, 200); - return () => clearTimeout(timer); - }, [searchValue, table]); - - useEffect(() => { - table.getColumn('type')?.setFilterValue(statusTypes); - }, [statusTypes, table]); - - useEffect(() => { - table.getColumn('status_approval')?.setFilterValue(approval); - }, [approval, table]); - - useEffect(() => { - table.getColumn('status_kind')?.setFilterValue(statusKind); - }, [statusKind, table]); - const fetchTransferTypes = async () => { try { const response = await GetData(`${API_URL}/transactiontype/list`, { @@ -106,11 +80,21 @@ table.getColumn('name')?.setFilterValue(`%${searchValue}%`); fetchTransferTypes(); }, []); + const handleApplyFilter = () => { + table.getColumn('name')?.setFilterValue(tempSearchValue ? `%${tempSearchValue}%` : ''); + + table.getColumn('type')?.setFilterValue(tempStatusTypes); + table.getColumn('status_approval')?.setFilterValue(tempApproval); + table.getColumn('status_kind')?.setFilterValue(tempStatusKind); + + table.setPageIndex(0); + }; + const handleClearFilter = () => { - setSearchValue(''); - setStatusTypes(''); - setApproval(''); - setStatusKind(''); + setTempSearchValue(''); + setTempStatusTypes(''); + setTempApproval(''); + setTempStatusKind(''); table.getColumn('status_kind')?.setFilterValue(''); table.getColumn('name')?.setFilterValue(''); @@ -123,6 +107,27 @@ table.getColumn('name')?.setFilterValue(`%${searchValue}%`); }, 0); }; + const handleRefreshAndClearFilter = () => { + setTempSearchValue(''); + setTempStatusTypes(''); + setTempApproval(''); + setTempStatusKind(''); + + table.getColumn('name')?.setFilterValue(''); + table.getColumn('type')?.setFilterValue(''); + table.getColumn('status_approval')?.setFilterValue(''); + table.getColumn('status_kind')?.setFilterValue(''); + + table.setPageIndex(0); + reload(); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleApplyFilter(); + } + }; + return (
@@ -135,18 +140,18 @@ table.getColumn('name')?.setFilterValue(`%${searchValue}%`); setSearchValue(e.target.value)} - className="input input-sm w-full pl-9 text-ellipsis overflow-hidden" + value={tempSearchValue} + onChange={(e) => setTempSearchValue(e.target.value)} + className="input input-sm w-full pl-9 text-ellipsis overflow-hidden" onKeyDown={handleKeyDown} />
+
setWalletId(value)}> + setGroupId(value)}> +