update detail statement on wallet statement

This commit is contained in:
Raja Oktafrianto
2025-05-22 09:06:49 +07:00
parent 8e726d2966
commit e0c62d05b3
8 changed files with 522 additions and 388 deletions

View File

@ -0,0 +1,38 @@
import { Container, DataGridInner } from '@/components';
import { Breadcrumbs, Link } from '@mui/material';
import { Helmet } from 'react-helmet';
import { ManageWalletContextProvider } from './hooks/ManageWalletStatementContext';
const WalletStatement = () => {
return (
<>
<Helmet>
<title>TPAY | Wallet Statement</title>
</Helmet>
<ManageWalletContextProvider>
<Container>
<h1 className="text-xl font-medium leading-none text-gray-900 mb-5">Wallet Statement</h1>
<Breadcrumbs sx={{ mb: 2 }}>
<Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Wallet</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Wallet Statement</span>
</Link>
</Breadcrumbs>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
</Container>
</ManageWalletContextProvider>
</>
);
};
export default WalletStatement;

View File

@ -0,0 +1,213 @@
import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components';
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;
}
interface GroupProps {
ID: string;
name: string;
}
const getOneMonthsAgo = () => {
const today = new Date();
return new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
};
const formatDate = (date: Date): string => date.toISOString().split('T')[0];
const ListToolbar = () => {
const { table, reload } = useDataGrid();
const { GetData } = useCallApi();
const [searchValue, setSearchValue] = useState<string>(
(table.getColumn('msisdn')?.getFilterValue() as string) ?? ''
);
const [walletId, setWalletId] = useState<string>(
(table.getColumn('id_wallet')?.getFilterValue() as string) ?? ''
);
const [groupId, setGroupId] = useState<string>(
(table.getColumn('id_group')?.getFilterValue() as string) ?? ''
);
const [wallets, setWallets] = useState<WalletProps[]>([]);
const [groups, setGroups] = useState<GroupProps[]>([]);
useEffect(() => {
const timer = setTimeout(() => {
table.getColumn('msisdn')?.setFilterValue(searchValue);
table.setPageIndex(0);
}, 200);
return () => clearTimeout(timer);
}, [searchValue, table]);
useEffect(() => {
table.getColumn('id_wallet')?.setFilterValue(walletId);
table.setPageIndex(0);
}, [walletId, table]);
useEffect(() => {
table.getColumn('id_group')?.setFilterValue(groupId);
table.setPageIndex(0);
}, [groupId, table]);
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);
}
};
const fetchGroups = async () => {
try {
const response = await GetData(`${API_URL_WALLET}/dashboard/group/`, {
limit: 100,
page: 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'ASC'
});
setGroups(response?.data.list || []);
} catch (error) {
console.error('Error fetching groups', error);
}
};
useEffect(() => {
fetchWallets();
fetchGroups();
}, []);
const handleClearAllFilters = () => {
const today = new Date();
const oneMonthAgo = getOneMonthsAgo();
const resetDateRange = {
from: formatDate(oneMonthAgo),
to: formatDate(today)
};
setSearchValue('');
setWalletId('');
setGroupId('');
table.getColumn('msisdn')?.setFilterValue('');
table.getColumn('id_wallet')?.setFilterValue('');
table.getColumn('id_group')?.setFilterValue('');
table.getColumn('CreatedAt')?.setFilterValue(resetDateRange);
setTimeout(() => {
table.setPageIndex(0);
reload();
}, 0);
};
const handleRefresh = () => {
const today = new Date();
const threeMonthsAgo = getOneMonthsAgo();
const resetDateRange = {
from: formatDate(threeMonthsAgo),
to: formatDate(today)
};
setSearchValue('');
setWalletId('');
setGroupId('');
table.setColumnFilters([{ id: 'CreatedAt', value: resetDateRange }]);
table.setPageIndex(0);
reload();
};
return (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex flex-wrap items-end gap-3 w-full">
<label className="input input-sm w-[160px]">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search MSISDN"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
className="overflow-hidden text-ellipsis w-full"
/>
</label>
<div className="w-[160px]">
<Select value={walletId} onValueChange={(value) => setWalletId(value)}>
<SelectTrigger className="h-[32px]">
<SelectValue placeholder="Select Wallet" />
</SelectTrigger>
<SelectContent>
{wallets.map((wallet) => (
<SelectItem key={wallet.ID} value={wallet.ID}>
{wallet.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-[160px]">
<Select value={groupId} onValueChange={(value) => setGroupId(value)}>
<SelectTrigger className="h-[32px]">
<SelectValue placeholder="Select Group" />
</SelectTrigger>
<SelectContent>
{groups.map((group) => (
<SelectItem key={group.ID} value={group.ID}>
{group.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<DefaultTooltip title={'Reset Filter'} placement={'top'}>
<Button
variant="outline"
className="h-8 disabled:bg-gray-400"
onClick={handleClearAllFilters}
>
<KeenIcon icon="arrow-circle-left" />
</Button>
</DefaultTooltip>
</div>
<div className="flex gap-3 items-center">
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={handleRefresh}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -0,0 +1,503 @@
import { useEffect, useState } from 'react';
import {
Dialog,
DialogBody,
DialogContent,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
import { useManageStatementContext } from '../hooks/useManageWalletStatementContext';
import { DefaultTooltip, KeenIcon } from '@/components';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { useCallApi } from '@/hooks';
import { apiConfig } from '@/config/api.config';
const API_URL_WALLET = apiConfig.service_wallet;
const API_URL = apiConfig.service_transaction;
interface TransactionType {
id: string;
name: string;
}
interface WalletTransaction {
ID: string;
id_balance: string;
transaction_code: string;
transaction_type: {
id: string;
name: string;
};
type: string;
amount: number;
pre_amount: number;
post_amount: number;
category: string;
notes: string;
date: string;
msisdn_reff: string;
purpose: string;
}
const ShowDialog = () => {
const { showDetailDialog, setShowDetailDialog, selectedWallet } = useManageStatementContext();
const [isLoading, setIsLoading] = useState(false);
const { GetData } = useCallApi();
const [transactionType, setTransactionType] = useState<TransactionType[]>([]);
const [filteredTransactions, setFilteredTransactions] = useState<WalletTransaction[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [totalPages, setTotalPages] = useState(1);
const itemsPerPage = 10;
const getDefaultDateRange = () => {
const today = new Date();
today.setHours(23, 59, 59, 999);
const sevenDaysAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
sevenDaysAgo.setHours(0, 0, 0, 0);
return {
from: formatDate(sevenDaysAgo, true),
to: formatDate(today, true)
};
};
const formatDate = (date: Date, includeTime = false) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
if (includeTime) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
}
return `${year}-${month}-${day}`;
};
const [dateRange, setDateRange] = useState(getDefaultDateRange());
const [category, setCategory] = useState<TransactionKindValue | null>(null);
const [selectedCategory, setSelectedCategory] = useState<string>('');
const [selectedTransactionType, setSelectedTransactionType] = useState<string>('');
const [searchValue, setSearchValue] = useState('');
const TransactionKind = {
return: 'R',
transfer: 'T',
purchase: 'P',
purchase_loja: 'L',
withdraw: 'W',
topup: 'U',
topup_p24: 'B',
topup_partner: 'N',
reward: 'E',
transfer_agent: 'A',
withdraw_agent: 'M',
topup_agent: 'O',
transfer_p24: 'S',
withdraw_merchant: 'I',
donation: 'D',
fee: 'F',
raversal: 'V',
cashback_cash: 'C',
cashback_point: 'H',
withdraw_admin: 'J'
};
type TransactionKindValue = (typeof TransactionKind)[keyof typeof TransactionKind];
const fetchTransactionType = async () => {
try {
setIsLoading(true);
const response = await GetData(`${API_URL}/transactiontype/list`, {
limit: 100,
page: 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'ASC'
});
setTransactionType(
(response?.data?.list || []).sort((a: any, b: any) => a.name.localeCompare(b.name))
);
} catch (error) {
console.error('Error fetching Transaction type', error);
toast.error('Failed to load transaction types');
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchTransactionType();
}, []);
const fetchFilteredTransactions = async () => {
if (!selectedWallet?.ID) return;
setIsLoading(true);
try {
let fromDateTime = dateRange.from;
if (!fromDateTime.includes('T')) {
const fromDate = new Date(fromDateTime);
fromDateTime = formatDate(fromDate, true);
}
let toDateTime = dateRange.to;
if (!toDateTime.includes('T')) {
const toDate = new Date(toDateTime);
toDate.setHours(23, 59, 59, 999);
toDateTime = formatDate(toDate, true);
}
const response = await GetData(
`${API_URL_WALLET}/dashboard/balance/list-balance-detail/${selectedWallet.ID}`,
{
limit: itemsPerPage,
page: currentPage,
order_field: 'created_at',
order_direction: 'DESC',
start_date: fromDateTime,
end_date: toDateTime,
transaction_type: selectedTransactionType || undefined,
category: selectedCategory || undefined,
search: searchValue || undefined
}
);
console.log(response);
setFilteredTransactions(response?.data?.list || []);
setTotalItems(response?.data?.total || 0);
setTotalPages(Math.ceil((response?.data?.total || 0) / itemsPerPage));
} catch (error) {
console.error('Error fetching filtered transactions:', error);
toast.error('Failed to load filtered transactions');
} finally {
setIsLoading(false);
}
};
useEffect(() => {
if (!showDetailDialog || !selectedWallet?.ID) return;
const timer = setTimeout(() => {
fetchFilteredTransactions();
}, 300);
return () => clearTimeout(timer);
}, [
showDetailDialog,
dateRange.from,
dateRange.to,
selectedTransactionType,
selectedCategory,
searchValue,
selectedWallet?.ID,
currentPage
]);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchValue(value);
};
const handleDateChange = (type: 'from' | 'to', value: string) => {
const date = new Date(value);
if (type === 'from') {
date.setHours(0, 0, 0, 0);
} else {
date.setHours(23, 59, 59, 999);
}
const formattedDate = formatDate(date, true);
console.log(`Setting ${type} date to:`, formattedDate);
setDateRange((prev) => ({ ...prev, [type]: formattedDate }));
};
const handleClearAllFilters = () => {
const defaultDates = getDefaultDateRange();
console.log('Resetting filters to default:', defaultDates);
setDateRange(defaultDates);
setSelectedTransactionType('');
setSearchValue('');
setSelectedCategory('');
setCurrentPage(1);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
useEffect(() => {
if (!showDetailDialog) return;
const checkNewDay = () => {
const now = new Date();
const currentDate = formatDate(now);
const fromDate = new Date(dateRange.from);
if (
currentDate !== formatDate(new Date(dateRange.to)) &&
now.getTime() - fromDate.getTime() > 7 * 24 * 60 * 60 * 1000
) {
setDateRange(getDefaultDateRange());
toast.info('Date range has been updated to the current period');
}
};
checkNewDay();
const interval = setInterval(checkNewDay, 60 * 60 * 1000);
return () => clearInterval(interval);
}, [showDetailDialog, dateRange]);
const getDisplayDate = (dateTimeString: string) => {
if (!dateTimeString) return '';
return dateTimeString.split('T')[0];
};
return (
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
<DialogContent className="max-w-[95vw] sm:max-w-[1200px] p-4 overflow-hidden">
<DialogHeader>
<DialogTitle>Details Wallet Statement</DialogTitle>
</DialogHeader>
<DialogBody>
<div className="py-4 max-h-[90vh] overflow-y-auto overflow-x auto flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex flex-wrap gap-3 w-full">
<label className="input input-sm w-full sm:w-[160px]">
From
<input
type="date"
placeholder="From"
value={getDisplayDate(dateRange.from)}
onChange={(e) => handleDateChange('from', e.target.value)}
/>
</label>
<label className="input input-sm w-full sm:w-[160px]">
To
<input
type="date"
placeholder="To"
value={getDisplayDate(dateRange.to)}
onChange={(e) => handleDateChange('to', e.target.value)}
/>
</label>
<div className="w-full sm:w-[200px]">
<Select
value={selectedTransactionType}
onValueChange={(value) => setSelectedTransactionType(value)}
>
<SelectTrigger className="h-[32px]">
<SelectValue placeholder="Transaction Type" />
</SelectTrigger>
<SelectContent>
{transactionType.map((transaction) => (
<SelectItem key={transaction.id} value={transaction.id}>
{transaction.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-full sm:w-[160px]">
<Select
value={selectedCategory}
onValueChange={(value) => setSelectedCategory(value)}
>
<SelectTrigger className="h-[32px]">
<SelectValue placeholder="Status Kind" />
</SelectTrigger>
<SelectContent>
{Object.entries(TransactionKind).map(([label, value]) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<label className="input input-sm w-full sm:w-[160px]">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Transaction Code"
className="overflow-hidden text-ellipsis w-full"
value={searchValue}
onChange={handleSearchChange}
/>
</label>
<DefaultTooltip title={'Reset Filter'} placement={'top'}>
<Button
variant="outline"
className="h-8 disabled:bg-gray-400"
onClick={handleClearAllFilters}
>
<KeenIcon icon="arrow-circle-left" />
</Button>
</DefaultTooltip>
</div>
{isLoading ? (
<div className="w-full text-center text-gray-500">Loading details...</div>
) : filteredTransactions.length > 0 ? (
<div className="w-[80%] border overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full border-collapse table-auto">
<thead>
<tr className="bg-gray-100 text-left">
<th className="p-3 text-sm min-w-[120px] border border-gray-300">
Transaction Code
</th>
<th className="p-3 text-sm min-w-[120px] border border-gray-300">
MSISDN Reffer
</th>
<th className="p-3 text-sm min-w-[180px] border border-gray-300">
Transaction Type
</th>
<th className="p-3 text-sm min-w-[80px] border border-gray-300">Type</th>
<th className="p-3 text-sm min-w-[100px] border border-gray-300">Amount</th>
<th className="p-3 text-sm min-w-[100px] border border-gray-300">
Pre Amount
</th>
<th className="p-3 text-sm min-w-[100px] border border-gray-300">
Post Amount
</th>
<th className="p-3 text-sm min-w-[80px] border border-gray-300">
Status Kind
</th>
<th className="p-3 text-sm min-w-[150px] border border-gray-300">
Date Time
</th>
<th className="p-3 text-sm min-w-[180px] border border-gray-300">Notes</th>
<th className="p-3 text-sm min-w-[180px] border border-gray-300">
Purpose
</th>
</tr>
</thead>
<tbody>
{filteredTransactions.map((transaction) => (
<tr key={transaction.ID} className="hover:bg-gray-50">
<td className="p-3 text-sm border border-gray-300">
{transaction.transaction_code}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.msisdn_reff}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.transaction_type?.name || 'N/A'}
</td>
<td className="p-3 text-sm border border-gray-300">{transaction.type}</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.amount.toFixed(2)}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.pre_amount.toFixed(2)}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.post_amount.toFixed(2)}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.category}
</td>
<td className="p-3 text-sm border border-gray-300">
{new Date(transaction.date).toLocaleString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.notes || '-'}
</td>
<td className="p-3 text-sm border border-gray-300">
{transaction.purpose || '-'}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
) : (
<div className="text-center text-gray-500 py-10">No transaction data available</div>
)}
<div className="flex justify-between items-center mt-4 flex-wrap gap-2">
<div className="text-sm text-gray-600">
Showing {(currentPage - 1) * itemsPerPage + 1} to{' '}
{Math.min(currentPage * itemsPerPage, totalItems)} of {totalItems} entries
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handlePageChange(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
>
Prev
</Button>
<div className="flex items-center gap-1">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageToShow;
if (totalPages <= 5) {
pageToShow = i + 1;
} else if (currentPage <= 3) {
pageToShow = i + 1;
} else if (currentPage >= totalPages - 2) {
pageToShow = totalPages - 4 + i;
} else {
pageToShow = currentPage - 2 + i;
}
return (
<Button
key={pageToShow}
variant={currentPage === pageToShow ? 'default' : 'outline'}
size="sm"
onClick={() => handlePageChange(pageToShow)}
className="w-8 h-8 p-0"
>
{pageToShow}
</Button>
);
})}
</div>
<Button
variant="outline"
size="sm"
onClick={() => handlePageChange(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
>
Next
</Button>
</div>
</div>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default ShowDialog;

View File

@ -0,0 +1,398 @@
import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components';
import { Toaster } from '@/components/ui/sonner';
import { apiConfig } from '@/config/api.config';
import { useCallApi } from '@/hooks';
import { ColumnDef } from '@tanstack/react-table';
import React, { createContext, useCallback, useMemo, useState } from 'react';
import ListToolbar from '../blocks/ListToolbar';
import ShowDetailDialog from '../blocks/ShowDetailDialog';
const formatNumber = (num: number): string => {
return num.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
};
interface WalletProps {
ID: string;
id: string;
id_wallet: string;
name: string;
id_currency: string;
status: string;
}
interface ContextProps {
wallets: WalletProps[];
showDetailDialog: boolean;
setShowDetailDialog: React.Dispatch<React.SetStateAction<boolean>>;
showAddDialog: boolean;
handleAddDialog: (show: boolean) => void;
showEditDialog: boolean;
handleEditDialog: (show: boolean, selected_wallet: WalletProps | null) => void;
showDeleteDialog: boolean;
handleDeleteDialog: (show: boolean, selected_wallet: WalletProps | null) => void;
selectedWallet: WalletProps | null;
getWalletLists: (
page: number,
limit: number,
sorting: any,
filter: any
) => Promise<{ data: WalletProps[]; totalCount: number } | undefined>;
}
const initialProps: ContextProps = {
wallets: [],
showDetailDialog: false,
setShowDetailDialog: () => {},
showAddDialog: false,
handleAddDialog: (show: boolean) => {},
showEditDialog: false,
handleEditDialog: (show: boolean, selected_wallet: object | null) => {},
showDeleteDialog: false,
handleDeleteDialog: (show: boolean, selected_wallet: object | null) => {},
selectedWallet: null,
getWalletLists: async () => undefined
};
const ManageWalletContext = createContext<ContextProps>(initialProps);
const API_URL_WALLET = apiConfig.service_wallet;
const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }) => {
const [wallets, setWallets] = useState<WalletProps[]>([]);
const [showDetailDialog, setShowDetailDialog] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedWallet, setSelectedWallet] = useState<WalletProps | null>(null);
const { GetData } = useCallApi();
const handleAddDialog = useCallback((show: boolean) => {
setShowAddDialog(show);
}, []);
const handleEditDialog = useCallback((show: boolean, selected_wallet: WalletProps | null) => {
setShowEditDialog(show);
setSelectedWallet(show ? selected_wallet : null);
}, []);
const handleDeleteDialog = useCallback((show: boolean, selected_wallet: WalletProps | null) => {
setShowDeleteDialog(show);
setSelectedWallet(show ? selected_wallet : null);
}, []);
const columns = useMemo<ColumnDef<any>[]>(
() => [
{
accessorKey: 'id_wallet',
header: ({ column }) => <DataGridColumnHeader title="Wallet Name" column={column} />,
cell: ({ row }) => row.original.name || 'Unknown Wallet',
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'msisdn',
header: ({ column }) => <DataGridColumnHeader title="MSISDN" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[300px]',
cellClassName: 'p-[20px]'
}
},
{
accessorKey: 'amount',
header: ({ column }) => <DataGridColumnHeader title="Amount" column={column} />,
cell: ({ row }) => formatNumber(row.original.amount),
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'total_debit',
header: ({ column }) => <DataGridColumnHeader title="Total Debit" column={column} />,
cell: ({ row }) => formatNumber(row.original.total_debit),
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'total_credit',
header: ({ column }) => <DataGridColumnHeader title="Total Credit" column={column} />,
cell: ({ row }) => formatNumber(row.original.total_credit),
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'trx_count_today',
header: ({ column }) => (
<DataGridColumnHeader title="Transaction Count Today" column={column} />
),
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'amount_this_month',
header: ({ column }) => <DataGridColumnHeader title="Amount This Month" column={column} />,
cell: ({ row }) => formatNumber(row.original.amount_this_month),
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'CreatedAt',
header: ({ column }) => <DataGridColumnHeader title="Created At" column={column} />,
cell: ({ row }) =>
new Date(row.original.CreatedAt).toLocaleString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
}),
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'currency_name',
header: ({ column }) => <DataGridColumnHeader title="Currency Name" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'currency_code',
header: ({ column }) => <DataGridColumnHeader title="Currency Code" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'id_group',
header: ({ column }) => <DataGridColumnHeader title="Group Name" column={column} />,
cell: ({ row }) => row.original.group_name || 'Unknown Group',
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorKey: 'detail_statemenet',
header: ({ column }) => <DataGridColumnHeader title="Detail Statement" column={column} />,
enableSorting: false,
enableHiding: false,
cell: (data) => {
const row = data.row.original;
return (
<div key={`detail_statement-${row.id}`} className="flex gap-2 justify-center">
<button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => {
setSelectedWallet({
...row,
ID: row.ID || row.id
});
setShowDetailDialog(true);
}}
>
{' '}
<KeenIcon icon="eye" />
</button>
</div>
);
}
}
// {
// accessorFn: (row) => row.group?.is_bank,
// id: 'is_bank',
// header: ({ column }) => <DataGridColumnHeader title="Is Bank" column={column} />,
// enableSorting: false,
// enableHiding: false,
// meta: {
// headerClassName: 'w-[200px]'
// }
// }
],
[]
);
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 ? 'ASC' : 'DESC') : 'DESC';
let filterParams: any = {};
if (Array.isArray(filter)) {
filter.forEach((f: any) => {
if (f.id === 'msisdn' && f.value) {
filterParams.msisdn = f.value;
}
if (f.id === 'id_wallet' && f.value) {
filterParams.id_wallet = f.value;
}
if (f.id === 'id_group' && f.value) {
filterParams.id_group = 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(filterParams)
});
if (!response || !response.data) {
console.warn('No data received:', response);
return { data: [], totalCount: 0 };
}
const walletsResponse = await GetData(`${API_URL_WALLET}/dashboard/wallet/`, {
limit: 100,
page: 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'ASC'
});
const groupsResponse = await GetData(`${API_URL_WALLET}/dashboard/group/`, {
limit: 100,
page: 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'ASC'
});
const currenciesResponse = await GetData(`${API_URL_WALLET}/dashboard/currency/`, {
limit: 100,
page: 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'ASC'
});
const walletsMap = walletsResponse?.data?.list
? walletsResponse.data.list.reduce((acc: any, wallet: any) => {
acc[wallet.ID] = {
name: wallet.name,
id_currency: wallet.id_currency
};
return acc;
}, {})
: {};
const groupsMap = groupsResponse?.data?.list
? groupsResponse.data.list.reduce((acc: any, group: any) => {
acc[group.ID] = group.name;
return acc;
}, {})
: {};
const currenciesMap = currenciesResponse?.data?.list
? currenciesResponse.data.list.reduce((acc: any, currency: any) => {
acc[currency.ID] = {
name: currency.name,
code: currency.code
};
return acc;
}, {})
: {};
const enrichedData = response?.data.list.map((item: any) => {
const walletInfo = walletsMap[item.id_wallet] || {
name: 'Unknown Wallet',
id_currency: null
};
const currencyInfo = currenciesMap[walletInfo.id_currency] || {
name: 'Unknown Currency',
code: 'USD'
};
return {
...item,
name: walletInfo.name,
group_name: groupsMap[item.id_group] || 'Unknown Group',
currency_name: currencyInfo.name,
currency_code: currencyInfo.code
};
});
setWallets(enrichedData);
return {
data: enrichedData,
totalCount: response?.data.total_count
};
} catch (error) {
console.error('Error fetching Wallet', error);
return { data: [], totalCount: 0 };
}
};
return (
<ManageWalletContext.Provider
value={{
wallets,
showDetailDialog,
setShowDetailDialog,
showAddDialog,
handleAddDialog,
showEditDialog,
handleEditDialog,
showDeleteDialog,
handleDeleteDialog,
selectedWallet,
getWalletLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<ShowDetailDialog />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
toolbar={<ListToolbar />}
layout={{ card: true }}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getWalletLists(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManageWalletContext.Provider>
);
};
export { ManageWalletContext, ManageWalletContextProvider };
export type { WalletProps };

View File

@ -0,0 +1,13 @@
import { useContext } from 'react';
import { ManageWalletContext } from './ManageWalletStatementContext';
const useManageStatementContext = () => {
const context = useContext(ManageWalletContext);
if (!context) {
throw new Error('useManageStatementContext must be used within a ManageStatementContextProvider');
}
return context;
};
export { useManageStatementContext };