This commit is contained in:
wayanrivan
2025-05-21 09:51:11 +07:00
8 changed files with 577 additions and 90 deletions

View File

@ -5,22 +5,23 @@ import { useCallback, useEffect, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { DateRangePicker } from '@/pages/dashboards/home/blocks'; import { DateRangePicker } from '@/pages/dashboards/home/blocks';
const formatDate = (date: Date): string => date.toISOString().split('T')[0]; const formatDate = (date: Date): string => date.toLocaleDateString('sv-SE');
const formatDateTime = (date: Date): string => date.toISOString();
const getLocalDateString = (date: Date): string => {
return date.toLocaleDateString('sv-SE');
};
const ListToolBar = () => { const ListToolBar = () => {
const { table, reload } = useDataGrid(); const { table, reload } = useDataGrid();
const { handleAddDialog } = useManageNotificationContext(); const { handleAddDialog } = useManageNotificationContext();
const [dateRange, setDateRange] = useState({ from: '', to: '' }); const [dateRange, setDateRange] = useState({
from: getLocalDateString(new Date()),
to: getLocalDateString(new Date())
});
const [searchValue, setSearchValue] = useState<string>( const [searchValue, setSearchValue] = useState<string>(
(table.getColumn('content')?.getFilterValue() as string) ?? '' (table.getColumn('content')?.getFilterValue() as string) ?? ''
); );
useEffect(() => {
const today = new Date();
const formatted = formatDate(today);
setDateRange({ from: formatted, to: formatted });
}, []);
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
table.getColumn('content')?.setFilterValue(searchValue); table.getColumn('content')?.setFilterValue(searchValue);
@ -31,13 +32,25 @@ const ListToolBar = () => {
useEffect(() => { useEffect(() => {
const today = new Date(); const today = new Date();
const formatted = formatDate(today); // Set waktu ke awal hari (00:00:00)
const initialDateRange = { from: formatted, to: formatted }; const startOfDay = new Date(today);
startOfDay.setHours(0, 0, 0, 0);
// Set waktu ke akhir hari (23:59:59)
const endOfDay = new Date(today);
endOfDay.setHours(23, 59, 59, 999);
const initialDateRange = {
from: formatDate(startOfDay), // << Gunakan formatDate di sini
to: formatDate(endOfDay)
};
setDateRange(initialDateRange); setDateRange(initialDateRange);
try { try {
table.getColumn('created_at')?.setFilterValue(initialDateRange); table.getColumn('created_at')?.setFilterValue({
from: formatDateTime(startOfDay),
to: formatDateTime(endOfDay)
});
} catch (error) { } catch (error) {
toast.error('Error applying initial date filter'); toast.error('Error applying initial date filter');
console.error('Initial date filter error:', error); console.error('Initial date filter error:', error);
@ -46,16 +59,28 @@ const ListToolBar = () => {
const handleClearAllFilters = () => { const handleClearAllFilters = () => {
const today = new Date(); const today = new Date();
const startOfDay = new Date(today);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(today);
endOfDay.setHours(23, 59, 59, 999);
// Set ulang filter tanggal dengan waktu
const resetDateRange = { const resetDateRange = {
from: formatDate(today), from: formatDate(startOfDay), // YYYY-MM-DD untuk input date
to: formatDate(today) to: formatDate(endOfDay)
}; };
setSearchValue(''); setSearchValue('');
setDateRange(resetDateRange); setDateRange(resetDateRange);
table.getColumn('content')?.setFilterValue(''); table.getColumn('content')?.setFilterValue('');
table.getColumn('created_at')?.setFilterValue(resetDateRange);
table.getColumn('created_at')?.setFilterValue({
from: formatDateTime(startOfDay),
to: formatDateTime(endOfDay)
});
setTimeout(() => { setTimeout(() => {
table.setPageIndex(0); table.setPageIndex(0);
@ -75,9 +100,24 @@ const ListToolBar = () => {
useEffect(() => { useEffect(() => {
const checkNewDay = () => { const checkNewDay = () => {
const now = new Date(); const now = new Date();
const formattedNow = formatDate(now); const currentDate = formatDate(now); // Tetap gunakan formatDate untuk perbandingan tanggal saja
if (formattedNow !== dateRange.from || formattedNow !== dateRange.to) {
setDateRange({ from: formattedNow, to: formattedNow }); // Periksa apakah tanggal sekarang berbeda dengan tanggal yang difilter
if (currentDate !== formatDate(new Date(dateRange.from))) {
const startOfDay = new Date(now);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(now);
endOfDay.setHours(23, 59, 59, 999);
setDateRange({
from: formatDate(startOfDay), // hanya YYYY-MM-DD
to: formatDate(endOfDay)
});
table.getColumn('created_at')?.setFilterValue({
from: formatDateTime(startOfDay),
to: formatDateTime(endOfDay)
});
} }
}; };
@ -95,7 +135,16 @@ const ListToolBar = () => {
type="date" type="date"
placeholder="From" placeholder="From"
value={dateRange.from} value={dateRange.from}
onChange={(event) => setDateRange({ ...dateRange, from: event.target.value })} onChange={(e) => {
setDateRange({ ...dateRange, from: e.target.value });
// Untuk filter, konversi ke Date object dengan waktu awal hari
const date = new Date(e.target.value);
date.setHours(0, 0, 0, 0);
table.getColumn('created_at')?.setFilterValue({
from: date.toISOString(),
to: new Date(dateRange.to).toISOString()
});
}}
name="from" name="from"
/> />
</label> </label>
@ -106,7 +155,16 @@ const ListToolBar = () => {
type="date" type="date"
placeholder="To" placeholder="To"
value={dateRange.to} value={dateRange.to}
onChange={(event) => setDateRange({ ...dateRange, to: event.target.value })} onChange={(e) => {
setDateRange({ ...dateRange, to: e.target.value });
// Untuk filter, konversi ke Date object dengan waktu akhir hari
const date = new Date(e.target.value);
date.setHours(23, 59, 59, 999);
table.getColumn('created_at')?.setFilterValue({
from: new Date(dateRange.from).toISOString(),
to: date.toISOString()
});
}}
name="to" name="to"
/> />
</label> </label>

View File

@ -18,6 +18,7 @@ import { useCallApi } from '@/hooks';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { doSaveLogActivity } from '@/actions/GlobalActions'; import { doSaveLogActivity } from '@/actions/GlobalActions';
import { set } from 'date-fns'; import { set } from 'date-fns';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
const API_URL = apiConfig.service_dashboard; const API_URL = apiConfig.service_dashboard;
@ -61,7 +62,8 @@ const MenuItemComponent: React.FC<{
}; };
const initialState = { const initialState = {
name: '' name: '',
status_balance: '',
}; };
const AddDialog = () => { const AddDialog = () => {
@ -86,13 +88,15 @@ const AddDialog = () => {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => { const resetForm = () => {
setFormField(() => ({ name: '' })); setFormField(initialState);
setErrors(() => ({})); setErrors(() => ({}));
setSelectMenus([]); setSelectMenus([]);
}; };
const validateForm = () => { const validateForm = () => {
const requiredFields = [{ key: 'name', label: 'Position Name' }]; const requiredFields = [
{ key: 'name', label: 'Position Name' },
{ key: 'status_balance', label: 'Status Balance' }];
const newErrors: Record<string, string> = {}; const newErrors: Record<string, string> = {};
let isValid = true; let isValid = true;
requiredFields.forEach(({ key, label }) => { requiredFields.forEach(({ key, label }) => {
@ -128,7 +132,8 @@ const AddDialog = () => {
const response = await PostData(`${API_URL}/user_role/create`, { const response = await PostData(`${API_URL}/user_role/create`, {
name: formField.name, name: formField.name,
roles: selectMenus, roles: selectMenus,
status: 'Y' status: 'Y',
status_balance: formField.status_balance
}); });
if (response?.status) { if (response?.status) {
@ -203,6 +208,33 @@ const AddDialog = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Status Balance <span className="text-danger">*</span>
</label>
<div className="grow flex flex-col">
<Select
value={formField.status_balance}
onValueChange={(value) => {
setFormField((prev) => ({ ...prev, status_balance: value }));
setErrors((prev) => ({ ...prev, status_balance: '' }));
}}
>
<SelectTrigger className={errors.status ? 'border-red-500' : ''}>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Inactive</SelectItem>
</SelectContent>
</Select>
{errors.status && (
<span className="text-red-500 text-xs mt-1">{errors.status}</span>
)}
</div>
</div>
</div>
<div className="grid md:grid-cols-3 w-full gap-5"> <div className="grid md:grid-cols-3 w-full gap-5">
{menus.map((menu) => ( {menus.map((menu) => (

View File

@ -79,11 +79,15 @@ const EditDialog = () => {
const [selectMenus, setSelectMenus] = useState<string[]>([]); const [selectMenus, setSelectMenus] = useState<string[]>([]);
const [formField, setFormField] = useState({ const [formField, setFormField] = useState({
name: '', name: '',
status: '' status: '',
status_balance: ''
}); });
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
const validateForm = () => { const validateForm = () => {
const requiredFields = [{ key: 'name', label: 'Position Name' }]; const requiredFields = [
{ key: 'name', label: 'Position Name' },
{ key: 'status_balance', label: 'Status Balance' }
];
const newErrors: Record<string, string> = {}; const newErrors: Record<string, string> = {};
let isValid = true; let isValid = true;
requiredFields.forEach(({ key, label }) => { requiredFields.forEach(({ key, label }) => {
@ -131,7 +135,8 @@ const EditDialog = () => {
const response = await PutData(`${API_URL}/user_role/update/${selectedPosition.id}`, { const response = await PutData(`${API_URL}/user_role/update/${selectedPosition.id}`, {
name: formField.name, name: formField.name,
roles: selectMenus, roles: selectMenus,
status: formField.status status: formField.status,
status_balance: formField.status_balance
}); });
if (response?.status) { if (response?.status) {
@ -163,7 +168,8 @@ const EditDialog = () => {
setFormField((prev) => ({ setFormField((prev) => ({
...prev, ...prev,
name: selectedPosition.name, name: selectedPosition.name,
status: selectedPosition.status status: selectedPosition.status,
status_balance: selectedPosition.status_balance
})); }));
setSelectMenus(selectedPosition.roles); setSelectMenus(selectedPosition.roles);
@ -236,6 +242,26 @@ const EditDialog = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="w-full">
<div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Status Balance</label>
<div className="grow">
<Select
value={formField.status_balance}
onValueChange={(status_balance) => setFormField((prev) => ({ ...prev, status_balance }))}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Non Active</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="grid md:grid-cols-3 w-full gap-5"> <div className="grid md:grid-cols-3 w-full gap-5">
{menus.map((menu) => ( {menus.map((menu) => (

View File

@ -23,6 +23,7 @@ interface selectedPosition {
name: string; name: string;
roles: string[]; roles: string[];
status: string; status: string;
status_balance: string;
} }
const initialProps: ContextProps = { const initialProps: ContextProps = {

View File

@ -7,11 +7,11 @@ const WalletHistory = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>TPAY | Wallet History</title> <title>TPAY | Wallet Statement</title>
</Helmet> </Helmet>
<ManageWalletContextProvider> <ManageWalletContextProvider>
<Container> <Container>
<h1 className="text-xl font-medium leading-none text-gray-900 mb-5">Wallet History</h1> <h1 className="text-xl font-medium leading-none text-gray-900 mb-5">Wallet Statement</h1>
<Breadcrumbs sx={{ mb: 2 }}> <Breadcrumbs sx={{ mb: 2 }}>
<Link underline="none" color="inherit" href="/"> <Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span> <span className="text-sm hover:underline">Dashboard</span>
@ -22,7 +22,7 @@ const WalletHistory = () => {
</Link> </Link>
<Link underline="none" color="inherit"> <Link underline="none" color="inherit">
<span className="text-sm">Wallet History</span> <span className="text-sm">Wallet Statement</span>
</Link> </Link>
</Breadcrumbs> </Breadcrumbs>

View File

@ -38,7 +38,6 @@ const ListToolbar = () => {
const [searchValue, setSearchValue] = useState<string>( const [searchValue, setSearchValue] = useState<string>(
(table.getColumn('msisdn')?.getFilterValue() as string) ?? '' (table.getColumn('msisdn')?.getFilterValue() as string) ?? ''
); );
const [dateRange, setDateRange] = useState({ from: '', to: '' });
const [walletId, setWalletId] = useState<string>( const [walletId, setWalletId] = useState<string>(
(table.getColumn('id_wallet')?.getFilterValue() as string) ?? '' (table.getColumn('id_wallet')?.getFilterValue() as string) ?? ''
); );
@ -48,12 +47,6 @@ const ListToolbar = () => {
const [wallets, setWallets] = useState<WalletProps[]>([]); const [wallets, setWallets] = useState<WalletProps[]>([]);
const [groups, setGroups] = useState<GroupProps[]>([]); const [groups, setGroups] = useState<GroupProps[]>([]);
useEffect(() => {
const today = new Date();
const threeMonthsAgo = getOneMonthsAgo();
setDateRange({ from: formatDate(threeMonthsAgo), to: formatDate(today) });
}, []);
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
table.getColumn('msisdn')?.setFilterValue(searchValue); table.getColumn('msisdn')?.setFilterValue(searchValue);
@ -62,15 +55,6 @@ const ListToolbar = () => {
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [searchValue, table]); }, [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(() => { useEffect(() => {
table.getColumn('id_wallet')?.setFilterValue(walletId); table.getColumn('id_wallet')?.setFilterValue(walletId);
table.setPageIndex(0); table.setPageIndex(0);
@ -81,12 +65,6 @@ const ListToolbar = () => {
table.setPageIndex(0); table.setPageIndex(0);
}, [groupId, table]); }, [groupId, table]);
useEffect(() => {
if (dateRange.from && dateRange.to) {
handleFilterByDate();
}
}, [dateRange, handleFilterByDate]);
const fetchWallets = async () => { const fetchWallets = async () => {
try { try {
const response = await GetData(`${API_URL_WALLET}/dashboard/wallet/`, { const response = await GetData(`${API_URL_WALLET}/dashboard/wallet/`, {
@ -129,23 +107,22 @@ const ListToolbar = () => {
from: formatDate(oneMonthAgo), from: formatDate(oneMonthAgo),
to: formatDate(today) to: formatDate(today)
}; };
setSearchValue(''); setSearchValue('');
setWalletId(''); setWalletId('');
setGroupId(''); setGroupId('');
setDateRange(resetDateRange);
table.getColumn('msisdn')?.setFilterValue(''); table.getColumn('msisdn')?.setFilterValue('');
table.getColumn('id_wallet')?.setFilterValue(''); table.getColumn('id_wallet')?.setFilterValue('');
table.getColumn('id_group')?.setFilterValue(''); table.getColumn('id_group')?.setFilterValue('');
table.getColumn('CreatedAt')?.setFilterValue(resetDateRange); table.getColumn('CreatedAt')?.setFilterValue(resetDateRange);
setTimeout(() => { setTimeout(() => {
table.setPageIndex(0); table.setPageIndex(0);
reload(); reload();
}, 0); }, 0);
}; };
const handleRefresh = () => { const handleRefresh = () => {
const today = new Date(); const today = new Date();
const threeMonthsAgo = getOneMonthsAgo(); const threeMonthsAgo = getOneMonthsAgo();
@ -157,7 +134,6 @@ const ListToolbar = () => {
setSearchValue(''); setSearchValue('');
setWalletId(''); setWalletId('');
setGroupId(''); setGroupId('');
setDateRange(resetDateRange);
table.setColumnFilters([{ id: 'CreatedAt', value: resetDateRange }]); table.setColumnFilters([{ id: 'CreatedAt', value: resetDateRange }]);
table.setPageIndex(0); table.setPageIndex(0);
@ -169,24 +145,6 @@ const ListToolbar = () => {
<div className="flex flex-wrap gap-2 lg:gap-5 w-full"> <div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center"> <div className="flex justify-between w-full items-center">
<div className="flex flex-wrap items-end gap-3 w-full"> <div className="flex flex-wrap items-end gap-3 w-full">
<label className="input input-sm w-[160px]">
From
<input
type="date"
value={dateRange.from}
onChange={(e) => setDateRange({ ...dateRange, from: e.target.value })}
/>
</label>
<label className="input input-sm w-[160px]">
To
<input
type="date"
value={dateRange.to}
onChange={(e) => setDateRange({ ...dateRange, to: e.target.value })}
/>
</label>
<label className="input input-sm w-[160px]"> <label className="input input-sm w-[160px]">
<KeenIcon icon="magnifier" /> <KeenIcon icon="magnifier" />
<input <input
@ -252,4 +210,4 @@ const ListToolbar = () => {
); );
}; };
export default ListToolbar; export default ListToolbar;

View File

@ -0,0 +1,363 @@
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 { useManageWalletContext } from '../hooks/useManageWalletHistoryContext';
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 formatDate = (date: Date) => date.toLocaleDateString('sv-SE');
const API_URL_WALLET = apiConfig.service_wallet;
const API_URL = apiConfig.service_transaction;
interface TransferType {
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;
}
const ShowDialog = () => {
const { showDetailDialog, setShowDetailDialog, selectedWallet } = useManageWalletContext();
const [isLoading, setIsLoading] = useState(false);
const { GetData } = useCallApi();
const [transferType, setTransferType] = useState<TransferType[]>([]);
const [transactions, setTransactions] = useState<WalletTransaction[]>([]);
const [filteredTransactions, setFilteredTransactions] = useState<WalletTransaction[]>([]);
const [category, setCategory] = useState<string[]>([]);
const getDefaultDateRange = () => {
const today = new Date();
const sevenDaysAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
return {
from: formatDate(sevenDaysAgo),
to: formatDate(today)
};
};
useEffect(() => {
const uniqueCategories = Array.from(
new Set(transactions.map((transaction) => transaction.category))
);
setCategory(uniqueCategories);
}, [transactions]);
const [dateRange, setDateRange] = useState(getDefaultDateRange());
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [selectedTransferType, setSelectedTransferType] = useState<string | null>(null);
const [searchValue, setSearchValue] = useState('');
const fetchTransferType = async () => {
try {
const response = await GetData(`${API_URL}/transactiontype/list`, {
limit: 100,
page: 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'ASC'
});
setTransferType(response?.data?.list || []);
} catch (error) {
console.error('Error fetching transfer type', error);
}
};
useEffect(() => {
fetchTransferType();
}, []);
useEffect(() => {
const fetchWalletTransactions = async () => {
if (!selectedWallet?.ID || !showDetailDialog) return;
setIsLoading(true);
try {
const response = await GetData(
`${API_URL_WALLET}/dashboard/balance/list-balance-detail/${selectedWallet.ID}`,
{
limit: 100,
page: 1,
order_field: 'created_at',
order_direction: 'DESC'
}
);
const transactionData =
response?.data?.list || (Array.isArray(response?.data) ? response.data : []);
// console.log('Loaded Transactions:', transactionData);
setTransactions(transactionData);
setFilteredTransactions(transactionData);
} catch (error) {
console.error('Error fetching balance:', error);
} finally {
setIsLoading(false);
}
};
fetchWalletTransactions();
}, [showDetailDialog, selectedWallet, GetData]);
useEffect(() => {
if (transactions.length === 0) return;
setIsLoading(true);
const timer = setTimeout(() => {
// console.log('Debugging Filter - Selected Transfer Type:', selectedTransferType);
const filtered = transactions.filter((transaction) => {
// console.log('Transaction Type:', {
// id: transaction.transaction_type?.id,
// expected: selectedTransferType,
// match: transaction.transaction_type?.id === selectedTransferType
// });
const transactionDate = new Date(transaction.date);
const fromDate = new Date(dateRange.from);
const toDate = new Date(dateRange.to);
fromDate.setHours(0, 0, 0, 0);
toDate.setHours(23, 59, 59, 999);
const dateMatch = transactionDate >= fromDate && transactionDate <= toDate;
const typeMatch = selectedTransferType
? transaction.transaction_type?.id === selectedTransferType
: true;
const codeMatch = searchValue
? transaction.transaction_code.toLowerCase().includes(searchValue.toLowerCase())
: true;
const categoryMatch = selectedCategory ? transaction.category === selectedCategory : true;
return dateMatch && typeMatch && codeMatch && categoryMatch;
});
// console.log('Filtered Results Count:', filtered.length);
setFilteredTransactions(filtered);
setIsLoading(false);
}, 300);
return () => clearTimeout(timer);
}, [dateRange, transactions, selectedTransferType, searchValue, selectedCategory]);
const handleCategoryChange = (value: string) => {
setSelectedCategory(value);
};
const handleTransferTypeChange = (value: string) => {
// console.log('Transfer Type Changed:', value);
setSelectedTransferType(value === 'all' ? null : value);
};
const handleClearAllFilters = () => {
setDateRange(getDefaultDateRange());
setSelectedTransferType(null);
setSearchValue('');
setSelectedCategory(null);
};
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]);
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="flex flex-wrap gap-2 lg:gap-5 w-full mb-4">
<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={dateRange.from}
onChange={(e) => setDateRange({ ...dateRange, from: e.target.value })}
/>
</label>
<label className="input input-sm w-full sm:w-[160px]">
To
<input
type="date"
placeholder="To"
value={dateRange.to}
onChange={(e) => setDateRange({ ...dateRange, to: e.target.value })}
/>
</label>
<div className="w-full sm:w-[160px]">
<Select
value={selectedTransferType || 'all'}
onValueChange={handleTransferTypeChange}
>
<SelectTrigger className="h-[32px]">
<SelectValue placeholder="Transaction Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Select Transaction</SelectItem>
{transferType.map((transfer) => (
<SelectItem
key={transfer.id}
value={transfer.id} // Pastikan tidak ada .toString() di sini
>
{transfer.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-full sm:w-[160px]">
<Select value={selectedCategory ?? ''} onValueChange={handleCategoryChange}>
<SelectTrigger className="h-[32px]">
<SelectValue placeholder="Select Category" />
</SelectTrigger>
<SelectContent>
{category.length === 0 ? (
<div className="text-gray-500 px-4 py-2">No data</div>
) : (
category.map((cat) => (
<SelectItem key={cat} value={cat}>
{cat}
</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={(e) => setSearchValue(e.target.value)}
/>
</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>
</div>
<div className="py-4 max-h-[70vh] overflow-y-auto">
{isLoading ? (
<div className="text-center text-gray-500">Loading details...</div>
) : filteredTransactions.length > 0 ? (
<div className="w-full">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-gray-100 text-left">
<th className="p-3 text-sm min-w-[120px]">Transaction Code</th>
<th className="p-3 text-sm min-w-[180px]">Transaction Type</th>
<th className="p-3 text-sm min-w-[80px]">Type</th>
<th className="p-3 text-sm min-w-[100px]">Amount</th>
<th className="p-3 text-sm min-w-[100px]">Pre Amount</th>
<th className="p-3 text-sm min-w-[100px]">Post Amount</th>
<th className="p-3 text-sm min-w-[80px]">Category</th>
<th className="p-3 text-sm min-w-[150px]">Date</th>
<th className="p-3 text-sm min-w-[200px]">Notes</th>
</tr>
</thead>
<tbody>
{filteredTransactions.map((transaction) => (
<tr key={transaction.ID}>
<td className="p-3 text-sm">{transaction.transaction_code}</td>
<td className="p-3 text-sm">
{transaction.transaction_type?.name || 'N/A'}
</td>
<td className="p-3 text-sm">{transaction.type}</td>
<td className="p-3 text-sm">{transaction.amount.toFixed(2)}</td>
<td className="p-3 text-sm">{transaction.pre_amount.toFixed(2)}</td>
<td className="p-3 text-sm">{transaction.post_amount.toFixed(2)}</td>
<td className="p-3 text-sm">{transaction.category}</td>
<td className="p-3 text-sm">
{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">{transaction.notes || '-'}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
) : (
<div className="text-center text-gray-500 py-10">No transaction data available</div>
)}
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default ShowDialog;

View File

@ -5,6 +5,7 @@ import { useCallApi } from '@/hooks';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
import React, { createContext, useCallback, useMemo, useState } from 'react'; import React, { createContext, useCallback, useMemo, useState } from 'react';
import ListToolbar from '../blocks/ListToolbar'; import ListToolbar from '../blocks/ListToolbar';
import ShowDetailDialog from '../blocks/ShowDetailDialog';
const formatNumber = (num: number): string => { const formatNumber = (num: number): string => {
return num.toLocaleString('en-US', { return num.toLocaleString('en-US', {
@ -14,7 +15,9 @@ const formatNumber = (num: number): string => {
}; };
interface WalletProps { interface WalletProps {
ID: string;
id: string; id: string;
id_wallet: string;
name: string; name: string;
id_currency: string; id_currency: string;
status: string; status: string;
@ -22,6 +25,8 @@ interface WalletProps {
interface ContextProps { interface ContextProps {
wallets: WalletProps[]; wallets: WalletProps[];
showDetailDialog: boolean;
setShowDetailDialog: React.Dispatch<React.SetStateAction<boolean>>;
showAddDialog: boolean; showAddDialog: boolean;
handleAddDialog: (show: boolean) => void; handleAddDialog: (show: boolean) => void;
showEditDialog: boolean; showEditDialog: boolean;
@ -39,6 +44,8 @@ interface ContextProps {
const initialProps: ContextProps = { const initialProps: ContextProps = {
wallets: [], wallets: [],
showDetailDialog: false,
setShowDetailDialog: () => {},
showAddDialog: false, showAddDialog: false,
handleAddDialog: (show: boolean) => {}, handleAddDialog: (show: boolean) => {},
showEditDialog: false, showEditDialog: false,
@ -54,6 +61,7 @@ const API_URL_WALLET = apiConfig.service_wallet;
const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }) => { const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }) => {
const [wallets, setWallets] = useState<WalletProps[]>([]); const [wallets, setWallets] = useState<WalletProps[]>([]);
const [showDetailDialog, setShowDetailDialog] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false); const [showAddDialog, setShowAddDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false);
@ -92,7 +100,7 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
meta: { meta: {
headerClassName: 'w-[550px]', headerClassName: 'w-[300px]',
cellClassName: 'p-[20px]' cellClassName: 'p-[20px]'
} }
}, },
@ -106,6 +114,26 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
headerClassName: 'w-[200px]' 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', accessorKey: 'trx_count_today',
header: ({ column }) => ( header: ({ column }) => (
@ -144,16 +172,6 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
headerClassName: 'w-[200px]' headerClassName: 'w-[200px]'
} }
}, },
// {
// accessorFn: (row) => row.balance_type?.name,
// id: 'balance_type_name',
// header: ({ column }) => <DataGridColumnHeader title="Balance Type Name" column={column} />,
// enableSorting: false,
// enableHiding: false,
// meta: {
// headerClassName: 'w-[200px]'
// }
// },
{ {
accessorKey: 'currency_name', accessorKey: 'currency_name',
header: ({ column }) => <DataGridColumnHeader title="Currency Name" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Currency Name" column={column} />,
@ -182,6 +200,32 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
headerClassName: 'w-[200px]' 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, // accessorFn: (row) => row.group?.is_bank,
// id: 'is_bank', // id: 'is_bank',
@ -219,7 +263,7 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
if (f.id === 'id_wallet' && f.value) { if (f.id === 'id_wallet' && f.value) {
filterParams.id_wallet = f.value; filterParams.id_wallet = f.value;
} }
if (f.id === 'id_group' && f.value) { if (f.id === 'id_group' && f.value) {
filterParams.id_group = f.value; filterParams.id_group = f.value;
} }
@ -325,6 +369,8 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
<ManageWalletContext.Provider <ManageWalletContext.Provider
value={{ value={{
wallets, wallets,
showDetailDialog,
setShowDetailDialog,
showAddDialog, showAddDialog,
handleAddDialog, handleAddDialog,
showEditDialog, showEditDialog,
@ -336,6 +382,9 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
}} }}
> >
<Toaster expand visibleToasts={9} duration={3000} /> <Toaster expand visibleToasts={9} duration={3000} />
<ShowDetailDialog />
<DataGridProvider <DataGridProvider
columns={columns} columns={columns}
pagination={{ size: 10 }} pagination={{ size: 10 }}
@ -353,4 +402,4 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }
}; };
export { ManageWalletContext, ManageWalletContextProvider }; export { ManageWalletContext, ManageWalletContextProvider };
export type { WalletProps }; export type { WalletProps };