Merge branch 'master' of https://git.shiblysolution.id/TPAY/dashboard
This commit is contained in:
BIN
public/media/file-templates/batch-sample.xlsx
Normal file
BIN
public/media/file-templates/batch-sample.xlsx
Normal file
Binary file not shown.
@ -150,7 +150,7 @@ const Login = () => {
|
||||
/>
|
||||
<span className="checkbox-label">Remember me</span>
|
||||
</label>
|
||||
<Link
|
||||
{/* <Link
|
||||
to={
|
||||
currentLayout?.name === 'auth-branded'
|
||||
? '/auth/reset-password'
|
||||
@ -159,7 +159,7 @@ const Login = () => {
|
||||
className="text-2sm link shrink-0"
|
||||
>
|
||||
Forgot Password?
|
||||
</Link>
|
||||
</Link> */}
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@ -53,7 +53,7 @@ const MemberActivity = ({ startdate, enddate }: Props) => {
|
||||
const arcY = 50 - radius * Math.sin(arcAngle);
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-white rounded-lg shadow-md w-full max-w-xl">
|
||||
<div className="p-6 bg-white rounded-lg shadow-md w-1/3">
|
||||
<div className="flex justify-between items-center pb-3 mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-700">Member Activity</h2>
|
||||
</div>
|
||||
|
||||
@ -46,7 +46,7 @@ const TransactionPieChart = ({ startdate, enddate }: Props) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-md p-6 w-full max-w-md">
|
||||
<div className="bg-white rounded-lg shadow-md p-6 w-1/3">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-700">Transaction Chart</h2>
|
||||
</div>
|
||||
|
||||
@ -80,7 +80,7 @@ const TransactionValue = ({ startdate, enddate }: Props) => {
|
||||
const maxValue = transactionData.length > 0 ? Math.max(...transactionData.map(item => item.value)) : 1;
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow p-6 w-full max-w-md">
|
||||
<div className="bg-white rounded-xl shadow p-6 w-1/3">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-800">Transaction Value</h2>
|
||||
</div>
|
||||
|
||||
@ -3,6 +3,7 @@ import { useTransactionContext } from '../hooks/useTransactionContext';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
|
||||
const ListToolbar = () => {
|
||||
const { table, reload } = useDataGrid();
|
||||
@ -23,8 +24,8 @@ const ListToolbar = () => {
|
||||
nextWeek.setDate(today.getDate() + 7);
|
||||
|
||||
settrxDate({
|
||||
from: formatDate(today), // Set 'from' to today
|
||||
to: formatDate(nextWeek), // Set 'to' to 7 days later
|
||||
from: formatDate(today), // Set 'from' to today
|
||||
to: formatDate(nextWeek) // Set 'to' to 7 days later
|
||||
});
|
||||
}, []);
|
||||
|
||||
@ -37,6 +38,15 @@ const ListToolbar = () => {
|
||||
}
|
||||
}, [trxDate, table]);
|
||||
|
||||
const handleExportData = () => {
|
||||
const link = document.createElement('a');
|
||||
link.href = toAbsoluteUrl('/media/file-templates/batch-sample.xlsx');
|
||||
link.download = 'upload-batch-template.xlsx';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (trxDate.from && trxDate.to) {
|
||||
handleFilterData();
|
||||
@ -55,6 +65,15 @@ const ListToolbar = () => {
|
||||
>
|
||||
Upload Batch
|
||||
</Button>
|
||||
<DefaultTooltip title={'Download Template'} placement={'top'}>
|
||||
<Button onClick={handleExportData} className="h-7.5" variant={'outline'}>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/file-types/xls.svg')}
|
||||
className="dark:hidden h-5"
|
||||
alt="Export to Excel"
|
||||
/>
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
<DefaultTooltip title={'Refresh'} placement={'top'}>
|
||||
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
|
||||
<KeenIcon icon="arrows-circle" />
|
||||
|
||||
@ -24,6 +24,7 @@ import { toast } from 'sonner';
|
||||
import { useCallApi } from '@/hooks';
|
||||
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||
import clsx from 'clsx';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
|
||||
const API_URL = apiConfig.service_disbursement;
|
||||
|
||||
@ -32,17 +33,18 @@ const UploadBatchDialog = () => {
|
||||
const { showUploadBatchDialog, handleUploadBatchDialog } = useTransactionContext();
|
||||
const { reload } = useDataGrid();
|
||||
const { PostData, PostDataFile, GetData } = useCallApi();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [alert, setAlert] = useState({
|
||||
show: false,
|
||||
message: ''
|
||||
});
|
||||
const initialState: {
|
||||
execution_date: string,
|
||||
execution_date: string;
|
||||
file: File | null;
|
||||
} = {
|
||||
execution_date: '',
|
||||
file: null
|
||||
}
|
||||
};
|
||||
const [formField, setFormField] = useState(initialState);
|
||||
|
||||
const resetForm = () => {
|
||||
@ -54,31 +56,33 @@ const UploadBatchDialog = () => {
|
||||
const doUploadBatch = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('execution_date', formField.execution_date);
|
||||
|
||||
if (formField.file) {
|
||||
formData.append('file', formField.file);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await PostDataFile(`${API_URL}/upload-excel`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (response?.status) {
|
||||
handleUploadBatchDialog(false);
|
||||
resetForm();
|
||||
reload();
|
||||
|
||||
|
||||
const createActivity = {
|
||||
module: 'Disbursement',
|
||||
description: `Create New Disbursement => ${formField.file?.name}`,
|
||||
action: 'C'
|
||||
};
|
||||
|
||||
|
||||
doSaveLogActivity(createActivity);
|
||||
toast.success('Success Create Disbursement');
|
||||
} else {
|
||||
@ -88,6 +92,8 @@ const UploadBatchDialog = () => {
|
||||
} catch (error) {
|
||||
toast.error('Error uploading batch');
|
||||
setAlert({ show: true, message: 'Something went wrong. Please try again.' });
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
},
|
||||
[formField]
|
||||
@ -98,10 +104,7 @@ const UploadBatchDialog = () => {
|
||||
|
||||
console.log('Form data before submit:', formField);
|
||||
|
||||
if (
|
||||
formField.execution_date.trim() === '' ||
|
||||
formField.file === null
|
||||
) {
|
||||
if (formField.execution_date.trim() === '' || formField.file === null) {
|
||||
setAlert({ show: true, message: 'Please fill in all required fields.' });
|
||||
return;
|
||||
}
|
||||
@ -110,7 +113,7 @@ const UploadBatchDialog = () => {
|
||||
// console.log(formField);
|
||||
setAlert({ show: false, message: '' });
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (showUploadBatchDialog === false) {
|
||||
resetForm();
|
||||
@ -150,7 +153,9 @@ const UploadBatchDialog = () => {
|
||||
<div className="card-body grid gap-5 p-0">
|
||||
<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">Execution Date</label>
|
||||
<label className="form-label flex items-center gap-1 max-w-56">
|
||||
Execution Date
|
||||
</label>
|
||||
<Input
|
||||
className="input"
|
||||
type="datetime-local"
|
||||
@ -167,19 +172,23 @@ const UploadBatchDialog = () => {
|
||||
<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">File</label>
|
||||
<Input
|
||||
className="input"
|
||||
type="file"
|
||||
autoComplete="off"
|
||||
required
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, file: target.files?.[0] ?? null }))
|
||||
}
|
||||
className="input"
|
||||
type="file"
|
||||
autoComplete="off"
|
||||
required
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, file: target.files?.[0] ?? null }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end pt-2.5">
|
||||
<Button className="btn btn-primary" type="submit">
|
||||
Save Changes
|
||||
<Button variant="default" type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? (
|
||||
<RefreshCw className="animate-spin h-8 w-8 text-white mx-3" />
|
||||
) : (
|
||||
'Submit'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@ -59,16 +59,12 @@ const EditDialog = () => {
|
||||
if (response?.status) {
|
||||
toast.success('Feedback reviewed successfully');
|
||||
|
||||
// Close the edit dialog
|
||||
handleEditDialog(false, null);
|
||||
|
||||
// First close the detail dialog to reset its state
|
||||
handleDetailDialog(false, null);
|
||||
|
||||
// Refresh the main data grid first
|
||||
refreshData();
|
||||
|
||||
// Wait a tiny bit then reopen the detail with refreshed data
|
||||
setTimeout(() => {
|
||||
handleDetailDialog(true, selectedFeedback);
|
||||
}, 300);
|
||||
@ -97,7 +93,6 @@ const EditDialog = () => {
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
// console.log('Fetched data:', data);
|
||||
|
||||
setReviewNotes(data.review_notes || '');
|
||||
|
||||
@ -112,7 +107,11 @@ const EditDialog = () => {
|
||||
|
||||
return (
|
||||
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
|
||||
<DialogContent className="p-0 w-full sm:max-w-2xl bg-white rounded-lg overflow-hidden">
|
||||
<DialogContent className="p-0 w-full sm:max-w-2xl bg-white rounded-lg overflow-hidden [&>button]:hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle></DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="relative">
|
||||
<div className="p-6 flex items-center justify-between border-b">
|
||||
<div>
|
||||
|
||||
@ -65,7 +65,7 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa
|
||||
const handleChange = async (e: any) => {
|
||||
const { name, value } = e.target;
|
||||
if (name === "file_selfie" || name === "photouser" || name === 'file_document_id' || name === "file_document_id_selfie" ||
|
||||
name === "file_commercial_license" || name === 'photomerchant') { // FOR FILE ONLY
|
||||
name === "file_commercial_license") { // FOR FILE ONLY
|
||||
setFormData({ ...formData, [name]: e.target.files[0] });
|
||||
} else if(name === "nationality") {
|
||||
setFormData({ ...formData, [name]: value });
|
||||
@ -258,7 +258,6 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa
|
||||
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Merchant Address', 'merchantaddress', 'text', false, viewOnly): ''}
|
||||
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Longitude Merchant', 'longitudemerchant', 'text', false, viewOnly): ''}
|
||||
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Latitude Merchant', 'latitudemerchant', 'text', false, viewOnly): ''}
|
||||
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'Photo Merchant', 'photomerchant', previewImg, setPreviewImg): ''}
|
||||
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Selfie', 'file_selfie', previewImg, setPreviewImg): ''}
|
||||
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Document', 'file_document_id', previewImg, setPreviewImg): ''}
|
||||
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Document & Selfie', 'file_document_id_selfie', previewImg, setPreviewImg): ''}
|
||||
|
||||
@ -10,8 +10,10 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
const API_URL = apiConfig.transaction;
|
||||
const API_URL_CURRENT_BALANCE = apiConfig.service_wallet;
|
||||
|
||||
const DetailApprovalTransaction = () => {
|
||||
const { GetData } = useCallApi();
|
||||
@ -51,6 +53,60 @@ const DetailApprovalTransaction = () => {
|
||||
}
|
||||
}, [showDetailDialog]);
|
||||
|
||||
const [currentBalance, setCurrentBalance] = useState<number | null>(null);
|
||||
const [currentBalanceTransfer, setCurrentBalanceTransfer] = useState<number | null>(null);
|
||||
useEffect(() => {
|
||||
setCurrentBalance(null);
|
||||
}, [transactionDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentBalanceTransfer(null);
|
||||
}, [transactionDetails]);
|
||||
|
||||
const getCurrentBallanceWallet = async (customerId: string) => {
|
||||
try {
|
||||
const responsecurrentbalance = await GetData(
|
||||
`${API_URL_CURRENT_BALANCE}/dashboard/balance/account/${customerId}`,
|
||||
{}
|
||||
);
|
||||
|
||||
const match = responsecurrentbalance?.data?.find(
|
||||
(item: any) => item.id_wallet === transactionDetails?.origin_wallet?.id
|
||||
);
|
||||
|
||||
if (match) {
|
||||
// console.log('Matched Wallet:', match); // Optional debug
|
||||
return match.amount;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching current balance', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCurrentBallanceWalletTransfer = async (customerIdtransfer: string) => {
|
||||
try {
|
||||
const response = await GetData(
|
||||
`${API_URL_CURRENT_BALANCE}/dashboard/balance/account/${customerIdtransfer}`,
|
||||
{}
|
||||
);
|
||||
|
||||
const match = response?.data?.find(
|
||||
(item: any) => item.id_wallet === transactionDetails?.transfer?.destination_wallet?.id
|
||||
);
|
||||
|
||||
if (match) {
|
||||
// console.log('Matched Wallet:', match);
|
||||
return match.amount;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching current balance', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
||||
@ -146,14 +202,20 @@ const DetailApprovalTransaction = () => {
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Amount</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase?.amount) ?? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.transfer.amount)}</p>
|
||||
<p className="font-medium">
|
||||
{(transactionDetails?.purchase?.amount ?? transactionDetails?.transfer?.amount) != null
|
||||
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails.purchase?.amount ?? transactionDetails.transfer?.amount
|
||||
)
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Fee</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.kind === 'P'
|
||||
? transactionDetails?.purchase.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
|
||||
: transactionDetails?.transfer.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||
{(transactionDetails?.transfer?.fee_amount ?? transactionDetails?.purchase?.fee_amount ?? 0)
|
||||
.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@ -234,19 +296,33 @@ const DetailApprovalTransaction = () => {
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Amount</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.amount)}</p>
|
||||
<p className="font-medium">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails?.purchase?.amount ?? transactionDetails?.transfer?.amount ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Cashback</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.cashback)}</p>
|
||||
<p className="font-medium">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails?.purchase?.cashback ?? transactionDetails?.transfer?.cashback ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Cashback Point</p>
|
||||
<p className="font-medium">{transactionDetails?.purchase.cashback_point}</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.cashback_point ?? transactionDetails?.transfer?.cashback_point ?? 0}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Fee Amount</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.fee_amount)}</p>
|
||||
<p className="font-medium">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails?.purchase?.fee_amount ?? transactionDetails?.transfer?.fee_amount ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -471,7 +547,27 @@ const DetailApprovalTransaction = () => {
|
||||
<p className="text-sm text-gray-500">Description</p>
|
||||
<p className="font-medium">{transactionDetails?.origin_wallet.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Current Balance</p>
|
||||
<p className="font-medium">
|
||||
{currentBalance !== null && currentBalance !== undefined
|
||||
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(currentBalance)
|
||||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const balance = await getCurrentBallanceWallet(transactionDetails?.origin_customer.id);
|
||||
if (balance !== null) {
|
||||
setCurrentBalance(balance);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Generate Current Balance
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -482,16 +578,42 @@ const DetailApprovalTransaction = () => {
|
||||
{!transactionDetails?.transfer ? (
|
||||
<div className="text-center text-sm text-gray-500">No Data available</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Name</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.name}</p>
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Name</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Description</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Current Balance</p>
|
||||
<p className="font-medium">
|
||||
{currentBalanceTransfer !== null
|
||||
? new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(currentBalanceTransfer)
|
||||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Description</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const customerId = transactionDetails?.transfer?.destination_customer?.id;
|
||||
const balance = await getCurrentBallanceWalletTransfer(customerId);
|
||||
if (balance !== null) {
|
||||
setCurrentBalanceTransfer(balance); // ✅ INI YANG BENAR
|
||||
}
|
||||
}}
|
||||
>
|
||||
Generate Current Balance
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -540,8 +662,8 @@ const DetailApprovalTransaction = () => {
|
||||
})
|
||||
: ''}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_body}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
|
||||
@ -40,9 +40,9 @@ const ListToolbar = () => {
|
||||
// useEffect to set the default date values
|
||||
useEffect(() => {
|
||||
const today = new Date();
|
||||
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
// const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
settrxDate({
|
||||
from: formatDate(firstDayOfMonth),
|
||||
from: formatDate(today),
|
||||
to: formatDate(today),
|
||||
});
|
||||
}, []);
|
||||
@ -121,7 +121,7 @@ const ListToolbar = () => {
|
||||
<KeenIcon icon="magnifier" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Transaction ID"
|
||||
placeholder="Search Transaction ID, MSISDN, Or Full Name Customer"
|
||||
value={searchValue}
|
||||
onChange={(event) => setSearchValue(event.target.value)}
|
||||
/>
|
||||
@ -136,13 +136,13 @@ const ListToolbar = () => {
|
||||
className="h-7.5"
|
||||
onClick={() => {
|
||||
const today = new Date();
|
||||
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
// const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
|
||||
// Resetting all filters
|
||||
setSearchValue('');
|
||||
setStatusApproval('');
|
||||
settrxDate({
|
||||
from: formatDate(firstDayOfMonth),
|
||||
from: formatDate(today),
|
||||
to: formatDate(today),
|
||||
});
|
||||
|
||||
|
||||
@ -270,16 +270,19 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode }
|
||||
});
|
||||
}
|
||||
|
||||
// Constructing filter object with dynamic properties
|
||||
const formattedFilter: any = {
|
||||
"Transactions.transaction_date": {
|
||||
from: `${startdate} 00:00:00`,
|
||||
to: `${enddate} 23:59:59`,
|
||||
},
|
||||
to: `${enddate} 23:59:59`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (transactioncode) {
|
||||
formattedFilter["Transactions.code"] = { like: `%${transactioncode}%` };
|
||||
formattedFilter["$or"] = [
|
||||
{ "Transactions.code": { like: `%${transactioncode}%` } },
|
||||
{ "Transactions.msisdn": { like: `${transactioncode}` } },
|
||||
{ "origin_customer.fullname": { like: `${transactioncode}` } }
|
||||
];
|
||||
}
|
||||
|
||||
// Handle approval status filter
|
||||
|
||||
@ -13,11 +13,15 @@ import {
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog';
|
||||
import { useDataGrid } from '@/components';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
const API_URL = apiConfig.transaction;
|
||||
const API_URL_CURRENT_BALANCE = apiConfig.service_wallet;
|
||||
|
||||
|
||||
const DetailTransaction = () => {
|
||||
const { GetData } = useCallApi();
|
||||
|
||||
// const { reload } = useDataGrid();
|
||||
const {
|
||||
showDetailDialog,
|
||||
@ -67,6 +71,61 @@ const DetailTransaction = () => {
|
||||
}
|
||||
}, [showDetailDialog]);
|
||||
|
||||
const [currentBalance, setCurrentBalance] = useState<number | null>(null);
|
||||
const [currentBalanceTransfer, setCurrentBalanceTransfer] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentBalance(null);
|
||||
}, [transactionDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentBalanceTransfer(null);
|
||||
}, [transactionDetails]);
|
||||
|
||||
const getCurrentBallanceWallet = async (customerId: string) => {
|
||||
try {
|
||||
const responsecurrentbalance = await GetData(
|
||||
`${API_URL_CURRENT_BALANCE}/dashboard/balance/account/${customerId}`,
|
||||
{}
|
||||
);
|
||||
|
||||
const match = responsecurrentbalance?.data?.find(
|
||||
(item: any) => item.id_wallet === transactionDetails?.origin_wallet?.id
|
||||
);
|
||||
|
||||
if (match) {
|
||||
// console.log('Matched Wallet:', match); // Optional debug
|
||||
return match.amount;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching current balance', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
const getCurrentBallanceWalletTransfer = async (customerIdtransfer: string) => {
|
||||
try {
|
||||
const response = await GetData(
|
||||
`${API_URL_CURRENT_BALANCE}/dashboard/balance/account/${customerIdtransfer}`,
|
||||
{}
|
||||
);
|
||||
|
||||
const match = response?.data?.find(
|
||||
(item: any) => item.id_wallet === transactionDetails?.transfer?.destination_wallet?.id
|
||||
);
|
||||
|
||||
if (match) {
|
||||
// console.log('Matched Wallet:', match);
|
||||
return match.amount;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching current balance', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
||||
@ -162,14 +221,20 @@ const DetailTransaction = () => {
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Amount</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase?.amount) ?? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.transfer.amount)}</p>
|
||||
<p className="font-medium">
|
||||
{(transactionDetails?.purchase?.amount ?? transactionDetails?.transfer?.amount) != null
|
||||
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails.purchase?.amount ?? transactionDetails.transfer?.amount
|
||||
)
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Fee</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.kind === 'P'
|
||||
? transactionDetails?.purchase.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
|
||||
: transactionDetails?.transfer.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||
{(transactionDetails?.transfer?.fee_amount ?? transactionDetails?.purchase?.fee_amount ?? 0)
|
||||
.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@ -231,16 +296,6 @@ const DetailTransaction = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="font-semibold flex items-center">
|
||||
Origin Wallet
|
||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
||||
Wallet
|
||||
</span>
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
|
||||
</div>
|
||||
|
||||
<h3 className="font-semibold flex items-center">
|
||||
Purchase
|
||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
||||
@ -250,69 +305,91 @@ const DetailTransaction = () => {
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Amount</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.amount)}</p>
|
||||
<p className="font-medium">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails?.purchase?.amount ?? transactionDetails?.transfer?.amount ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Cashback</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.cashback)}</p>
|
||||
<p className="font-medium">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails?.purchase?.cashback ?? transactionDetails?.transfer?.cashback ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Cashback Point</p>
|
||||
<p className="font-medium">{transactionDetails?.purchase.cashback_point}</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.cashback_point ?? transactionDetails?.transfer?.cashback_point ?? 0}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Fee Amount</p>
|
||||
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.fee_amount)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 'detail' && transactionDetails?.kind === 'P' && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold flex items-center">
|
||||
Product Information
|
||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
||||
Product Info
|
||||
</span>
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Product Name</p>
|
||||
<p className="font-medium">{transactionDetails?.purchase.product.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Price Cash</p>
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.product.price_cash)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Price Point</p>
|
||||
<p className="font-medium">{transactionDetails?.purchase.product.price_point}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Product Type</p>
|
||||
<p className="font-medium">{transactionDetails?.purchase.product.type}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Provider Name</p>
|
||||
<p className="font-medium">{transactionDetails?.purchase.product.provider.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Provider Type</p>
|
||||
<p className="font-medium">
|
||||
{(() => {
|
||||
let providertype;
|
||||
if (transactionDetails?.purchase.product.provider.type === 'h2h') {
|
||||
providertype = 'HOST TO HOST';
|
||||
} else if (transactionDetails?.purchase.product.provider.type === 'agent') {
|
||||
providertype = 'AGENT';
|
||||
}
|
||||
return providertype;
|
||||
})()}
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||
transactionDetails?.purchase?.fee_amount ?? transactionDetails?.transfer?.fee_amount ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* {activeTab === 'detail' && transactionDetails?.kind === 'P' && ( */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold flex items-center">
|
||||
Product Information
|
||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
||||
Product Info
|
||||
</span>
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Product Name</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.product?.name ?? "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Price Cash</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.product?.price_cash != null
|
||||
? new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(transactionDetails.purchase.product.price_cash)
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Price Point</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.product?.price_point ?? "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Product Type</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.product?.type ?? "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Provider Name</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.product?.provider?.description ?? "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Provider Type</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.purchase?.product?.provider?.type === "h2h"
|
||||
? "HOST TO HOST"
|
||||
: transactionDetails?.purchase?.product?.provider?.type === "agent"
|
||||
? "AGENT"
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* )} */}
|
||||
|
||||
{activeTab === 'detail' && transactionDetails?.kind != 'P' && transactionDetails?.transfer != null && (
|
||||
<div className="space-y-4">
|
||||
@ -348,7 +425,7 @@ const DetailTransaction = () => {
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Fee</p>
|
||||
<p className="font-medium">
|
||||
{transactionDetails?.transfer.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||
{(transactionDetails?.transfer?.fee_amount ?? transactionDetails?.purchase?.fee_amount ?? 0).toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@ -482,7 +559,27 @@ const DetailTransaction = () => {
|
||||
<p className="text-sm text-gray-500">Description</p>
|
||||
<p className="font-medium">{transactionDetails?.origin_wallet.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Current Balance</p>
|
||||
<p className="font-medium">
|
||||
{currentBalance !== null && currentBalance !== undefined
|
||||
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(currentBalance)
|
||||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const balance = await getCurrentBallanceWallet(transactionDetails?.origin_customer.id);
|
||||
if (balance !== null) {
|
||||
setCurrentBalance(balance);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Generate Current Balance
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -493,16 +590,43 @@ const DetailTransaction = () => {
|
||||
{!transactionDetails?.transfer ? (
|
||||
<div className="text-sm text-gray-500">No Data available</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Name</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.name}</p>
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Name</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Description</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Current Balance</p>
|
||||
<p className="font-medium">
|
||||
{currentBalanceTransfer !== null
|
||||
? new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(currentBalanceTransfer)
|
||||
: '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Description</p>
|
||||
<p className="font-medium">{transactionDetails.transfer.destination_wallet.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const customerId = transactionDetails?.transfer?.destination_customer?.id;
|
||||
const balance = await getCurrentBallanceWalletTransfer(customerId);
|
||||
if (balance !== null) {
|
||||
setCurrentBalanceTransfer(balance); // ✅ INI YANG BENAR
|
||||
}
|
||||
}}
|
||||
>
|
||||
Generate Current Balance
|
||||
</Button>
|
||||
</>
|
||||
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -61,9 +61,9 @@ const ListToolbar = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const today = new Date();
|
||||
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
// const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
settrxDate({
|
||||
from: formatDate(firstDayOfMonth),
|
||||
from: formatDate(today),
|
||||
to: formatDate(today),
|
||||
});
|
||||
}, []);
|
||||
@ -152,7 +152,7 @@ const ListToolbar = () => {
|
||||
<KeenIcon icon="magnifier" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Transaction ID"
|
||||
placeholder="Search Transaction ID, MSISDN, Or Full Name Customer"
|
||||
value={searchValue}
|
||||
onChange={(event) => setSearchValue(event.target.value)}
|
||||
/>
|
||||
@ -167,14 +167,14 @@ const ListToolbar = () => {
|
||||
onClick={() => {
|
||||
// Preserve trxDate values (from, to) and reset others
|
||||
const today = new Date();
|
||||
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
// const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
|
||||
// Only reset filters excluding from and to
|
||||
setSearchValue('');
|
||||
// setStatusValue('');
|
||||
setTypeValue('');
|
||||
settrxDate({
|
||||
from: formatDate(firstDayOfMonth),
|
||||
from: formatDate(today),
|
||||
to: formatDate(today),
|
||||
});
|
||||
|
||||
|
||||
@ -265,18 +265,19 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Build filter object dynamically
|
||||
const formattedFilter: any = {
|
||||
"Transactions.transaction_date": {
|
||||
from: `${startdate} 00:00:00`,
|
||||
to: `${enddate} 23:59:59`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (transactioncode) {
|
||||
formattedFilter["Transactions.code"] = {
|
||||
like: `%${transactioncode}%`
|
||||
};
|
||||
formattedFilter["$or"] = [
|
||||
{ "Transactions.code": { like: `%${transactioncode}%` } },
|
||||
{ "Transactions.msisdn": { like: `${transactioncode}` } },
|
||||
{ "origin_customer.fullname": { like: `${transactioncode}` } }
|
||||
];
|
||||
}
|
||||
|
||||
// if (transactionstatus) {
|
||||
@ -286,7 +287,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
if (type) {
|
||||
formattedFilter["Transactions.kind"] = type;
|
||||
}
|
||||
|
||||
|
||||
const response = await GetData(`${API_URL}/transaction/history`, {
|
||||
limit,
|
||||
page: page + 1,
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
|
||||
<div className="flex justify-between w-full items-center">
|
||||
<div className="flex w-[50%] gap-3 items-center">
|
||||
<label className="input input-sm w-1/3">
|
||||
{/* <label className="input input-sm w-1/3">
|
||||
<KeenIcon icon="magnifier" />
|
||||
<input
|
||||
type="text"
|
||||
@ -19,7 +19,7 @@
|
||||
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
|
||||
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</label> */}
|
||||
</div>
|
||||
<div className="flex gap-3 items-center">
|
||||
<Button
|
||||
|
||||
@ -23,6 +23,29 @@ interface TransferType {
|
||||
|
||||
const API_URL = apiConfig.service_transaction;
|
||||
|
||||
const typeLabelMap: Record<string, string> = {
|
||||
D: 'Disbursement',
|
||||
O: 'Other',
|
||||
CA: 'Change Group Emoney Customer to Agent',
|
||||
AC: 'Change Group Emoney Agent to Customer',
|
||||
PC: 'Change Group Point Agent to Customer',
|
||||
PA: 'Change Group Point Customer to Agent',
|
||||
CE: 'Return Customer Emoney',
|
||||
AD: 'Return Agent Deposit',
|
||||
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',
|
||||
DE: 'Disbursment Escrow',
|
||||
DM: 'Disbursment Master Agent',
|
||||
DA: 'Disbursment Agent',
|
||||
WI: 'Withdraw Merchant',
|
||||
IC: 'Income Merchant'
|
||||
};
|
||||
|
||||
const ListToolbar = () => {
|
||||
const { table, reload } = useDataGrid();
|
||||
const { handleAddDialog } = useManageTransferTypeContext();
|
||||
@ -31,68 +54,33 @@ const ListToolbar = () => {
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [statusTypes, setStatusTypes] = useState('');
|
||||
const [walletOrigins, setWalletOrigin] = useState('');
|
||||
const [walletDestinations, setWalletDestination] = useState('');
|
||||
const [approval, setApproval] = useState('');
|
||||
|
||||
const unique = (arr: string[]) => Array.from(new Set(arr));
|
||||
const types = unique(transferTypes.map((t) => t.type));
|
||||
const origins = unique(transferTypes.map((t) => t.wallet_origin.name));
|
||||
const destinations = unique(transferTypes.map((t) => t.wallet_destination.name));
|
||||
const typeLabelMap: Record<string, string> = {
|
||||
D: 'Disbursement',
|
||||
O: 'Other',
|
||||
CA: 'Change Group Emoney Customer to Agent',
|
||||
AC: 'Change Group Emoney Agent to Customer',
|
||||
PC: 'Change Group Point Agent to Customer',
|
||||
PA: 'Change Group Point Customer to Agent',
|
||||
CE: 'Return Customer Emoney',
|
||||
AD: 'Return Agent Deposit',
|
||||
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',
|
||||
DE: 'Disbursment Escrow',
|
||||
DM: 'Disbursment Master Agent',
|
||||
DA: 'Disbursment Agent',
|
||||
WI: 'Withdraw Merchant',
|
||||
IC: 'Income Merchant'
|
||||
};
|
||||
const approvalLabelMap: Record<string, string> = {
|
||||
Y: 'Yes',
|
||||
N: 'No'
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
table.getColumn('name')?.setFilterValue(searchValue);
|
||||
table.setPageIndex(0);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
table.getColumn('name')?.setFilterValue(searchValue);
|
||||
table.setPageIndex(0);
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchValue]);
|
||||
}, [searchValue, table]);
|
||||
|
||||
useEffect(() => {
|
||||
table.getColumn('type')?.setFilterValue(statusTypes);
|
||||
}, [statusTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
table
|
||||
.getColumn('wallet_origin')
|
||||
?.setFilterValue(walletOrigins === '__all__' ? '' : walletOrigins);
|
||||
}, [walletOrigins]);
|
||||
|
||||
useEffect(() => {
|
||||
table
|
||||
.getColumn('wallet_destination')
|
||||
?.setFilterValue(walletDestinations === '__all__' ? '' : walletDestinations);
|
||||
}, [walletDestinations]);
|
||||
}, [statusTypes, table]);
|
||||
|
||||
useEffect(() => {
|
||||
table.getColumn('status_approval')?.setFilterValue(approval);
|
||||
}, [approval]);
|
||||
}, [approval, table]);
|
||||
|
||||
const fetchTransferTypes = async () => {
|
||||
try {
|
||||
@ -115,62 +103,89 @@ const ListToolbar = () => {
|
||||
|
||||
const handleClearFilter = () => {
|
||||
setSearchValue('');
|
||||
setStatusTypes('__all__');
|
||||
setWalletOrigin('__all__');
|
||||
setWalletDestination('__all__');
|
||||
setApproval('__all__');
|
||||
setStatusTypes('');
|
||||
setApproval('');
|
||||
|
||||
table.getColumn('name')?.setFilterValue('');
|
||||
table.getColumn('type')?.setFilterValue('');
|
||||
table.getColumn('wallet_origin')?.setFilterValue('');
|
||||
table.getColumn('wallet_destination')?.setFilterValue('');
|
||||
table.getColumn('status_approval')?.setFilterValue('');
|
||||
table.setPageIndex(0);
|
||||
|
||||
setTimeout(() => {
|
||||
table.setPageIndex(0);
|
||||
reload();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const renderSelect = (
|
||||
placeholder: string,
|
||||
value: string,
|
||||
onChange: (val: string) => void,
|
||||
options: string[],
|
||||
labelMap?: Record<string, string>
|
||||
) => (
|
||||
<div className="min-w-[220px]">
|
||||
<Select value={value === '' ? undefined : value} onValueChange={onChange}>
|
||||
<SelectTrigger className="h-8 text-sm">
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{labelMap?.[opt] ?? opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
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="card-header flex-wrap border-b-0 px-5">
|
||||
<div className="flex flex-wrap gap-1 w-full">
|
||||
<div className="flex justify-between w-full items-center mb-3">
|
||||
<div className="flex w-[34%] gap-2 items-center">
|
||||
<label className="input input-sm w-full text-sm">
|
||||
<KeenIcon icon="magnifier" />
|
||||
<div className="flex flex-nowrap items-end gap-3 w-full overflow-x-auto">
|
||||
{/* Fixed Search Input dengan icon yang selalu terlihat */}
|
||||
<div className="relative w-48 shrink-0">
|
||||
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 pointer-events-none ">
|
||||
<KeenIcon icon="magnifier" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Transaction Type Name"
|
||||
placeholder="Search Name"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
className="input input-sm w-full pl-9 text-ellipsis overflow-hidden"
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="min-w-[220px] shrink-0">
|
||||
<Select
|
||||
key={`type-filter-${statusTypes}`}
|
||||
value={statusTypes}
|
||||
onValueChange={setStatusTypes}
|
||||
>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue placeholder="Select Status Transaction Type" className="text-ellipsis overflow-hidden" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="max-h-64 overflow-y-auto">
|
||||
{types.map((opt) => (
|
||||
<SelectItem key={opt} value={opt} className="truncate">
|
||||
{typeLabelMap[opt] ?? opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="min-w-[220px] shrink-0">
|
||||
<Select
|
||||
key={`approval-filter-${approval}`}
|
||||
value={approval}
|
||||
onValueChange={setApproval}
|
||||
>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue placeholder="Select Status Approval" className="text-ellipsis overflow-hidden" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="max-h-64 overflow-y-auto">
|
||||
<SelectItem value="Y" className="truncate">
|
||||
Yes
|
||||
</SelectItem>
|
||||
<SelectItem value="N" className="truncate">
|
||||
No
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DefaultTooltip title={'Reset Filter'} placement={'top'}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-7.5 disabled:bg-gray-400 shrink-0"
|
||||
onClick={handleClearFilter}
|
||||
>
|
||||
<KeenIcon icon="arrow-circle-left" />
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button variant="outline" className="h-8 px-3 text-xs" onClick={handleClearFilter}>
|
||||
Clear
|
||||
</Button>
|
||||
<div className="flex gap-2 items-center ml-2 shrink-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 px-3 text-xs"
|
||||
@ -186,36 +201,9 @@ const ListToolbar = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter bar */}
|
||||
<div className="flex flex-wrap gap-2 w-full">
|
||||
{renderSelect(
|
||||
'Select Status Transaction Type',
|
||||
statusTypes,
|
||||
setStatusTypes,
|
||||
types,
|
||||
typeLabelMap
|
||||
)}
|
||||
{renderSelect(
|
||||
'Select Status Approval',
|
||||
approval,
|
||||
setApproval,
|
||||
['Y', 'N'],
|
||||
approvalLabelMap
|
||||
)}
|
||||
{/* {renderSelect('Select Origin Wallet', walletOrigins, setWalletOrigin, origins)}
|
||||
{renderSelect(
|
||||
'Select Destination Wallet',
|
||||
walletDestinations,
|
||||
setWalletDestination,
|
||||
destinations
|
||||
)} */}
|
||||
|
||||
{/* {renderSelect('Origin', walletOrigins, setWalletOrigin, origins)}
|
||||
{renderSelect('Destination', walletDestinations, setWalletDestination, destinations)} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListToolbar;
|
||||
export default ListToolbar;
|
||||
@ -212,7 +212,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Status Transaction Type" column={column} />
|
||||
),
|
||||
enableSorting: true,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: { headerClassName: 'w-[250px]' }
|
||||
},
|
||||
@ -241,7 +241,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Status Kind" column={column} />
|
||||
),
|
||||
enableSorting: true,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: { headerClassName: 'w-[250px]' }
|
||||
},
|
||||
|
||||
@ -19,6 +19,13 @@ interface WalletProps {
|
||||
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();
|
||||
@ -32,12 +39,10 @@ const ListToolbar = () => {
|
||||
);
|
||||
const [wallets, setWallets] = useState<WalletProps[]>([]);
|
||||
|
||||
const formatDate = (date: Date): string => date.toISOString().split('T')[0];
|
||||
|
||||
useEffect(() => {
|
||||
const today = new Date();
|
||||
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
setDateRange({ from: formatDate(firstDayOfMonth), to: formatDate(today) });
|
||||
const threeMonthsAgo = getOneMonthsAgo();
|
||||
setDateRange({ from: formatDate(threeMonthsAgo), to: formatDate(today) });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -45,7 +50,6 @@ const ListToolbar = () => {
|
||||
table.getColumn('msisdn')?.setFilterValue(searchValue);
|
||||
table.setPageIndex(0);
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchValue, table]);
|
||||
|
||||
@ -89,35 +93,50 @@ const ListToolbar = () => {
|
||||
}, []);
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
const today = new Date();
|
||||
const oneMonthAgo = getOneMonthsAgo();
|
||||
const resetDateRange = {
|
||||
from: formatDate(oneMonthAgo),
|
||||
to: formatDate(today)
|
||||
};
|
||||
|
||||
setSearchValue('');
|
||||
setWalletId('');
|
||||
setDateRange(resetDateRange);
|
||||
|
||||
table.getColumn('msisdn')?.setFilterValue('');
|
||||
table.getColumn('id_wallet')?.setFilterValue('');
|
||||
table.getColumn('CreatedAt')?.setFilterValue(resetDateRange);
|
||||
|
||||
setTimeout(() => {
|
||||
table.setPageIndex(0);
|
||||
reload();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
||||
const handleRefresh = () => {
|
||||
const today = new Date();
|
||||
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
setDateRange({
|
||||
from: formatDate(firstDayOfMonth),
|
||||
const threeMonthsAgo = getOneMonthsAgo();
|
||||
const resetDateRange = {
|
||||
from: formatDate(threeMonthsAgo),
|
||||
to: formatDate(today)
|
||||
});
|
||||
};
|
||||
|
||||
table.getAllColumns().forEach((column) => {
|
||||
if (column.id !== 'CreatedAt') {
|
||||
column.setFilterValue(undefined);
|
||||
}
|
||||
});
|
||||
setSearchValue('');
|
||||
setWalletId('');
|
||||
setDateRange(resetDateRange);
|
||||
|
||||
table.setColumnFilters([{ id: 'CreatedAt', value: resetDateRange }]);
|
||||
table.setPageIndex(0);
|
||||
|
||||
table.getColumn('CreatedAt')?.setFilterValue({
|
||||
from: formatDate(firstDayOfMonth),
|
||||
to: formatDate(today)
|
||||
});
|
||||
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 gap-3 items-center flex-wrap">
|
||||
<div className="flex flex-wrap items-end gap-3 w-full">
|
||||
<label className="input input-sm w-[160px]">
|
||||
From
|
||||
<input
|
||||
@ -136,24 +155,20 @@ const ListToolbar = () => {
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="input input-sm w-1/3">
|
||||
<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-[220px]">
|
||||
<Select
|
||||
value={walletId}
|
||||
onValueChange={(value) => {
|
||||
setWalletId(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<div className="w-[160px]">
|
||||
<Select value={walletId} onValueChange={(value) => setWalletId(value)}>
|
||||
<SelectTrigger className="h-[32px]">
|
||||
<SelectValue placeholder="Select Wallet" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -165,25 +180,20 @@ const ListToolbar = () => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" size="sm" onClick={handleClearAllFilters}>
|
||||
Clear Filter
|
||||
</Button>
|
||||
<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={() => {
|
||||
setSearchValue('');
|
||||
setWalletId('');
|
||||
table.setColumnFilters((prev) => prev.filter((f) => f.id === 'CreatedAt'));
|
||||
table.setPageIndex(0);
|
||||
reload();
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" className="h-7.5" onClick={handleRefresh}>
|
||||
<KeenIcon icon="arrows-circle" />
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
@ -194,4 +204,4 @@ const ListToolbar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ListToolbar;
|
||||
export default ListToolbar;
|
||||
Reference in New Issue
Block a user