From 99687023a3bdccf6e520d10d77a51c02f2979566 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Mon, 28 Apr 2025 15:24:20 +0700 Subject: [PATCH 1/9] alert for approval transaction --- package.json | 1 + .../account/home/user-profile/blocks/index.ts | 1 + .../blocks/ApprovalDialog.tsx | 64 +++++++++++-------- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 89c60d4..09bf451 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "styled-components": "^6.1.13", "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1", + "sweetalert2": "^11.19.1", "tabs": "^0.2.0", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", diff --git a/src/pages/account/home/user-profile/blocks/index.ts b/src/pages/account/home/user-profile/blocks/index.ts index 623b2f0..00174e5 100644 --- a/src/pages/account/home/user-profile/blocks/index.ts +++ b/src/pages/account/home/user-profile/blocks/index.ts @@ -1,2 +1,3 @@ export * from './BasicSettings'; export * from './Password'; +export * from './PinCode'; diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 016fd45..da5a4b8 100644 --- a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx @@ -22,6 +22,7 @@ import { doSaveLogActivity } from '@/actions/GlobalActions'; import { toast } from 'sonner'; import { Input } from '@/components/ui/input'; import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import Swal from 'sweetalert2'; // ✅ IMPORT sweetalert2 const API_URL = apiConfig.transaction; @@ -63,34 +64,48 @@ const ApprovalDialog = () => { return; } - const response = await PostData(`${API_URL}/transaction/set-approval`, { - id_transaction: transactionDetails.id, - notes: formField.notes, - status: formField.status, - pin: formField.pin, + // ✅ TUTUP Dialog sebelum munculkan SweetAlert + setShowApprovalDialog(false); + + // ✅ TAMPILKAN SWEETALERT + const result = await Swal.fire({ + title: 'Are you sure?', + text: "You want to save changes?", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, save it!', + cancelButtonText: 'Cancel' }); - if (response?.status === false) { - setAlert({ - show: true, - message: response?.message?.error?.message || 'Approval failed', + if (result.isConfirmed) { + // ✅ Kalau tekan YES, baru tembak API + const response = await PostData(`${API_URL}/transaction/set-approval`, { + id_transaction: transactionDetails.id, + notes: formField.notes, + status: formField.status, + pin: formField.pin, }); - return; - } - if (response?.status) { - setAlert({ show: false, message: '' }); - toast.success('Success Update Approval'); - const createActivity = { - module: 'Approval Transaction', - description: `Change status approve for transaction => ${transactionDetails.code}`, - action: 'U', - }; - doSaveLogActivity(createActivity); - setShowApprovalDialog(false); - reload(); + if (response?.status === false) { + toast.error(response?.message?.error?.message || 'Approval failed'); + return; + } + + if (response?.status) { + toast.success('Success Update Approval'); + const createActivity = { + module: 'Approval Transaction', + description: `Change status approve for transaction => ${transactionDetails.code}`, + action: 'U', + }; + doSaveLogActivity(createActivity); + reload(); + } } else { - setAlert({ show: true, message: response?.message }); + // ✅ Kalau tekan Cancel + console.log('User cancelled'); } }, [formField, transactionDetails] @@ -102,7 +117,7 @@ const ApprovalDialog = () => { transaction_code: '', notes: '', status: '', - pin:'' + pin: '' }); setTransactionDetails(null); setAlert({ show: false, message: '' }); @@ -171,7 +186,6 @@ const ApprovalDialog = () => {
-
Date: Mon, 28 Apr 2025 15:51:05 +0700 Subject: [PATCH 2/9] update transaction detail --- .../home/user-profile/blocks/PinCode.tsx | 158 +++--------------- .../hooks/AccountUserProfileContext.tsx | 60 +++++-- .../blocks/DetailApprovalTransaction.tsx | 52 +++++- .../blocks/DetailTransaction.tsx | 58 ++++++- 4 files changed, 166 insertions(+), 162 deletions(-) diff --git a/src/pages/account/home/user-profile/blocks/PinCode.tsx b/src/pages/account/home/user-profile/blocks/PinCode.tsx index da647ca..ff0ba0d 100644 --- a/src/pages/account/home/user-profile/blocks/PinCode.tsx +++ b/src/pages/account/home/user-profile/blocks/PinCode.tsx @@ -1,95 +1,50 @@ -import { useState, useContext, useEffect, useCallback, MouseEvent } from 'react'; +import { useState, useContext, useCallback, MouseEvent } from 'react'; import { AccountUserProfileContext } from '../hooks'; import { toast } from 'sonner'; import { useAuthContext } from '@/auth'; import { KeenIcon } from '@/components'; import clsx from 'clsx'; -type PasswordType = 'password' | 'retype_password' | 'current_password'; +type PasswordType = 'password'; + const PinCode = () => { - const { setPassword } = useContext(AccountUserProfileContext); + const { setPincode } = useContext(AccountUserProfileContext); const { getUser } = useAuthContext(); - const [newPassword, setNewPassword] = useState(''); - const [currentPassword, setCurrentPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); + const [newPincode, setNewPincode] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); - const [messagePassword, setMessagePassword] = useState(true); const [showPassword, setShowPassword] = useState({ - current_password: false, password: false, - retype_password: false }); - const [passwordErrors, setPasswordErrors] = useState([]); - - const validatePassword = (password: string) => { - const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); - const hasCapitalLetter = /[A-Z]/.test(password); - const hasNumber = /[0-9]/.test(password); - return hasSpecialChar && hasCapitalLetter && hasNumber; - }; - - useEffect(() => { - const passwordsMatch = newPassword === confirmPassword; - const isPasswordValid = validatePassword(newPassword); - setMessagePassword(passwordsMatch && isPasswordValid); - - const errors: string[] = []; - if (newPassword) { - if (!/[A-Z]/.test(newPassword)) { - errors.push('Password must contain at least one capital letter'); - } - if (!/[0-9]/.test(newPassword)) { - errors.push('Password must contain at least one number'); - } - if (!/[!@#$%^&*(),.?":{}|<>]/.test(newPassword)) { - errors.push('Password must contain at least one special character'); - } - if (newPassword !== confirmPassword) { - errors.push('Passwords do not match'); - } - } - setPasswordErrors(errors); - }, [newPassword, confirmPassword]); - const handleResetPassword = useCallback(async () => { const user: any = await getUser(); - if (!messagePassword) { - toast.error('Passwords do not match!'); - return; - } - setIsSubmitting(true); try { - await setPassword({ - current_password: currentPassword, - password: newPassword, - retype_password: confirmPassword, - username: user.data.username ?? '' + await setPincode({ + newpincode: newPincode, }); - setNewPassword(''); - setConfirmPassword(''); - setCurrentPassword(''); + setNewPincode(''); + toast.success('Pin code updated successfully'); } catch (error: any) { const errorMessage = error?.response?.data?.message || error?.message || - 'An error occurred while resetting the password.'; + 'An error occurred while updating the pin code.'; toast.error(errorMessage); } finally { setIsSubmitting(false); } - }, [currentPassword, newPassword, newPassword, messagePassword, getUser]); + }, [newPincode, setPincode, getUser]); - const isButtonDisabled = isSubmitting || !newPassword || !confirmPassword || !messagePassword; + const isButtonDisabled = isSubmitting || !newPincode; - const togglePassword = useCallback((event: MouseEvent, key: string) => { + const togglePassword = useCallback((event: MouseEvent, key: PasswordType) => { event.preventDefault(); - setShowPassword((prev) => ({ ...prev, [key]: !prev[key as PasswordType] })); + setShowPassword((prev) => ({ ...prev, [key]: !prev[key] })); }, []); return ( @@ -99,89 +54,28 @@ const PinCode = () => {
- -
+ +
setCurrentPassword(e.target.value)} - disabled={isSubmitting} - /> - -
-
-
- -
- setNewPassword(e.target.value)} + value={newPincode} + onChange={(e) => setNewPincode(e.target.value)} // ✅ fix disabled={isSubmitting} type={showPassword.password ? 'text' : 'password'} /> -
-
-
- -
- setConfirmPassword(e.target.value)} - disabled={isSubmitting} - /> - -
-
-
- - {passwordErrors.length > 0 && ( -
- {passwordErrors.map((error, index) => ( -

{error}

- ))} -
- )} -
-
+ {/* New Pin Code */} +
+ +
+ { + setNewPincode(e.target.value); + // Kalau retype sudah diisi, cek lagi error + if (retypePincode) { + if (e.target.value !== retypePincode) { + setErrorRetype('New Pin Code and Retype Pin Code do not match.'); + } else { + setErrorRetype(''); + } + } + }} + disabled={isSubmitting} + type={showPassword.new ? 'text' : 'password'} + /> + +
+
+ + {/* Retype Pin Code */} +
+ +
+ handleRetypeChange(e.target.value)} + disabled={isSubmitting} + type={showPassword.retype ? 'text' : 'password'} + /> + +
+
+ + {/* Error message under Retype */} + {errorRetype && ( +
{errorRetype}
+ )} + + {/* Submit button */}
+
- diff --git a/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx b/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx index e2b2edd..136c9b0 100644 --- a/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx +++ b/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx @@ -6,6 +6,16 @@ import { ColumnDef } from '@tanstack/react-table'; import React, { createContext, useCallback, useMemo, useState } from 'react'; import ListToolbar from '../blocks/ListToolbar'; +// Helper function for number formatting with currency format +const formatNumber = (num: number, currencyCode: string = 'USD'): string => { + return num.toLocaleString('en-US', { + style: 'currency', + currency: currencyCode, + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); +}; + interface WalletProps { id: string; name: string; @@ -23,11 +33,10 @@ interface ContextProps { handleDeleteDialog: (show: boolean, selected_wallet: WalletProps | null) => void; selectedWallet: WalletProps | null; getWalletLists: ( - limit: number, page: number, - with_deleted: boolean, - order_field: any, - order_direction: any + limit: number, + sorting: any, + filter: any ) => Promise<{ data: WalletProps[]; totalCount: number } | undefined>; } @@ -81,8 +90,22 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } } }, { - accessorKey: 'amount' , + accessorKey: 'id_wallet', + header: ({ column }) => , + enableSorting: false, + enableHiding: true, // Hide this column from view but use it for filtering + meta: { + headerClassName: 'w-[200px]' + } + }, + { + accessorKey: 'amount', header: ({ column }) => , + cell: ({ row }) => { + // Get currency code from the nested data structure if available + const currencyCode = row.original.balance_type?.currency?.code || 'USD'; + return formatNumber(row.original.amount, currencyCode); + }, enableSorting: false, enableHiding: false, meta: { @@ -90,8 +113,10 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } } }, { - accessorKey: 'trx_count_today' , - header: ({ column }) => , + accessorKey: 'trx_count_today', + header: ({ column }) => ( + + ), enableSorting: false, enableHiding: false, meta: { @@ -99,8 +124,13 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } } }, { - accessorKey: 'amount_this_month' , - header: ({ column }) => , + accessorKey: 'amount_this_month', + header: ({ column }) => , + cell: ({ row }) => { + // Get currency code from the nested data structure if available + const currencyCode = row.original.balance_type?.currency?.code || 'USD'; + return formatNumber(row.original.amount_this_month, currencyCode); + }, enableSorting: false, enableHiding: false, meta: { @@ -109,16 +139,14 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } }, { accessorKey: 'CreatedAt', - header: ({ column }) => ( - - ), - cell: ({ row }) => + header: ({ column }) => , + cell: ({ row }) => new Date(row.original.CreatedAt).toLocaleString('id-ID', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', - minute: '2-digit', + minute: '2-digit' }), enableSorting: false, enableHiding: false, @@ -182,22 +210,54 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } const getWalletLists = async (page: number, limit: number, sorting: any, filter: any) => { try { - const sortField = 'CreatedAt'; - const sortDirection = 'ASC'; - - filter = filter.length == 0 ? {} : { name: filter[0].value?.toLowerCase() }; + const sortField = sorting.length > 0 ? sorting[0].id : 'created_at'; + const sortDirection = sorting.length > 0 ? (sorting[0].desc ? 'DESC' : 'ASC') : 'ASC'; + + // Initialize filter object + let filterParams: any = {}; + + // Process filter array + if (Array.isArray(filter)) { + filter.forEach((f: any) => { + // Handle MSISDN search + if (f.id === 'msisdn' && f.value) { + filterParams.msisdn = { like: `%${f.value.toLowerCase()}%` }; + } + + // Handle date range filter + if (f.id === 'CreatedAt' && f.value?.from && f.value?.to) { + filterParams.created_at = { + from: `${f.value.from} 00:00:00`, + to: `${f.value.to} 23:59:59` + }; + } + + // Handle wallet ID filter + if (f.id === 'id_wallet' && f.value) { + filterParams.id_wallet = f.value; + } + }); + } + const response = await GetData(`${API_URL_WALLET}/dashboard/balance/`, { limit, page: page + 1, with_deleted: false, order_field: sortField, order_direction: sortDirection, - // filter: JSON.stringify(filter) + filter: JSON.stringify(filterParams) }); + + if (!response || !response.data) { + console.warn('No data received:', response); + return { data: [], totalCount: 0 }; + } + setWallets(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { console.error('Error fetching Wallet', error); + return { data: [], totalCount: 0 }; } }; @@ -221,7 +281,6 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } pagination={{ size: 10 }} toolbar={} layout={{ card: true }} - // sorting={[{ id: 'created_at', desc: true }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => getWalletLists(pageIndex, pageSize, sorting, columnFilters) From 6664abd77736eaa43f664787f95c52c7ce1ba913 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Mon, 28 Apr 2025 20:41:35 +0700 Subject: [PATCH 7/9] fixing sorting from the newest on wallet history --- .../wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx b/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx index 136c9b0..c2a99d3 100644 --- a/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx +++ b/src/pages/wallet/wallet-history/hooks/ManageWalletHistoryContext.tsx @@ -211,7 +211,7 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } const getWalletLists = async (page: number, limit: number, sorting: any, filter: any) => { try { const sortField = sorting.length > 0 ? sorting[0].id : 'created_at'; - const sortDirection = sorting.length > 0 ? (sorting[0].desc ? 'DESC' : 'ASC') : 'ASC'; + const sortDirection = sorting.length > 0 ? (sorting[0].desc ? 'ASC' : 'DESC') : 'DESC'; // Initialize filter object let filterParams: any = {}; From 04330e3acfc4ce121c83d2337ba55a685bcb48aa Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Tue, 29 Apr 2025 09:30:28 +0700 Subject: [PATCH 8/9] update logs --- .../blocks/DetailApprovalTransaction.tsx | 54 ++++++++++-------- .../blocks/DetailTransaction.tsx | 56 ++++++++++--------- 2 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx b/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx index 81d0025..4b5d549 100644 --- a/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx +++ b/src/pages/transaction/approval-transaction/blocks/DetailApprovalTransaction.tsx @@ -504,38 +504,44 @@ const DetailApprovalTransaction = () => { - + - + - + {transactionDetails?.log && transactionDetails?.log.length > 0 ? ( - transactionDetails.log.map((log: { request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( + transactionDetails.log.map((log: { type: string, request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( - - - - - - + + + + + + )) ) : ( diff --git a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx index c60a661..29c65c4 100644 --- a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx @@ -45,7 +45,7 @@ const DetailTransaction = () => { id: selectedTransactionId }); setTransactionDetails(response?.data); - // console.log(response?.data); + console.log(response?.data); // console.log(selectedTransactionId); } catch (error) { console.error('Error fetching transaction', error); @@ -515,38 +515,44 @@ const DetailTransaction = () => {
StatusType Request DateRequest BodyResponse Date Response Body Response CodeRequest End PointRequest Endpoint
- {(() => { - let status; - if (log.status === 'P') { - status = 'PENDING'; - } else if (log.status === 'O') { - status = 'ON PROCESS'; - } else if (log.status === 'F') { - status = 'FAILED'; - } else if (log.status === 'C') { - status = 'COMPLETE'; - } - return status; - })()} - {log.request_date ?? '-'}{log.response_date ?? '-'}{log.request_body ?? '-'}{log.response_body ?? '-'}{log.request_endpoint ?? '-'}{log.type}{log.request_date + ? new Date(log.request_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}{log.response_date + ? new Date(log.response_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}{log.request_body}{log.response_body}{log.request_endpoint}
- + - + - + {transactionDetails?.log && transactionDetails?.log.length > 0 ? ( - transactionDetails.log.map((log: { request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( + transactionDetails.log.map((log: { type: string, request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( - - - - - - + + + + + + )) ) : ( From a4541a82ee3da9bcea864dcf5b4af3a3bdce75bd Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Tue, 29 Apr 2025 09:31:08 +0700 Subject: [PATCH 9/9] update --- .../history-transaction/blocks/DetailTransaction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx index 29c65c4..de6a873 100644 --- a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx @@ -45,7 +45,7 @@ const DetailTransaction = () => { id: selectedTransactionId }); setTransactionDetails(response?.data); - console.log(response?.data); + // console.log(response?.data); // console.log(selectedTransactionId); } catch (error) { console.error('Error fetching transaction', error);
StatusType Request DateRequest BodyResponse Date Response Body Response CodeRequest End PointRequest Endpoint
- {(() => { - let status; - if (log.status === 'P') { - status = 'PENDING'; - } else if (log.status === 'O') { - status = 'ON PROCESS'; - } else if (log.status === 'F') { - status = 'FAILED'; - } else if (log.status === 'C') { - status = 'COMPLETE'; - } - return status; - })()} - {log.request_date ?? '-'}{log.response_date ?? '-'}{log.request_body ?? '-'}{log.response_body ?? '-'}{log.request_endpoint ?? '-'}{log.type}{log.request_date + ? new Date(log.request_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}{log.response_date + ? new Date(log.response_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}{log.request_body}{log.response_body}{log.request_endpoint}