diff --git a/src/layouts/demo2/header/HeaderTopbar.tsx b/src/layouts/demo2/header/HeaderTopbar.tsx index 44a6057..5ef3aab 100644 --- a/src/layouts/demo2/header/HeaderTopbar.tsx +++ b/src/layouts/demo2/header/HeaderTopbar.tsx @@ -1,9 +1,10 @@ -import { useRef } from 'react'; +import { useRef, useEffect, useState } from 'react'; import { KeenIcon } from '@/components/keenicons'; import { toAbsoluteUrl } from '@/utils'; import { Menu, MenuItem, MenuToggle } from '@/components'; import { DropdownUser } from '@/partials/dropdowns/user'; import { useLanguage } from '@/i18n'; +import { getAuth } from '@/auth'; const HeaderTopbar = () => { const itemChatRef = useRef(null); @@ -14,38 +15,44 @@ const HeaderTopbar = () => { const handleDropdownChatShow = () => { window.dispatchEvent(new Event('resize')); }; - + console.log(getAuth()) return ( -
- - - - - - {DropdownUser({ menuItemRef: itemUserRef })} - - -
+
+
+ {getAuth()?.user.username} + {getAuth()?.role_name} +
+ + + + + User avatar + + {DropdownUser({ menuItemRef: itemUserRef })} + + +
+ ); }; -export { HeaderTopbar }; +export { HeaderTopbar }; \ No newline at end of file diff --git a/src/pages/dashboards/home/DashboardHomePage.tsx b/src/pages/dashboards/home/DashboardHomePage.tsx index 4884ebb..22d69b9 100644 --- a/src/pages/dashboards/home/DashboardHomePage.tsx +++ b/src/pages/dashboards/home/DashboardHomePage.tsx @@ -16,6 +16,10 @@ import { useFetchYear } from './hooks/useFetchYear'; import { get5LastYear } from '@/utils/Date'; import { staticChartData } from './staticChart'; import { Helmet } from 'react-helmet'; +import BalanceCard from './blocks/BalanceCard'; +import { getAuth } from '@/auth'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; // sum -> nominal, count-> total type CountType = 'sum' | 'count'; @@ -33,6 +37,9 @@ const DashboardHomePage = () => { from: new Date(), to: new Date() }); + const currentRole = getAuth()?.role_name; + const idCustomer = getAuth()?.user.customer?.id; + // console.log(currentRole); // Menyusun tanggal awal dan akhir berdasarkan selectedYear useEffect(() => { @@ -144,6 +151,13 @@ const DashboardHomePage = () => { TPAY | Dashboard + {/* Account Balance Cards */} + {currentRole === 'Escrow' || currentRole === 'Master Agent' ? ( +
+ +
+ ) : null} +
diff --git a/src/pages/dashboards/home/blocks/BalanceCard.tsx b/src/pages/dashboards/home/blocks/BalanceCard.tsx new file mode 100644 index 0000000..e349fbd --- /dev/null +++ b/src/pages/dashboards/home/blocks/BalanceCard.tsx @@ -0,0 +1,100 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { CreditCard, Wallet } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +type WalletResponse = { + amount: number; + id_wallet: string; + wallet: string; + credit_limit: number; + monthly_limit: number; +}; + +interface BalanceCardProps { + id: string; +} + +const API_URL_WALLET = apiConfig.service_wallet; + +const BalanceCard = ({ id }: BalanceCardProps) => { + const { GetData } = useCallApi(); + const [wallets, setWallets] = useState([]); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(value); + }; + + const formatNumberWithDots = (value: number) => { + return value.toLocaleString('id-ID'); + }; + + const getColorClass = (amount: number, walletType: string) => { + if (walletType.toLowerCase().includes('point account')) return 'bg-amber-300'; + // if (amount > 3125) return 'bg-amber-300'; + return 'bg-red-500'; + }; + + useEffect(() => { + async function fetchAccountBalances() { + try { + const response = await GetData(`${API_URL_WALLET}/dashboard/balance/account/${id}`, { + id: id + }); + if (response?.status === true) { + setWallets(response.data); + } + } catch (error) { + console.error('Error fetching account balances', error); + } + } + + fetchAccountBalances(); + }, [id]); + + return ( +
+ {wallets.map((wallet) => { + const colorClass = getColorClass(wallet.amount, wallet.wallet); + const isNegative = wallet.amount < 0; + + return ( +
+
+
+
+

{wallet.wallet}

+ {wallet.wallet.includes('Credit') ? ( + + ) : ( + + )} +
+

+ {formatCurrency(wallet.amount)} +

+ +
+
Credit Limit:
+
{formatNumberWithDots(wallet.credit_limit)}
+
+
+
Monthly Limit:
+
{formatNumberWithDots(wallet.monthly_limit)}
+
+
+
+ ); + })} +
+ ); +}; + +export default BalanceCard; diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 8968cd2..2371696 100644 --- a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx @@ -20,11 +20,13 @@ import { import { doSaveLogActivity } from '@/actions/GlobalActions'; import { toast } from 'sonner'; import { Input } from '@/components/ui/input'; +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; const API_URL = apiConfig.transaction; const ApprovalDialog = () => { const { GetData, PostData } = useCallApi(); + // const { table, reload } = useDataGrid(); const { showApprovalDialog, @@ -83,6 +85,7 @@ const ApprovalDialog = () => { }; doSaveLogActivity(createActivity); setShowApprovalDialog(false); + // reload(); } else { setAlert({ show: true, message: response?.message }); } diff --git a/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx b/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx index 6a8d4db..2bf79e0 100644 --- a/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx @@ -3,18 +3,40 @@ import { useTransactionContext } from '../hooks/useApprovalTransactionContext'; import { Button } from '@/components/ui/button'; import { useCallback, useState, useEffect } from 'react'; import { toast } from 'sonner'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; const ListToolbar = () => { const { table, reload } = useDataGrid(); // Set the initial state for trxDate const [trxDate, settrxDate] = useState({ from: '', to: '' }); + const [statusApproval, setStatusApproval] = useState( + (table.getColumn('status_approve')?.getFilterValue() as string) ?? '' + ); + + useEffect(() => { + const timer = setTimeout(() => { + table.getColumn('status_approve')?.setFilterValue(statusApproval); + table.setPageIndex(0); + }, 200); + return () => clearTimeout(timer); + }, [statusApproval, table]); // Function to format date to YYYY-MM-DD const formatDate = (date: Date): string => { return date.toISOString().split('T')[0]; }; + const [searchValue, setSearchValue] = useState( + (table.getColumn('code')?.getFilterValue() as string) ?? '' + ); + // useEffect to set the default date values useEffect(() => { const today = new Date(); @@ -34,6 +56,15 @@ const ListToolbar = () => { } }, [trxDate, table]); + useEffect(() => { + const timer = setTimeout(() => { + table.getColumn('code')?.setFilterValue(searchValue); + table.setPageIndex(0); + }, 200); + + return () => clearTimeout(timer); + }, [searchValue, table]); + useEffect(() => { if (trxDate.from && trxDate.to) { handleFilterData(); @@ -69,6 +100,33 @@ const ListToolbar = () => { name="to" /> + + + + +
@@ -79,11 +137,20 @@ const ListToolbar = () => { onClick={() => { const today = new Date(); const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + + // Resetting all filters + setSearchValue(''); + setStatusApproval(''); settrxDate({ from: formatDate(firstDayOfMonth), to: formatDate(today), }); - reload(); + + table.getColumn('code')?.setFilterValue(''); + table.getColumn('status_approve')?.setFilterValue(''); + table.setPageIndex(0); + + reload(); // Reload table data }} > @@ -93,7 +160,6 @@ const ListToolbar = () => {
); - }; export default ListToolbar; diff --git a/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx b/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx index 5c74f68..ba57c71 100644 --- a/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx +++ b/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx @@ -63,6 +63,15 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } const columns = useMemo[]>( () => [ + { + accessorKey: 'code', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, { accessorKey: 'transaction_date', header: ({ column }) => , @@ -164,12 +173,12 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } }, { accessorKey: 'status_approve', - header: 'Status', + header: 'Status Approval', cell: ({ row }) => { const statusCode = row.original.status_approve; let label = ''; let badgeClass = ''; - + switch (statusCode) { case 'Y': label = 'APPROVED'; @@ -184,14 +193,14 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } badgeClass = 'bg-gray-100 text-gray-800'; break; } - + return ( {label} ); }, - }, + }, { id: 'actions', header: ({ column }) => , @@ -234,27 +243,51 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } const getTransactionLists = async (page: number, limit: number, sorting: any, filter: any) => { try { - let startdate, enddate; + const today = new Date(); + const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); - if (!filter || filter.length === 0) { - const today = new Date(); - const nextWeek = new Date(); - nextWeek.setDate(today.getDate() + 7); - startdate = today.toISOString().split('T')[0]; - enddate = nextWeek.toISOString().split('T')[0]; - } else { - startdate = filter[0]?.value?.from; - enddate = filter[0]?.value?.to; + // Default values + let startdate = firstDayOfMonth.toISOString().split('T')[0]; + let enddate = today.toISOString().split('T')[0]; + let transactioncode = ''; + let approvalstatus = ''; + + if (Array.isArray(filter)) { + filter.forEach((f: any) => { + // console.log(f.id); + if (f.id === 'transaction_date' && f.value?.from && f.value?.to) { + startdate = f.value.from; + enddate = f.value.to; + } + + if (f.id === 'code' && f.value) { + transactioncode = f.value; + } + + if (f.id === 'status_approve' && f.value) { + approvalstatus = f.value; + } + }); } - const formattedFilter = { + // Constructing filter object with dynamic properties + const formattedFilter: any = { "Transactions.transaction_date": { from: `${startdate} 00:00:00`, to: `${enddate} 23:59:59`, }, - "Transactions.status_approve": { in: ["W", "Y", "N"] }, }; + if (transactioncode) { + formattedFilter["Transactions.code"] = { like: `%${transactioncode}%` }; + } + + // Handle approval status filter + formattedFilter["Transactions.status_approve"] = approvalstatus + ? approvalstatus + : { in: ["W", "Y", "N"] }; + + // Fetch data from API const response = await GetData(`${API_URL}/transaction/history`, { limit, page: page + 1, @@ -264,13 +297,17 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } filter: JSON.stringify(formattedFilter), }); - setTransaction(response?.data?.list || []); - return { - data: response?.data?.list || [], - totalCount: response?.data?.total_count || 0, - }; + if (!response || !response.data) { + console.warn('No data received:', response); + return { data: [], totalCount: 0 }; + } + + setTransaction(response.data.list); // Assuming this is a state setter + return { data: response.data.list, totalCount: response.data.total_count }; + } catch (error) { console.error('Error fetching transaction', error); + return { data: [], totalCount: 0 }; // Fail-safe fallback } }; diff --git a/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx b/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx index aeb3e0d..49bdb4a 100644 --- a/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx +++ b/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx @@ -3,19 +3,62 @@ import { useTransactionContext } from '../hooks/useTransactionContext'; import { Button } from '@/components/ui/button'; import { useCallback, useState, useEffect } from 'react'; import { toast } from 'sonner'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; const ListToolbar = () => { const { table, reload } = useDataGrid(); - // Set the initial state for trxDate const [trxDate, settrxDate] = useState({ from: '', to: '' }); + const [searchValue, setSearchValue] = useState( + (table.getColumn('code')?.getFilterValue() as string) ?? '' + ); + + // const [statusValue, setStatusValue] = useState( + // (table.getColumn('status')?.getFilterValue() as string) ?? '' + // ); + + const [typeValue, setTypeValue] = useState( + (table.getState().columnFilters.find(f => f.id === 'kind')?.value as string) ?? '' + ); - // Function to format date to YYYY-MM-DD const formatDate = (date: Date): string => { return date.toISOString().split('T')[0]; }; - // useEffect to set the default date values + useEffect(() => { + const timer = setTimeout(() => { + table.getColumn('code')?.setFilterValue(searchValue); + table.setPageIndex(0); + }, 200); + + return () => clearTimeout(timer); + }, [searchValue, table]); + + // useEffect(() => { + // const timer = setTimeout(() => { + // table.getColumn('status')?.setFilterValue(statusValue); + // table.setPageIndex(0); + // }, 200); + // return () => clearTimeout(timer); + // }, [statusValue, table]); + + useEffect(() => { + const timer = setTimeout(() => { + table.setColumnFilters((prev) => [ + ...prev.filter((f) => f.id !== 'kind'), + { id: 'kind', value: typeValue }, + ]); + table.setPageIndex(0); + }, 200); + return () => clearTimeout(timer); + }, [typeValue, table]); + useEffect(() => { const today = new Date(); const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); @@ -69,6 +112,50 @@ const ListToolbar = () => { name="to" /> + + {/* */} + + + +
@@ -77,12 +164,27 @@ const ListToolbar = () => { variant="outline" className="h-7.5" onClick={() => { + // Preserve trxDate values (from, to) and reset others const today = new Date(); const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + + // Only reset filters excluding from and to + setSearchValue(''); + // setStatusValue(''); + setTypeValue(''); settrxDate({ from: formatDate(firstDayOfMonth), to: formatDate(today), }); + + // Reset other filters except for date range + table.setColumnFilters([ + { id: 'code', value: '' }, + // { id: 'status', value: '' }, + { id: 'kind', value: '' }, + ]); + + table.setPageIndex(0); reload(); }} > diff --git a/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx b/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx index b9eeb9e..8d7bbdc 100644 --- a/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx @@ -70,15 +70,34 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const columns = useMemo[]>( () => [ - // { - // accessorKey: 'transaction_date', - // header: ({ column }) => , - // enableSorting: false, - // enableHiding: false, - // meta: { - // headerClassName: 'w-[250px]' - // } - // }, + { + accessorKey: 'code', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => { + switch (row.kind) { + case 'T': return 'TRANSFER'; + case 'P': return 'PURCHASE'; + case 'W': return 'WITHDRAW'; + case 'U': return 'TOP UP'; + case 'R': return 'RETURN'; + default: return '_'; + } + }, + accessorKey: 'kind', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, { accessorKey: 'transaction_date', header: ({ column }) => , @@ -242,30 +261,60 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const getTransactionLists = async (page: number, limit: number, sorting: any, filter: any) => { try { - let startdate; - let enddate; - let formattedFilter; + const today = new Date(); + const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); - if (filter == undefined || filter.length == 0) { - const today = new Date(); + // Default values + let startdate = firstDayOfMonth.toISOString().split('T')[0]; + let enddate = today.toISOString().split('T')[0]; + let transactioncode = ''; + // let transactionstatus = ''; + let type = '' - // Tanggal 1 di bulan sekarang - const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + if (Array.isArray(filter)) { + // console.log(filter); + filter.forEach((f: any) => { + if (f.id === 'transaction_date' && f.value?.from && f.value?.to) { + startdate = f.value.from; + enddate = f.value.to; + } - startdate = firstDayOfMonth.toISOString().split('T')[0]; - enddate = today.toISOString().split('T')[0]; - } else if (filter != undefined || filter.length != 0) { - startdate = filter[0].value.from; - enddate = filter[0].value.to; + if (f.id === 'code' && f.value) { + transactioncode = f.value; + } + + // if (f.id === 'status' && f.value) { + // transactionstatus = f.value; + // } + + if (f.id === 'kind' && f.value) { + type = f.value; + } + }); } - formattedFilter = { + // Build filter object dynamically + const formattedFilter: any = { "Transactions.transaction_date": { - from: startdate + " 00:00:00", - to: enddate + " 23:59:59" + from: `${startdate} 00:00:00`, + to: `${enddate} 23:59:59` } }; + if (transactioncode) { + formattedFilter["Transactions.code"] = { + like: `%${transactioncode}%` + }; + } + + // if (transactionstatus) { + // formattedFilter["Transactions.status"] = transactionstatus; + // } + + if (type) { + formattedFilter["Transactions.kind"] = type; + } + const response = await GetData(`${API_URL}/transaction/history`, { limit, page: page + 1, @@ -275,13 +324,21 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { filter: JSON.stringify(formattedFilter) }); - setTransaction(response?.data.list); - return { data: response?.data.list, totalCount: response?.data.total_count }; + if (!response || !response.data) { + console.warn('No data received:', response); + return { data: [], totalCount: 0 }; + } + + setTransaction(response.data.list); + return { data: response.data.list, totalCount: response.data.total_count }; + } catch (error) { console.error('Error fetching transaction', error); + return { data: [], totalCount: 0 }; } }; + return ( { Top Up Master Agent Top Up Agent Purchase Loja - + Disbursment Escrow + Disbursment Master Agent + Disbursment Agent
diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index 28e2296..a6070bf 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -605,6 +605,9 @@ const EditDialog = () => { Top Up Master Agent Top Up Agent Purchase Loja + Disbursment Escrow + Disbursment Master Agent + Disbursment Agent diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index a21febf..cae8c34 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -80,7 +80,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React const [selectedTransferType, setSelectedTransferType] = useState(null); const [transferType, setTransferType] = useState(null); const [searchTerm, setSearchTerm] = useState(''); - + const debouncedSearchTerm = useDebounce(searchTerm, 200); const handleEditDialog = useCallback((show: boolean, selected_transfertype: string | null) => { @@ -183,10 +183,13 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React AM: 'Return Agent Merchant', AE: 'Return Agent Emoney', R: 'Reward Point', - TE:'Top Up Escrow', - TM:'Top Up Master Agent', - TA:'Top Up Agent', - PL:'Purchase Loja' + TE: 'Top Up Escrow', + TM: 'Top Up Master Agent', + TA: 'Top Up Agent', + PL: 'Purchase Loja', + DE: 'Disbursment Escrow', + DM: 'Disbursment Master Agent', + DA: 'Disbursment Agent' }; return mapping[row.type] || 'Unknown'; @@ -256,7 +259,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React const orderDirection = sorting.length > 0 ? (sorting[0].desc ? 'DESC' : 'ASC') : 'DESC'; const searchFilter = debouncedSearchTerm ? { any: debouncedSearchTerm.toLowerCase() } : {}; - + filter = filter.length == 0 ? searchFilter : { any: filter[0].value?.toLowerCase() }; const response = await GetData(`${API_URL}/transactiontype/list`, { @@ -310,4 +313,4 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React }; export { ManageTransferTypeContext, ManageTransferTypeContextProvider }; -export type { TransferType }; \ No newline at end of file +export type { TransferType };