This commit is contained in:
wayanrivan
2026-05-18 10:02:28 +07:00
parent c2c8a3fe31
commit b9edf2ec89
2 changed files with 84 additions and 42 deletions

View File

@ -15,7 +15,7 @@ import {
DialogTitle, DialogTitle,
Typography, Typography,
Button, Button,
Box, Box,
Breadcrumbs, Breadcrumbs,
Link Link
} from '@mui/material'; } from '@mui/material';
@ -24,6 +24,7 @@ import { toast } from 'sonner';
import { Container, DataGridLoader, DataGridProvider, LoaderTransparant } from '@/components'; import { Container, DataGridLoader, DataGridProvider, LoaderTransparant } from '@/components';
import { ListToolBar } from './ListToolbar'; import { ListToolBar } from './ListToolbar';
import { toAbsoluteUrl } from '@/utils'; import { toAbsoluteUrl } from '@/utils';
const BASE_URL = apiConfig.service_customer; const BASE_URL = apiConfig.service_customer;
let initBalance = { let initBalance = {
@ -52,10 +53,9 @@ const AgentBalance = () => {
const [barcode, setBarcode] = useState(''); const [barcode, setBarcode] = useState('');
const [requested_date, setRequested_date] = useState(''); const [requested_date, setRequested_date] = useState('');
const [token_legacy, setToken_legacy] = useState(''); const [token_legacy, setToken_legacy] = useState('');
// const [selectedMember, setSelectedMember] = useState(initBalance);
useEffect(() => { useEffect(() => {
// fetchAgentBalance(); // fetchAgentBalance();
}, []); }, []);
const fetchAgentBalance = async (page: number, limit: number, sorting: any, filter: any) => { const fetchAgentBalance = async (page: number, limit: number, sorting: any, filter: any) => {
@ -63,7 +63,7 @@ const AgentBalance = () => {
let balances = await axios.get(`${BASE_URL}/customer/agent-balance`, { let balances = await axios.get(`${BASE_URL}/customer/agent-balance`, {
params: { params: {
limit: limit, limit: limit,
page: page + 1, // +1 because your API uses 1-based pages page: page + 1,
with_deleted: false, with_deleted: false,
order_field: 'created_at', order_field: 'created_at',
order_direction: 'DESC', order_direction: 'DESC',
@ -80,6 +80,41 @@ const AgentBalance = () => {
return { data: resBalance, totalCount: balances?.data.data.total_count }; return { data: resBalance, totalCount: balances?.data.data.total_count };
}; };
// ─── Export ───────────────────────────────────────────────────────────────────────────────
const fetchExport = async (filter: any) => {
try {
toast.info('Preparing export…');
// API expects filter as array of { id, value } — same shape as columnFilters
const filterArray = Object.entries(filter)
.filter(([, value]) => value !== '' && value != null)
.map(([id, value]) => ({ id, value }));
const res = await axios.get(`${BASE_URL}/customer/agent-balance/export-excel`, {
params: {
with_deleted: false,
order_field: 'created_at',
order_direction: 'DESC',
filter: JSON.stringify(filterArray),
specialFilter: true
},
responseType: 'blob'
});
const url = URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.download = `agent_balance_export_${new Date().toISOString().slice(0, 10)}.xlsx`;
link.click();
URL.revokeObjectURL(url);
toast.success('Export complete');
} catch (error: any) {
const msg = error?.response?.data?.error || error.message;
toast.error(`Export failed: ${msg}`);
}
};
// ─────────────────────────────────────────────────────────────────────────────
const openDialog = () => setIsDialogOpen(true); const openDialog = () => setIsDialogOpen(true);
const closeDialog = () => { const closeDialog = () => {
setIsDialogOpen(false); setIsDialogOpen(false);
@ -108,7 +143,6 @@ const AgentBalance = () => {
await fetchAgentBalance(currentPage, pageSize, '', ''); await fetchAgentBalance(currentPage, pageSize, '', '');
closeDialog(); closeDialog();
setIsReloading(false); setIsReloading(false);
// fetchAgentBalance();
}; };
function formatDate(value: string) { function formatDate(value: string) {
@ -139,24 +173,14 @@ const AgentBalance = () => {
<title>TPAY | Agent Balance</title> <title>TPAY | Agent Balance</title>
</Helmet> </Helmet>
<Container> <Container>
{/* <ConfirmDialog
open={dialogOpen}
onClose={() => setDialogOpen(false)}
title="Confirm Action"
content={''}
onYes={handleYes}
onNo={() => setDialogOpen(false)}
/> */}
<h1 className="text-xl font-medium leading-none text-gray-900 mb-5">Agent Balances</h1> <h1 className="text-xl font-medium leading-none text-gray-900 mb-5">Agent Balances</h1>
<Breadcrumbs> <Breadcrumbs>
<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>
</Link> </Link>
<Link underline="none" color="inherit"> <Link underline="none" color="inherit">
<span className="text-sm">Members</span> <span className="text-sm">Members</span>
</Link> </Link>
<Link underline="none" color="inherit"> <Link underline="none" color="inherit">
<span className="text-sm">Agent Balances</span> <span className="text-sm">Agent Balances</span>
</Link> </Link>
@ -166,7 +190,6 @@ const AgentBalance = () => {
{isReloading && <DataGridLoader />} {isReloading && <DataGridLoader />}
{!isReloading && ( {!isReloading && (
<DataGridProvider <DataGridProvider
// data={dataBalance}
pagination={{ size: pageSize }} pagination={{ size: pageSize }}
columns={getColumns(handleUpdate)} columns={getColumns(handleUpdate)}
layout={{ card: true }} layout={{ card: true }}
@ -179,6 +202,7 @@ const AgentBalance = () => {
createGroup={createGroup} createGroup={createGroup}
onReload={handleReload} onReload={handleReload}
isReloading={isReloading} isReloading={isReloading}
onExport={fetchExport}
/> />
} }
onRowSelectionChange={(selected, table: any) => { onRowSelectionChange={(selected, table: any) => {
@ -209,7 +233,7 @@ const AgentBalance = () => {
); );
}; };
// ==================== // ====================
const PaymentCard = ({ const PaymentCard = ({
barcode, barcode,
agent_name, agent_name,
@ -335,4 +359,4 @@ const PaymentCard = ({
); );
}; };
export default AgentBalance; export default AgentBalance;

View File

@ -6,14 +6,13 @@ interface ListToolBarProps {
createGroup: () => void; createGroup: () => void;
onReload: () => void; onReload: () => void;
isReloading: boolean; isReloading: boolean;
onExport: (filter: Record<string, string>) => void;
} }
const ListToolBar = ({ createGroup, onReload, isReloading }: ListToolBarProps) => { const ListToolBar = ({ createGroup, onReload, isReloading, onExport }: ListToolBarProps) => {
const { table, reload } = useDataGrid(); const { table, reload } = useDataGrid();
const [usernameFilter, setUsernameFilter] = useState(''); const [usernameFilter, setUsernameFilter] = useState('');
const [msisdn, setMsisdn] = useState(''); const [msisdn, setMsisdn] = useState('');
const [typeSearchValue, setSearchTypeValue] = useState<string>();
const [searchValue, setSearchValue] = useState<string>();
const [fullname, setFullname] = useState(''); const [fullname, setFullname] = useState('');
const [merchantName, setMerchantName] = useState(''); const [merchantName, setMerchantName] = useState('');
@ -33,14 +32,27 @@ const ListToolBar = ({ createGroup, onReload, isReloading }: ListToolBarProps) =
setMerchantName(e.target.value); setMerchantName(e.target.value);
}; };
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { /** Build the same filter object the table uses for server-side fetching */
e.preventDefault() const buildFilter = (): Record<string, string> => {
table.resetPageIndex() const filter: Record<string, string> = {};
if (msisdn) filter['msisdn'] = msisdn;
if (fullname) filter['fullname'] = fullname;
if (usernameFilter) filter['username'] = usernameFilter;
if (merchantName) filter['agent_name'] = merchantName;
return filter;
};
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
table.resetPageIndex();
table.getColumn('username')?.setFilterValue(usernameFilter); table.getColumn('username')?.setFilterValue(usernameFilter);
table.getColumn('msisdn')?.setFilterValue(msisdn); table.getColumn('msisdn')?.setFilterValue(msisdn);
table.getColumn('fullname')?.setFilterValue(fullname); table.getColumn('fullname')?.setFilterValue(fullname);
table.getColumn('agent_name')?.setFilterValue(merchantName); table.getColumn('agent_name')?.setFilterValue(merchantName);
// if (!merchantName && !fullname && !msisdn && !usernameFilter) reload() };
const handleExport = () => {
onExport(buildFilter());
}; };
return ( return (
@ -48,25 +60,30 @@ const ListToolBar = ({ createGroup, onReload, isReloading }: ListToolBarProps) =
<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">
<form onSubmit={(e:any) => handleSearch(e)}> <form onSubmit={handleSearch}>
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<label className="input input-sm w-1/3"> <label className="input input-sm w-1/3">
<input type="text" placeholder="Search phone" value={msisdn} onChange={filtermsisdn}/> <input type="text" placeholder="Search phone" value={msisdn} onChange={filtermsisdn} />
</label> </label>
<label className="input input-sm w-1/3"> <label className="input input-sm w-1/3">
<input type="text" placeholder="Search fullname" value={fullname} onChange={filterfullname}/> <input type="text" placeholder="Search fullname" value={fullname} onChange={filterfullname} />
</label> </label>
<label className="input input-sm w-1/3"> <label className="input input-sm w-1/3">
<input type="text" placeholder="Search username" value={usernameFilter} onChange={handleUsernameChange}/> <input type="text" placeholder="Search username" value={usernameFilter} onChange={handleUsernameChange} />
</label> </label>
<label className="input input-sm w-1/3"> <label className="input input-sm w-1/3">
<input type="text" placeholder="Search merchant name" value={merchantName} onChange={filtermerchantname}/> <input type="text" placeholder="Search merchant name" value={merchantName} onChange={filtermerchantname} />
</label> </label>
<Button variant="outline" className="h-7.5"><KeenIcon icon="magnifier" /></Button> <Button variant="outline" className="h-7.5" type="submit">
</div> <KeenIcon icon="magnifier" />
</Button>
<Button className="h-7.5" type="button" onClick={handleExport}>
Export
</Button>
</div>
</form> </form>
<div className="flex gap-3 items-center"> <div className="flex gap-2 items-center">
<DefaultTooltip title={isReloading ? 'Refreshing...' : 'Refresh'} placement={'top'}> <DefaultTooltip title={isReloading ? 'Refreshing...' : 'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={onReload} disabled={isReloading}> <Button variant="outline" className="h-7.5" onClick={onReload} disabled={isReloading}>
{isReloading ? ( {isReloading ? (
@ -79,10 +96,11 @@ const ListToolBar = ({ createGroup, onReload, isReloading }: ListToolBarProps) =
</Button> </Button>
</DefaultTooltip> </DefaultTooltip>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
}; };
export { ListToolBar }; export { ListToolBar };