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/config/api.config.ts b/src/config/api.config.ts index 47c5471..56e9c15 100644 --- a/src/config/api.config.ts +++ b/src/config/api.config.ts @@ -2,6 +2,7 @@ interface apiConfigProps { service_dashboard: string; service_customer: string; service_master_data: string; + // service_master_data2: string; service_transaction: string; service_wallet: string; transaction: string; @@ -18,7 +19,7 @@ const apiConfig: apiConfigProps = { // service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ''}`, service_dashboard: `${API_URL}/d`, service_customer: `${API_URL}/c`, - // service_master_data: `${API_URL}/m` + // service_master_data2: `${API_URL}/m`, service_master_data: `${API_URL}/t`, service_transaction: `${API_URL}/tt`, service_wallet: `${API_URL}/w`, diff --git a/src/pages/account/home/user-profile/blocks/PinCode.tsx b/src/pages/account/home/user-profile/blocks/PinCode.tsx index da647ca..64fcd3f 100644 --- a/src/pages/account/home/user-profile/blocks/PinCode.tsx +++ b/src/pages/account/home/user-profile/blocks/PinCode.tsx @@ -1,96 +1,74 @@ -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 { getAuth,useAuthContext } from '@/auth'; import { KeenIcon } from '@/components'; -import clsx from 'clsx'; -type PasswordType = 'password' | 'retype_password' | 'current_password'; +type PasswordType = 'current' | 'new' | 'retype'; + const PinCode = () => { - const { setPassword } = useContext(AccountUserProfileContext); - const { getUser } = useAuthContext(); + const { setPincode } = useContext(AccountUserProfileContext); - const [newPassword, setNewPassword] = useState(''); - const [currentPassword, setCurrentPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); + const [currentPincode, setCurrentPincode] = useState(''); + const [newPincode, setNewPincode] = useState(''); + const [retypePincode, setRetypePincode] = useState(''); + const [errorRetype, setErrorRetype] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); - const [messagePassword, setMessagePassword] = useState(true); const [showPassword, setShowPassword] = useState({ - current_password: false, - password: false, - retype_password: false + current: false, + new: false, + retype: 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!'); + if (newPincode !== retypePincode) { + toast.error('New Pin Code and Retype Pin Code do not match.'); + setErrorRetype('New Pin Code and Retype Pin Code do not match.'); return; } setIsSubmitting(true); try { - await setPassword({ - current_password: currentPassword, - password: newPassword, - retype_password: confirmPassword, - username: user.data.username ?? '' + await setPincode({ + currentpincode: currentPincode, + newpincode: newPincode, + retypepincode: retypePincode, }); - setNewPassword(''); - setConfirmPassword(''); - setCurrentPassword(''); + setCurrentPincode(''); + setNewPincode(''); + setRetypePincode(''); + setErrorRetype(''); // Clear error after success } 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]); + }, [currentPincode, newPincode, retypePincode, setPincode]); - const isButtonDisabled = isSubmitting || !newPassword || !confirmPassword || !messagePassword; + const isButtonDisabled = isSubmitting || !currentPincode || !newPincode || !retypePincode; - const togglePassword = useCallback((event: MouseEvent, key: string) => { - event.preventDefault(); - setShowPassword((prev) => ({ ...prev, [key]: !prev[key as PasswordType] })); - }, []); + const togglePassword = useCallback( + (event: MouseEvent, key: PasswordType) => { + event.preventDefault(); + setShowPassword((prev) => ({ ...prev, [key]: !prev[key] })); + }, + [] + ); + + const handleRetypeChange = (value: string) => { + setRetypePincode(value); + if (newPincode && value && newPincode !== value) { + setErrorRetype('New Pin Code and Retype Pin Code do not match.'); + } else { + setErrorRetype(''); + } + }; return (
@@ -98,91 +76,97 @@ const PinCode = () => {

Pin Code

+ {/* Current Pin Code */}
-
+
setCurrentPassword(e.target.value)} + value={currentPincode} + onChange={(e) => setCurrentPincode(e.target.value)} disabled={isSubmitting} + type={showPassword.current ? 'text' : 'password'} /> -
-
- -
- setNewPassword(e.target.value)} - 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 */}
-
{

Transaction Date

-

{transactionDetails?.transaction_date}

+

{transactionDetails?.transaction_date + ? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}

Full Name

@@ -299,8 +309,17 @@ const DetailApprovalTransaction = () => {

Transaction Date

-

{transactionDetails?.transaction_date}

-
+

{transactionDetails?.transaction_date + ? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}

Full Name

@@ -485,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) => ( - - - - - - + + + + + + )) ) : ( @@ -607,7 +632,7 @@ const DetailApprovalTransaction = () => { - + @@ -618,8 +643,29 @@ const DetailApprovalTransaction = () => { transactionDetails.p24.map((log: { request_endpoint: string, type: 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 39f5b07..de6a873 100644 --- a/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/transaction/history-transaction/blocks/DetailTransaction.tsx @@ -45,8 +45,8 @@ const DetailTransaction = () => { id: selectedTransactionId }); setTransactionDetails(response?.data); - console.log(response?.data); - console.log(selectedTransactionId); + // console.log(response?.data); + // console.log(selectedTransactionId); } catch (error) { console.error('Error fetching transaction', error); } @@ -141,7 +141,20 @@ const DetailTransaction = () => {

Transaction Date

-

{transactionDetails?.transaction_date}

+

+ {transactionDetails?.transaction_date + ? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''} +

+

Full Name

@@ -312,7 +325,17 @@ const DetailTransaction = () => {

Transaction Date

-

{transactionDetails?.transaction_date}

+

{transactionDetails?.transaction_date + ? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + : ''}

Full Name

@@ -492,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}
Type Request DateRequest BodyResponse Date Response Body Response Code Request Endpoint
{log.type}{log.request_date}{log.response_date}{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) => ( - - - - - - + + + + + + )) ) : ( @@ -614,7 +643,7 @@ const DetailTransaction = () => { - + @@ -625,8 +654,29 @@ const DetailTransaction = () => { transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( - - + + + diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index 2253d83..1167b49 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -22,6 +22,15 @@ function useDebounce(value: T, delay: number): T { return debouncedValue; } +const formatNumber = (num: number): string => { + return num.toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); +}; + interface AccountProps { id: string; name: string; @@ -147,6 +156,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React accessorFn: (row) => row.maximum_amount, id: 'maximum_amount', header: ({ column }) => , + cell: ({ row }) => formatNumber(row.original.maximum_amount), enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' } @@ -155,6 +165,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React accessorFn: (row) => row.minimum_amount, id: 'minimum_amount', header: ({ column }) => , + cell: ({ row }) => formatNumber(row.original.minimum_amount), enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' } @@ -165,6 +176,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React header: ({ column }) => ( ), + cell: ({ row }) => formatNumber(row.original.max_transaction_per_day), enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' } diff --git a/src/pages/wallet/wallet-history/blocks/ListToolbar.tsx b/src/pages/wallet/wallet-history/blocks/ListToolbar.tsx index 546a14a..683e9ae 100644 --- a/src/pages/wallet/wallet-history/blocks/ListToolbar.tsx +++ b/src/pages/wallet/wallet-history/blocks/ListToolbar.tsx @@ -1,40 +1,189 @@ import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; -import { useManageWalletContext } from '../hooks/useManageWalletHistoryContext'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'sonner'; + +const API_URL_WALLET = apiConfig.service_wallet; + +interface WalletProps { + ID: string; + name: string; +} const ListToolbar = () => { const { table, reload } = useDataGrid(); - const { handleAddDialog } = useManageWalletContext(); + const { GetData } = useCallApi(); + + const [searchValue, setSearchValue] = useState( + (table.getColumn('msisdn')?.getFilterValue() as string) ?? '' + ); + const [dateRange, setDateRange] = useState({ from: '', to: '' }); + const [walletId, setWalletId] = useState( + (table.getColumn('id_wallet')?.getFilterValue() as string) ?? '' + ); + const [wallets, setWallets] = useState([]); + + const formatDate = (date: Date): string => date.toISOString().split('T')[0]; + + useEffect(() => { + const today = new Date(); + const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + setDateRange({ from: formatDate(firstDayOfMonth), to: formatDate(today) }); + }, []); + + useEffect(() => { + const timer = setTimeout(() => { + table.getColumn('msisdn')?.setFilterValue(searchValue); + table.setPageIndex(0); + }, 200); + + return () => clearTimeout(timer); + }, [searchValue, table]); + + const handleFilterByDate = useCallback(() => { + try { + table.getColumn('CreatedAt')?.setFilterValue(dateRange); + } catch (error) { + toast.error('Error applying date filter'); + console.error('Error applying date filter:', error); + } + }, [dateRange, table]); + + useEffect(() => { + table.getColumn('id_wallet')?.setFilterValue(walletId); + table.setPageIndex(0); + }, [walletId, table]); + + useEffect(() => { + if (dateRange.from && dateRange.to) { + handleFilterByDate(); + } + }, [dateRange, handleFilterByDate]); + + const fetchWallets = async () => { + try { + const response = await GetData(`${API_URL_WALLET}/dashboard/wallet/`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: 'created_at', + order_direction: 'ASC' + }); + setWallets(response?.data.list || []); + } catch (error) { + console.error('Error fetching wallets', error); + } + }; + + useEffect(() => { + fetchWallets(); + }, []); + + const handleClearAllFilters = () => { + setSearchValue(''); + setWalletId(''); + + const today = new Date(); + const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + setDateRange({ + from: formatDate(firstDayOfMonth), + to: formatDate(today) + }); + + table.getAllColumns().forEach((column) => { + if (column.id !== 'CreatedAt') { + column.setFilterValue(undefined); + } + }); + + table.setPageIndex(0); + + table.getColumn('CreatedAt')?.setFilterValue({ + from: formatDate(firstDayOfMonth), + to: formatDate(today) + }); + }; return (
-
- {/*
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}
Type Request DateRequest BodyResponse Date Response Body Response Code Request Endpoint
{log.type}{log.request_date}{log.response_date}{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 ?? '-'}