Compare commits

...

10 Commits

Author SHA1 Message Date
306080f592 update agent balance column 2026-05-29 10:58:00 +07:00
479d27892c update issue: transaction kind ===k 2026-05-25 08:52:56 +07:00
5c3e03dd3b update 2026-05-18 15:41:47 +07:00
b9edf2ec89 update 2026-05-18 10:02:28 +07:00
c2c8a3fe31 Merge branch 'master' of https://git.telkomcel.tl/TPAY/dashboard 2026-04-10 09:17:06 +07:00
cdf06c867f update 2026-04-10 09:14:18 +07:00
367a8b9bb0 delete yarn-lock and package-lock 2026-03-10 14:09:24 +07:00
6796bacc20 Delete package-lock.json 2026-03-08 15:11:48 +00:00
57c14b8fde Delete yarn.lock 2026-03-08 15:11:38 +00:00
699857e085 update conflict 2026-03-08 14:05:59 +07:00
8 changed files with 144 additions and 16491 deletions

10718
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,3 @@ export function uniqueID(): string {
const baseUrl = import.meta.env?.VITE_BASE_URL || '/';
export const TEMPLATE_CSV_URL = `${baseUrl}template_notification.csv`;
<<<<<<< HEAD
=======
>>>>>>> 10532a7cb1d720488c181b1fcac3b8046c888e77

View File

@ -15,7 +15,7 @@ import {
DialogTitle,
Typography,
Button,
Box,
Box,
Breadcrumbs,
Link
} from '@mui/material';
@ -24,6 +24,7 @@ import { toast } from 'sonner';
import { Container, DataGridLoader, DataGridProvider, LoaderTransparant } from '@/components';
import { ListToolBar } from './ListToolbar';
import { toAbsoluteUrl } from '@/utils';
const BASE_URL = apiConfig.service_customer;
let initBalance = {
@ -52,10 +53,9 @@ const AgentBalance = () => {
const [barcode, setBarcode] = useState('');
const [requested_date, setRequested_date] = useState('');
const [token_legacy, setToken_legacy] = useState('');
// const [selectedMember, setSelectedMember] = useState(initBalance);
useEffect(() => {
// fetchAgentBalance();
// fetchAgentBalance();
}, []);
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`, {
params: {
limit: limit,
page: page + 1, // +1 because your API uses 1-based pages
page: page + 1,
with_deleted: false,
order_field: 'created_at',
order_direction: 'DESC',
@ -80,6 +80,74 @@ const AgentBalance = () => {
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 fetchExportPdf = 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-pdf`, {
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)}.pdf`;
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 closeDialog = () => {
setIsDialogOpen(false);
@ -108,7 +176,6 @@ const AgentBalance = () => {
await fetchAgentBalance(currentPage, pageSize, '', '');
closeDialog();
setIsReloading(false);
// fetchAgentBalance();
};
function formatDate(value: string) {
@ -139,24 +206,14 @@ const AgentBalance = () => {
<title>TPAY | Agent Balance</title>
</Helmet>
<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>
<Breadcrumbs>
<Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Members</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Agent Balances</span>
</Link>
@ -166,7 +223,6 @@ const AgentBalance = () => {
{isReloading && <DataGridLoader />}
{!isReloading && (
<DataGridProvider
// data={dataBalance}
pagination={{ size: pageSize }}
columns={getColumns(handleUpdate)}
layout={{ card: true }}
@ -179,6 +235,8 @@ const AgentBalance = () => {
createGroup={createGroup}
onReload={handleReload}
isReloading={isReloading}
onExport={fetchExport}
onExportPdf={fetchExportPdf}
/>
}
onRowSelectionChange={(selected, table: any) => {
@ -209,7 +267,7 @@ const AgentBalance = () => {
);
};
// ====================
// ====================
const PaymentCard = ({
barcode,
agent_name,
@ -335,4 +393,4 @@ const PaymentCard = ({
);
};
export default AgentBalance;
export default AgentBalance;

View File

@ -13,7 +13,8 @@ export type AgentBalance = {
balance_emoney: string,
balance_merchant: string,
balance_point: string,
balance_deposit: string
balance_deposit: string,
balance_edtl: string,
};
export const getColumns = (handleUpdate: (data: any) => void): ColumnDef<AgentBalance>[] => [
@ -129,6 +130,20 @@ export const getColumns = (handleUpdate: (data: any) => void): ColumnDef<AgentBa
);
}
},
{
accessorKey: 'balance_edtl',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
EDTL
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: 'balance_point',
header: ({ column }) => {

View File

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

View File

@ -374,7 +374,7 @@ const DetailTransaction = () => {
)}
<div className="py-4 overflow-y-auto max-h-[400px]">
{activeTab === 'detail' && transactionDetails?.kind === 'P' && (
{activeTab === 'detail' && (transactionDetails?.kind === 'P' || transactionDetails?.kind === 'K') && (
<div className="space-y-4">
<h3 className="font-semibold flex items-center">
Transaction Information

View File

@ -414,7 +414,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
limit,
page: page + 1,
with_deleted: false,
order_field: 'Transactions.created_at',
order_field: 'Transactions.transaction_date',
order_direction: 'DESC',
filter: JSON.stringify(formattedFilter)
});

5724
yarn.lock

File diff suppressed because it is too large Load Diff