Merge branch 'master' of https://git.shiblysolution.id/TPAY/dashboard
This commit is contained in:
BIN
public/media/file-templates/upload-batch-template.xlsx
Normal file
BIN
public/media/file-templates/upload-batch-template.xlsx
Normal file
Binary file not shown.
@ -9,6 +9,7 @@ export interface AuthModel {
|
|||||||
company: any;
|
company: any;
|
||||||
user: any;
|
user: any;
|
||||||
// api_token: string;
|
// api_token: string;
|
||||||
|
statusbalance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserModel {
|
export interface UserModel {
|
||||||
|
|||||||
@ -43,24 +43,6 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
const [auth, setAuth] = useState<AuthModel | undefined>(authHelper.getAuth());
|
const [auth, setAuth] = useState<AuthModel | undefined>(authHelper.getAuth());
|
||||||
const [currentUser, setCurrentUser] = useState<UserModel | undefined>();
|
const [currentUser, setCurrentUser] = useState<UserModel | undefined>();
|
||||||
|
|
||||||
const verify = async () => {
|
|
||||||
if (auth) {
|
|
||||||
try {
|
|
||||||
const { data: user } = await getUser();
|
|
||||||
const createCacheUser = {
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
username: user.username,
|
|
||||||
role_name: auth.role_name
|
|
||||||
};
|
|
||||||
localStorage.setItem('user', JSON.stringify(createCacheUser));
|
|
||||||
} catch {
|
|
||||||
saveAuth(undefined);
|
|
||||||
setCurrentUser(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveAuth = (auth: AuthModel | undefined) => {
|
const saveAuth = (auth: AuthModel | undefined) => {
|
||||||
setAuth(auth);
|
setAuth(auth);
|
||||||
if (auth) {
|
if (auth) {
|
||||||
@ -73,11 +55,20 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
const login = async (username: string, password: string) => {
|
const login = async (username: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
const { data: auth } = await axios
|
const { data: auth } = await axios
|
||||||
.post(LOGIN_URL, { username, password }) // , { headers: { 'Access-Control-Allow-Origin': "*" }}
|
.post(LOGIN_URL, { username, password })
|
||||||
.then((response) => response.data);
|
.then((response) => response.data);
|
||||||
|
|
||||||
saveAuth({ ...auth.token, id: auth.user.id, role_name: auth.role_name, user: auth.user });
|
const enhancedAuth: AuthModel = {
|
||||||
|
...auth.token,
|
||||||
|
id: auth.user.id,
|
||||||
|
role_name: auth.role_name,
|
||||||
|
user: auth.user,
|
||||||
|
statusbalance: auth.role?.status_balance ?? null // SAFE ACCESS
|
||||||
|
};
|
||||||
|
|
||||||
|
saveAuth(enhancedAuth);
|
||||||
setCurrentUser(auth.user);
|
setCurrentUser(auth.user);
|
||||||
|
|
||||||
const createActivity = {
|
const createActivity = {
|
||||||
module: 'Login',
|
module: 'Login',
|
||||||
description: `Login`,
|
description: `Login`,
|
||||||
@ -85,6 +76,7 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
};
|
};
|
||||||
doSaveLogActivity(createActivity);
|
doSaveLogActivity(createActivity);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error('Login error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -108,6 +100,33 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
return { data: _axios };
|
return { data: _axios };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verify = async () => {
|
||||||
|
if (auth) {
|
||||||
|
try {
|
||||||
|
const { data: user } = await getUser();
|
||||||
|
|
||||||
|
// Perbarui auth yang sekarang dengan statusbalance
|
||||||
|
saveAuth({
|
||||||
|
...auth,
|
||||||
|
statusbalance: user.role.status_balance
|
||||||
|
});
|
||||||
|
|
||||||
|
const createCacheUser = {
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
username: user.username,
|
||||||
|
role_name: auth.role_name,
|
||||||
|
statusbalance: user.role.status_balance
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem('user', JSON.stringify(createCacheUser));
|
||||||
|
} catch {
|
||||||
|
saveAuth(undefined);
|
||||||
|
setCurrentUser(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
const createActivity = {
|
const createActivity = {
|
||||||
module: 'Logout',
|
module: 'Logout',
|
||||||
@ -130,7 +149,6 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
currentUser,
|
currentUser,
|
||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
login,
|
login,
|
||||||
// register,
|
|
||||||
requestPasswordResetLink,
|
requestPasswordResetLink,
|
||||||
changePassword,
|
changePassword,
|
||||||
getUser,
|
getUser,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { apiConfig } from '@/config/api.config';
|
|||||||
import TransactionValue from './blocks/TransactionValue';
|
import TransactionValue from './blocks/TransactionValue';
|
||||||
import TransactionPieChart from './blocks/TransactionPieChart';
|
import TransactionPieChart from './blocks/TransactionPieChart';
|
||||||
import MemberActivity from './blocks/MemberActivity';
|
import MemberActivity from './blocks/MemberActivity';
|
||||||
|
import BankSaldo from './blocks/BankSaldo';
|
||||||
|
|
||||||
// sum -> nominal, count-> total
|
// sum -> nominal, count-> total
|
||||||
type CountType = 'sum' | 'count';
|
type CountType = 'sum' | 'count';
|
||||||
@ -57,6 +58,8 @@ const DashboardHomePage = () => {
|
|||||||
const API_URL = apiConfig.api_dashboard;
|
const API_URL = apiConfig.api_dashboard;
|
||||||
const { GetData } = useCallApi();
|
const { GetData } = useCallApi();
|
||||||
|
|
||||||
|
const API_URL_BANK = apiConfig.service_wallet;
|
||||||
|
|
||||||
const [responseStatisticCard, setResponseStatisticCard] = useState<any>(null);
|
const [responseStatisticCard, setResponseStatisticCard] = useState<any>(null);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@ -64,10 +67,21 @@ const DashboardHomePage = () => {
|
|||||||
setResponseStatisticCard(res);
|
setResponseStatisticCard(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [bankaccount, setbankaccount] = useState<any>(null);
|
||||||
|
|
||||||
|
const fetchDataBankAccount = async () => {
|
||||||
|
const res = await GetData(`${API_URL_BANK}/dashboard/balance/account/${getAuth()?.user?.customer?.id}`, {});
|
||||||
|
setbankaccount(res);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDataBankAccount();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [responseGraphic, setresponseGraphic] = useState<any>(null);
|
const [responseGraphic, setresponseGraphic] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -253,8 +267,24 @@ const DashboardHomePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<div className="flex space-x-4 mt-5">
|
||||||
|
{bankaccount?.data && bankaccount?.data.length > 0 && getAuth()?.statusbalance=='Y' ? (
|
||||||
|
bankaccount?.data.map((bankaccountdatas: { amount: string, creditlimit: string, monthlylimit: string; wallet: string; }, index: number) => (
|
||||||
|
<BankSaldo
|
||||||
|
title={bankaccountdatas.wallet}
|
||||||
|
balance={bankaccountdatas.amount}
|
||||||
|
creditLimit={bankaccountdatas.creditlimit}
|
||||||
|
monthlyLimit={bankaccountdatas.monthlylimit}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Cards */}
|
{/* Cards */}
|
||||||
<div className="flex gap-6 overflow-x-auto pb-2">
|
<div className="flex gap-6 overflow-x-auto pb-2 mt-5">
|
||||||
<Card
|
<Card
|
||||||
title="Registered Users"
|
title="Registered Users"
|
||||||
total={responseStatisticCard?.data.total_registered ?? 0}
|
total={responseStatisticCard?.data.total_registered ?? 0}
|
||||||
|
|||||||
62
src/pages/dashboards/home/blocks/BankSaldo.tsx
Normal file
62
src/pages/dashboards/home/blocks/BankSaldo.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// BankSaldo.tsx
|
||||||
|
import { Wallet } from "lucide-react";
|
||||||
|
|
||||||
|
interface AccountCardProps {
|
||||||
|
title: string;
|
||||||
|
balance: string;
|
||||||
|
creditLimit: string;
|
||||||
|
monthlyLimit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BankSaldo = ({
|
||||||
|
title,
|
||||||
|
balance,
|
||||||
|
creditLimit,
|
||||||
|
monthlyLimit,
|
||||||
|
}: AccountCardProps) => {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-xs bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
|
||||||
|
<div className="h-1 bg-red-500" />
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h2 className="text-lg font-semibold">{title}</h2>
|
||||||
|
<Wallet className="h-5 w-5 text-gray-500" />
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold text-gray-800">
|
||||||
|
{new Intl.NumberFormat("en-US", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "USD",
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}).format(parseFloat(balance))}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-600">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Credit Limit:</span>
|
||||||
|
<span>{creditLimit}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Monthly Limit:</span>
|
||||||
|
<span>{monthlyLimit}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AccountCardsProps {
|
||||||
|
accounts: AccountCardProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccountCards = ({ accounts }: AccountCardsProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 justify-center">
|
||||||
|
{accounts.map((account, index) => (
|
||||||
|
<BankSaldo key={index} {...account} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BankSaldo
|
||||||
@ -10,12 +10,37 @@ interface Props {
|
|||||||
enddate: string;
|
enddate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const polarToCartesian = (cx: number, cy: number, radius: number, angleInDegrees: number) => {
|
||||||
|
const angleInRadians = (angleInDegrees * Math.PI) / 180.0;
|
||||||
|
return {
|
||||||
|
x: cx + radius * Math.cos(angleInRadians),
|
||||||
|
y: cy + radius * Math.sin(angleInRadians),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const describeArc = (
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
radius: number,
|
||||||
|
startAngle: number,
|
||||||
|
endAngle: number
|
||||||
|
) => {
|
||||||
|
const start = polarToCartesian(x, y, radius, endAngle);
|
||||||
|
const end = polarToCartesian(x, y, radius, startAngle);
|
||||||
|
const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
|
||||||
|
|
||||||
|
return [
|
||||||
|
"M", start.x, start.y,
|
||||||
|
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y,
|
||||||
|
].join(" ");
|
||||||
|
};
|
||||||
|
|
||||||
const MemberActivity = ({ startdate, enddate }: Props) => {
|
const MemberActivity = ({ startdate, enddate }: Props) => {
|
||||||
const { GetData } = useCallApi();
|
const { GetData } = useCallApi();
|
||||||
const [responseTransactionValue, setResponseTransactionValue] = useState<any>(null);
|
const [responseTransactionValue, setResponseTransactionValue] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchDataTransactionValue = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await GetData(`${API_URL}/active-user`, {
|
const res = await GetData(`${API_URL}/active-user`, {
|
||||||
date_from: startdate,
|
date_from: startdate,
|
||||||
@ -23,43 +48,33 @@ const MemberActivity = ({ startdate, enddate }: Props) => {
|
|||||||
});
|
});
|
||||||
setResponseTransactionValue(res);
|
setResponseTransactionValue(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching transaction value:', error);
|
console.error('Error fetching data:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchDataTransactionValue();
|
fetchData();
|
||||||
}, [startdate, enddate, GetData]);
|
}, [startdate, enddate, GetData]);
|
||||||
|
|
||||||
const percentage = parseFloat((responseTransactionValue?.data?.total_customer_active_percentage ?? 0).toFixed(2)) ?? 0;
|
const percentage = parseFloat((responseTransactionValue?.data?.total_customer_active_percentage ?? 0).toFixed(2));
|
||||||
const totalCustomer = responseTransactionValue?.data?.total_customer ?? 0;
|
const totalCustomer = responseTransactionValue?.data?.total_customer ?? 0;
|
||||||
const activeCustomer = responseTransactionValue?.data?.total_customer_active ?? 0;
|
const activeCustomer = responseTransactionValue?.data?.total_customer_active ?? 0;
|
||||||
const reguler = responseTransactionValue?.data?.reguler ?? 0;
|
const reguler = responseTransactionValue?.data?.reguler ?? 0;
|
||||||
const premium = responseTransactionValue?.data?.premium ?? 0;
|
const premium = responseTransactionValue?.data?.premium ?? 0;
|
||||||
const agent = responseTransactionValue?.data?.agent ?? 0;
|
const agent = responseTransactionValue?.data?.agent ?? 0;
|
||||||
|
|
||||||
// Hitung sudut pointer
|
// Calculate pointer angle
|
||||||
const angle = (percentage / 100) * 180; // 0° (kiri) ke 180° (kanan)
|
const angle = (percentage / 100) * 180;
|
||||||
const radians = (angle * Math.PI) / 180;
|
const pointerX = 50 + 25 * Math.cos((angle - 180) * Math.PI / 180);
|
||||||
const radius = 40; // Radius dari setengah lingkaran
|
const pointerY = 50 + 25 * Math.sin((angle - 180) * Math.PI / 180);
|
||||||
const center = 50; // Titik pusat lingkaran
|
|
||||||
const pointerLength = 25; // Panjang pointer
|
|
||||||
|
|
||||||
const x = center + pointerLength * Math.cos(radians - Math.PI); // offset agar mulai dari kiri
|
|
||||||
const y = center + pointerLength * Math.sin(radians - Math.PI);
|
|
||||||
|
|
||||||
// Hitung titik akhir untuk arc aktif
|
|
||||||
const arcAngle = (Math.PI * percentage) / 100;
|
|
||||||
const arcX = 50 + radius * Math.cos(Math.PI - arcAngle);
|
|
||||||
const arcY = 50 - radius * Math.sin(arcAngle);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 bg-white rounded-lg shadow-md w-1/3">
|
<div className="p-6 bg-white rounded-lg shadow-md w-full max-w-md">
|
||||||
<div className="flex justify-between items-center pb-3 mb-4">
|
<div className="pb-3 mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-700">Member Activity</h2>
|
<h2 className="text-lg font-semibold text-gray-700">Member Activity</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
{/* Sidebar */}
|
{/* Sidebar Info */}
|
||||||
<ul className="space-y-4 w-1/2 text-gray-700 text-sm">
|
<ul className="space-y-4 w-1/2 text-gray-700 text-sm">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<UsersIcon className="w-4 h-4" />
|
<UsersIcon className="w-4 h-4" />
|
||||||
@ -88,7 +103,7 @@ const MemberActivity = ({ startdate, enddate }: Props) => {
|
|||||||
<div className="border border-gray-200 rounded-md p-4 text-center">
|
<div className="border border-gray-200 rounded-md p-4 text-center">
|
||||||
<h3 className="text-sm text-gray-600 font-medium mb-2">Active User</h3>
|
<h3 className="text-sm text-gray-600 font-medium mb-2">Active User</h3>
|
||||||
<div className="relative h-24 w-full">
|
<div className="relative h-24 w-full">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 50">
|
<svg viewBox="0 0 100 50" className="w-full h-full">
|
||||||
{/* Background arc */}
|
{/* Background arc */}
|
||||||
<path
|
<path
|
||||||
d="M 10 50 A 40 40 0 0 1 90 50"
|
d="M 10 50 A 40 40 0 0 1 90 50"
|
||||||
@ -97,24 +112,27 @@ const MemberActivity = ({ startdate, enddate }: Props) => {
|
|||||||
strokeWidth="10"
|
strokeWidth="10"
|
||||||
/>
|
/>
|
||||||
{/* Active arc */}
|
{/* Active arc */}
|
||||||
|
{percentage > 0 && (
|
||||||
<path
|
<path
|
||||||
d={`M 10 50 A 40 40 0 ${percentage > 50 ? 1 : 0} 1 ${arcX} ${arcY}`}
|
d={describeArc(50, 50, 40, 180, 180 + (percentage * 180 / 100))}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#34d399"
|
stroke="#34d399"
|
||||||
strokeWidth="10"
|
strokeWidth="10"
|
||||||
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
{/* Pointer */}
|
{/* Pointer */}
|
||||||
<line
|
<line
|
||||||
x1="50"
|
x1="50"
|
||||||
y1="50"
|
y1="50"
|
||||||
x2={x}
|
x2={pointerX}
|
||||||
y2={y}
|
y2={pointerY}
|
||||||
stroke="#111827"
|
stroke="#111827"
|
||||||
strokeWidth="4"
|
strokeWidth="4"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="flex justify-between text-xs text-gray-500 px-1">
|
<div className="flex justify-between text-xs text-gray-500 px-1 mt-1">
|
||||||
<span>{percentage}%</span>
|
<span>{percentage}%</span>
|
||||||
<span>100%</span>
|
<span>100%</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -43,7 +43,11 @@ const TransactionPieChart = ({ startdate, enddate }: Props) => {
|
|||||||
{ name: 'Purchase', value: parseFloat((responseTransactionValue?.data?.P ?? 0).toFixed(2)), color: '#baf7c5' },
|
{ name: 'Purchase', value: parseFloat((responseTransactionValue?.data?.P ?? 0).toFixed(2)), color: '#baf7c5' },
|
||||||
{ name: 'Withdraw', value: parseFloat((responseTransactionValue?.data?.W ?? 0).toFixed(2)), color: '#f56565' },
|
{ name: 'Withdraw', value: parseFloat((responseTransactionValue?.data?.W ?? 0).toFixed(2)), color: '#f56565' },
|
||||||
{ name: 'Top Up Partner', value: parseFloat((responseTransactionValue?.data?.N ?? 0).toFixed(2)), color: '#f7fa52' },
|
{ name: 'Top Up Partner', value: parseFloat((responseTransactionValue?.data?.N ?? 0).toFixed(2)), color: '#f7fa52' },
|
||||||
{ name: 'Reward', value: parseFloat((responseTransactionValue?.data?.E ?? 0).toFixed(2)), color: '#f7fa52' },
|
{ name: 'Reward', value: parseFloat((responseTransactionValue?.data?.E ?? 0).toFixed(2)), color: '#f50a19' },
|
||||||
|
{ name: 'Purchase Loja', value: parseFloat((responseTransactionValue?.data?.L ?? 0).toFixed(2)), color: '#F0A04B' },
|
||||||
|
{ name: 'Top Up P24', value: parseFloat((responseTransactionValue?.data?.B ?? 0).toFixed(2)), color: '#FADA7A' },
|
||||||
|
{ name: 'Transfer Agent', value: parseFloat((responseTransactionValue?.data?.A ?? 0).toFixed(2)), color: '#B1C29E' },
|
||||||
|
{ name: 'Withdrawal Agent', value: parseFloat((responseTransactionValue?.data?.M ?? 0).toFixed(2)), color: '#FCE7C8' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -67,6 +67,18 @@ const TransactionValue = ({ startdate, enddate }: Props) => {
|
|||||||
case "E":
|
case "E":
|
||||||
type = "Reward";
|
type = "Reward";
|
||||||
break;
|
break;
|
||||||
|
case "L":
|
||||||
|
type = "Purchase Loja"
|
||||||
|
break;
|
||||||
|
case "B":
|
||||||
|
type ="Top Up P24";
|
||||||
|
break;
|
||||||
|
case "A":
|
||||||
|
type = " Transfer Agent";
|
||||||
|
break;
|
||||||
|
case "M":
|
||||||
|
type = "Withdrawal Agent";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
type = "Unknown";
|
type = "Unknown";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -5,15 +5,15 @@ import { useCallApi } from '@/hooks';
|
|||||||
import { apiConfig } from '@/config/api.config';
|
import { apiConfig } from '@/config/api.config';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { getAuth } from '@/auth';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogBody,
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import TransactionLogViewer from './DetailTransactionLog';
|
|
||||||
|
|
||||||
const API_URL = apiConfig.service_disbursement;
|
const API_URL = apiConfig.service_disbursement;
|
||||||
|
|
||||||
@ -57,6 +57,7 @@ const DetailTransaction = () => {
|
|||||||
|
|
||||||
const [transactionDetails, setTransactionDetails] = useState<any>(null);
|
const [transactionDetails, setTransactionDetails] = useState<any>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTransactionDetails = async () => {
|
const fetchTransactionDetails = async () => {
|
||||||
@ -69,7 +70,6 @@ const DetailTransaction = () => {
|
|||||||
id: selectedTransactionId
|
id: selectedTransactionId
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// console.log(response?.data);
|
|
||||||
setTransactionDetails(response?.data);
|
setTransactionDetails(response?.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching transaction', error);
|
console.error('Error fetching transaction', error);
|
||||||
@ -84,8 +84,6 @@ const DetailTransaction = () => {
|
|||||||
}
|
}
|
||||||
}, [showDetailDialog, selectedTransactionId, GetData]);
|
}, [showDetailDialog, selectedTransactionId, GetData]);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState('detail'); // 'detail', 'log', 'approve'
|
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setTransactionDetails(null);
|
setTransactionDetails(null);
|
||||||
};
|
};
|
||||||
@ -96,16 +94,126 @@ const DetailTransaction = () => {
|
|||||||
}
|
}
|
||||||
}, [showDetailDialog]);
|
}, [showDetailDialog]);
|
||||||
|
|
||||||
|
const handleExport = async () => {
|
||||||
|
if (!selectedTransactionId || !transactionDetails) return;
|
||||||
|
|
||||||
|
setIsExporting(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_URL}/transaction/export?id_disbursment=${selectedTransactionId}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${getAuth()?.access_token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch file');
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const contentDisposition = response.headers.get('content-disposition');
|
||||||
|
|
||||||
|
const executionDate = transactionDetails.execution_date;
|
||||||
|
const formattedDate = executionDate
|
||||||
|
? moment(executionDate).format('YYYYMMDD_HHmm')
|
||||||
|
: moment().format('YYYYMMDD_HHmm');
|
||||||
|
|
||||||
|
let filename = `transaction_${formattedDate}.xlsx`;
|
||||||
|
|
||||||
|
if (contentDisposition) {
|
||||||
|
const filenameMatch = contentDisposition.match(/filename\*?=(?:UTF-8'')?"?([^;"\n]*)"?/);
|
||||||
|
if (filenameMatch && filenameMatch[1]) {
|
||||||
|
filename = decodeURIComponent(filenameMatch[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
toast.success('Export successful');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting transaction:', error);
|
||||||
|
toast.error('Failed to export transaction');
|
||||||
|
} finally {
|
||||||
|
setIsExporting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
||||||
<DialogContent className="container-fixed max-w-[1024px] flex flex-col p-5 overflow-hidden">
|
<DialogContent className="container-fixed max-w-[1280px] w-full h-[90vh] flex flex-col p-6 overflow-hidden">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Transaction Details</DialogTitle>
|
<DialogTitle className="text-2xl font-bold">Transaction Details</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
{/* Tab Content */}
|
<div className="py-4 overflow-y-auto max-h-[600px] space-y-6">
|
||||||
<div className="py-4 overflow-y-auto max-h-[400px]">
|
{/* Summary Info */}
|
||||||
<div className="space-y-4">
|
{transactionDetails && (
|
||||||
|
<div className="grid grid-cols-2 gap-y-4 gap-x-6 border rounded-lg p-4 bg-gray-50">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Filename Upload</p>
|
||||||
|
<p className="text-base text-gray-800">{transactionDetails.file_name ?? '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Total Amount</p>
|
||||||
|
<p className="text-base text-gray-800">{transactionDetails.amount ?? '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Total Record</p>
|
||||||
|
<p className="text-base text-gray-800">
|
||||||
|
{transactionDetails.total_record ?? '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Success Record</p>
|
||||||
|
<p className="text-base text-gray-800">
|
||||||
|
{transactionDetails.total_success ?? '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Fail Record</p>
|
||||||
|
<p className="text-base text-gray-800">{transactionDetails.total_fail ?? '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Pending Record</p>
|
||||||
|
<p className="text-base text-gray-800">
|
||||||
|
{transactionDetails.total_pending ?? '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Status</p>
|
||||||
|
<p className="text-base text-gray-800">
|
||||||
|
{renderStatusBadge(transactionDetails.status)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Execution Date</p>
|
||||||
|
<p className="text-base text-gray-800">
|
||||||
|
{transactionDetails.execution_date
|
||||||
|
? moment(transactionDetails.execution_date).format('DD/MM/YYYY HH:mm')
|
||||||
|
: '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Done Date</p>
|
||||||
|
<p className="text-base text-gray-800">
|
||||||
|
{transactionDetails.done_date
|
||||||
|
? moment(transactionDetails.done_date).format('DD/MM/YYYY HH:mm')
|
||||||
|
: '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Log Table */}
|
||||||
<div className="border rounded-lg overflow-x-auto">
|
<div className="border rounded-lg overflow-x-auto">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex flex-col items-center justify-center p-8">
|
<div className="flex flex-col items-center justify-center p-8">
|
||||||
@ -129,28 +237,16 @@ const DetailTransaction = () => {
|
|||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Amount</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Amount</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Process Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Process Date</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Invoice Number</th>
|
||||||
Invoice Number
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Remark 1</th>
|
||||||
</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Remark 2</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Remark 3</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Actions</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{transactionDetails?.log && transactionDetails.log.length > 0 ? (
|
{transactionDetails?.log && transactionDetails.log.length > 0 ? (
|
||||||
transactionDetails.log.map(
|
transactionDetails.log.map((log: any, index: number) => (
|
||||||
(
|
|
||||||
log: {
|
|
||||||
id: number;
|
|
||||||
customer: any;
|
|
||||||
amount: number;
|
|
||||||
remark: string;
|
|
||||||
reference: string;
|
|
||||||
request_date: string;
|
|
||||||
payment_response: string;
|
|
||||||
status: string;
|
|
||||||
},
|
|
||||||
index: number
|
|
||||||
) => (
|
|
||||||
<tr key={index} className="border-t">
|
<tr key={index} className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
{log.customer?.username ?? 'Not Found'}
|
{log.customer?.username ?? 'Not Found'}
|
||||||
@ -158,22 +254,22 @@ const DetailTransaction = () => {
|
|||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
{log.customer?.fullname ?? 'Not Found'}
|
{log.customer?.fullname ?? 'Not Found'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">{log.amount ?? '-'}</td>
|
||||||
{log.amount ?? '-'}
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
{renderStatusBadge(log.status) ?? '-'}
|
{renderStatusBadge(log.status) ?? '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
{log.request_date && moment(log.request_date).isValid()
|
{log.request_date && moment(log.request_date).isValid()
|
||||||
? moment(log.request_date).format('DD/MM/YYYY HH:mm:ss')
|
? moment(log.request_date).format('DD/MM/YYYY HH:mm')
|
||||||
: '-'}
|
: '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
{log.reference ?? '-'}
|
{log.reference ?? '-'}
|
||||||
</td>
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.remark_1 ?? '-'}</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.remark_2 ?? '-'}</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.remark_3 ?? '-'}</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
<div key={`actions-${log.id}`}>
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-icon btn-clear btn-light"
|
className="btn btn-sm btn-icon btn-clear btn-light"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -183,14 +279,12 @@ const DetailTransaction = () => {
|
|||||||
>
|
>
|
||||||
<KeenIcon icon="eye" />
|
<KeenIcon icon="eye" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
))
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8} className="px-4 py-2 text-center text-sm text-gray-500">
|
<td colSpan={10} className="px-4 py-2 text-center text-sm text-gray-500">
|
||||||
No logs available
|
No logs available
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -199,6 +293,31 @@ const DetailTransaction = () => {
|
|||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Export Button - Moved to bottom right after table */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
className={`px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center ${isExporting ? 'opacity-50 pointer-events-none' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isExporting) {
|
||||||
|
handleExport();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isExporting ? (
|
||||||
|
<>
|
||||||
|
<span className="animate-spin mr-2">
|
||||||
|
<KeenIcon icon="spinner" />
|
||||||
|
</span>
|
||||||
|
Exporting...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<KeenIcon icon="download" className="mr-2" />
|
||||||
|
Export
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
|
|||||||
@ -1,21 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useTransactionContext } from '../hooks/useTransactionContext';
|
import { useTransactionContext } from '../hooks/useTransactionContext';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody } from '@/components/ui/dialog';
|
import {
|
||||||
|
Dialog,
|
||||||
interface LogType {
|
DialogContent,
|
||||||
id: string;
|
DialogHeader,
|
||||||
amount: number;
|
DialogTitle,
|
||||||
remark: string;
|
DialogBody
|
||||||
reference: string;
|
} from '@/components/ui/dialog';
|
||||||
response_date: string;
|
|
||||||
status: string;
|
|
||||||
customer?: {
|
|
||||||
username: string;
|
|
||||||
fullname: string;
|
|
||||||
};
|
|
||||||
payment_response?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusCode = 'W' | 'O' | 'F' | 'D';
|
type StatusCode = 'W' | 'O' | 'F' | 'D';
|
||||||
|
|
||||||
@ -29,7 +21,7 @@ const statusMap: Record<StatusCode, StatusInfo> = {
|
|||||||
W: { label: 'Waiting Schedule', bg: 'bg-yellow-100', text: 'text-yellow-600' },
|
W: { label: 'Waiting Schedule', bg: 'bg-yellow-100', text: 'text-yellow-600' },
|
||||||
O: { label: 'On Process', bg: 'bg-blue-100', text: 'text-blue-600' },
|
O: { label: 'On Process', bg: 'bg-blue-100', text: 'text-blue-600' },
|
||||||
F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' },
|
F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' },
|
||||||
D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' },
|
D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderStatusBadge = (statusRaw: string | null | undefined) => {
|
export const renderStatusBadge = (statusRaw: string | null | undefined) => {
|
||||||
@ -37,22 +29,16 @@ export const renderStatusBadge = (statusRaw: string | null | undefined) => {
|
|||||||
const { label, bg, text } = statusMap[status] ?? {
|
const { label, bg, text } = statusMap[status] ?? {
|
||||||
label: 'Unknown',
|
label: 'Unknown',
|
||||||
bg: 'bg-gray-100',
|
bg: 'bg-gray-100',
|
||||||
text: 'text-gray-600',
|
text: 'text-gray-600'
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`px-2 py-1 text-xs font-semibold rounded-full ${bg} ${text}`}>
|
<span className={`px-2 py-1 text-xs font-semibold rounded-full ${bg} ${text}`}>{label}</span>
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TransactionLogViewer = () => {
|
const TransactionLogViewer = () => {
|
||||||
const {
|
const { showDetailLogDialog, setShowDetailLogDialog, detailLogData } = useTransactionContext();
|
||||||
showDetailLogDialog,
|
|
||||||
setShowDetailLogDialog,
|
|
||||||
detailLogData
|
|
||||||
} = useTransactionContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showDetailLogDialog} onOpenChange={setShowDetailLogDialog}>
|
<Dialog open={showDetailLogDialog} onOpenChange={setShowDetailLogDialog}>
|
||||||
@ -60,64 +46,144 @@ const TransactionLogViewer = () => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Detail Record</DialogTitle>
|
<DialogTitle>Detail Record</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogBody >
|
<DialogBody className="overflow-y-auto max-h-[70vh] scroll-smooth scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100">
|
||||||
{/* Tab Content */}
|
{detailLogData ? (
|
||||||
{detailLogData && detailLogData != null ? (
|
|
||||||
<div className="py-4 overflow-y-auto">
|
<div className="py-4 overflow-y-auto">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="border rounded-lg overflow-x-auto">
|
<div className="border rounded-lg overflow-x-auto">
|
||||||
<table className='min-w-full table-auto'>
|
<table className="min-w-full table-auto">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">ID</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Log ID</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.customer.id}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">{detailLogData.id}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
{detailLogData.customer && (
|
||||||
|
<>
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Customer ID</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.customer.id}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Username</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Username</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.customer.username}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.customer.username}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Name</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Fullname</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.customer.fullname}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.customer.fullname}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
{detailLogData.customer.email && (
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Email</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.customer.email}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
{detailLogData.customer.bank_name && (
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Bank Name</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.customer.bank_name}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
{detailLogData.customer.bank_account && (
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Bank Account</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.customer.bank_account}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Amount</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Amount</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.amount}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">{detailLogData.amount}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Remark</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Remark</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.remark}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">{detailLogData.remark}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Payment Request</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Remark 1</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500"><pre className="whitespace-pre-wrap break-words max-w-full overflow-auto bg-gray-100 p-2 rounded text-sm">{detailLogData.payment_request}</pre></td>
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.remark_1}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Payment Response</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Remark 2</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500"><pre className="whitespace-pre-wrap break-words max-w-full overflow-auto bg-gray-100 p-2 rounded text-sm">{detailLogData.payment_response}</pre></td>
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.remark_2}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Status</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Remark 3</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{renderStatusBadge(detailLogData.status)}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.remark_3}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='border-t'>
|
<tr className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Prosess Date</td>
|
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.request_date && moment(detailLogData.request_date).isValid() ? moment(detailLogData.request_date).format('DD/MM/YYYY HH:mm:ss') : '-'}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className='border-t'>
|
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Response Date</td>
|
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.response_date && moment(detailLogData.response_date).isValid() ? moment(detailLogData.response_date).format('DD/MM/YYYY HH:mm:ss') : '-'}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className='border-t'>
|
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">Reference Number</td>
|
<td className="px-4 py-2 text-sm text-gray-500">Reference Number</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{detailLogData.reference}</td>
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.reference ?? '-'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Payment Request</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
<pre className="whitespace-pre-wrap break-words max-w-full overflow-auto bg-gray-100 p-2 rounded text-sm">
|
||||||
|
{detailLogData.payment_request ?? '-'}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Payment Response</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
<pre className="whitespace-pre-wrap break-words max-w-full overflow-auto bg-gray-100 p-2 rounded text-sm">
|
||||||
|
{detailLogData.payment_response ?? '-'}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Status</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{renderStatusBadge(detailLogData.status)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Request Date</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.request_date &&
|
||||||
|
moment(detailLogData.request_date).isValid()
|
||||||
|
? moment(detailLogData.request_date).format('DD/MM/YYYY HH:mm:ss')
|
||||||
|
: '-'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-t">
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">Response Date</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-800">
|
||||||
|
{detailLogData.response_date &&
|
||||||
|
moment(detailLogData.response_date).isValid()
|
||||||
|
? moment(detailLogData.response_date).format('DD/MM/YYYY HH:mm:ss')
|
||||||
|
: '-'}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (<div></div>)}
|
) : (
|
||||||
|
<div className="text-center text-sm text-gray-400 py-10">No data available</div>
|
||||||
|
)}
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -9,23 +9,20 @@ const ListToolbar = () => {
|
|||||||
const { table, reload } = useDataGrid();
|
const { table, reload } = useDataGrid();
|
||||||
const { handleUploadBatchDialog } = useTransactionContext();
|
const { handleUploadBatchDialog } = useTransactionContext();
|
||||||
|
|
||||||
// Set the initial state for trxDate
|
|
||||||
const [trxDate, settrxDate] = useState({ from: '', to: '' });
|
const [trxDate, settrxDate] = useState({ from: '', to: '' });
|
||||||
|
|
||||||
// Function to format date to YYYY-MM-DD
|
|
||||||
const formatDate = (date: Date): string => {
|
const formatDate = (date: Date): string => {
|
||||||
return date.toISOString().split('T')[0];
|
return date.toISOString().split('T')[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
// useEffect to set the default date values
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const nextWeek = new Date(today);
|
const nextWeek = new Date(today);
|
||||||
nextWeek.setDate(today.getDate() + 7);
|
nextWeek.setDate(today.getDate() + 7);
|
||||||
|
|
||||||
settrxDate({
|
settrxDate({
|
||||||
from: formatDate(today), // Set 'from' to today
|
from: formatDate(today),
|
||||||
to: formatDate(nextWeek) // Set 'to' to 7 days later
|
to: formatDate(nextWeek)
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -40,7 +37,7 @@ const ListToolbar = () => {
|
|||||||
|
|
||||||
const handleExportData = () => {
|
const handleExportData = () => {
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = toAbsoluteUrl('/media/file-templates/batch-sample.xlsx');
|
link.href = toAbsoluteUrl('/media/file-templates/upload-batch-template.xlsx');
|
||||||
link.download = 'upload-batch-template.xlsx';
|
link.download = 'upload-batch-template.xlsx';
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
|
|||||||
@ -27,23 +27,28 @@ import clsx from 'clsx';
|
|||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
|
|
||||||
const API_URL = apiConfig.service_disbursement;
|
const API_URL = apiConfig.service_disbursement;
|
||||||
|
const API_URL_TRANSACTION = apiConfig.service_transaction;
|
||||||
|
interface TransferType {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
const UploadBatchDialog = () => {
|
const UploadBatchDialog = () => {
|
||||||
const parentRef = useRef<any | null>(null);
|
const parentRef = useRef<any | null>(null);
|
||||||
const { showUploadBatchDialog, handleUploadBatchDialog } = useTransactionContext();
|
const { showUploadBatchDialog, handleUploadBatchDialog } = useTransactionContext();
|
||||||
const { reload } = useDataGrid();
|
const { reload } = useDataGrid();
|
||||||
const { PostData, PostDataFile, GetData } = useCallApi();
|
const { PostDataFile, GetData } = useCallApi();
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [alert, setAlert] = useState({
|
const [alert, setAlert] = useState({ show: false, message: '' });
|
||||||
show: false,
|
|
||||||
message: ''
|
const [transferTypes, setTransferTypes] = useState<TransferType[]>([]);
|
||||||
});
|
|
||||||
const initialState: {
|
const initialState = {
|
||||||
execution_date: string;
|
|
||||||
file: File | null;
|
|
||||||
} = {
|
|
||||||
execution_date: '',
|
execution_date: '',
|
||||||
file: null
|
file: null as File | null,
|
||||||
|
id_transaction_type: ''
|
||||||
};
|
};
|
||||||
const [formField, setFormField] = useState(initialState);
|
const [formField, setFormField] = useState(initialState);
|
||||||
|
|
||||||
@ -52,14 +57,17 @@ const UploadBatchDialog = () => {
|
|||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/* actions */
|
|
||||||
const doUploadBatch = useCallback(
|
const doUploadBatch = useCallback(
|
||||||
async (e: React.FormEvent<HTMLFormElement>) => {
|
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('execution_date', formField.execution_date);
|
const localDate = new Date(formField.execution_date);
|
||||||
|
const newDate = localDate.toISOString();
|
||||||
|
|
||||||
|
formData.append('execution_date', newDate);
|
||||||
|
formData.append('id_transaction_type', formField.id_transaction_type);
|
||||||
|
|
||||||
if (formField.file) {
|
if (formField.file) {
|
||||||
formData.append('file', formField.file);
|
formData.append('file', formField.file);
|
||||||
@ -67,9 +75,7 @@ const UploadBatchDialog = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await PostDataFile(`${API_URL}/upload-excel`, formData, {
|
const response = await PostDataFile(`${API_URL}/upload-excel`, formData, {
|
||||||
headers: {
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response?.status) {
|
if (response?.status) {
|
||||||
@ -102,26 +108,48 @@ const UploadBatchDialog = () => {
|
|||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
console.log('Form data before submit:', formField);
|
if (
|
||||||
|
formField.execution_date.trim() === '' ||
|
||||||
if (formField.execution_date.trim() === '' || formField.file === null) {
|
formField.file === null ||
|
||||||
|
formField.id_transaction_type.trim() === ''
|
||||||
|
) {
|
||||||
setAlert({ show: true, message: 'Please fill in all required fields.' });
|
setAlert({ show: true, message: 'Please fill in all required fields.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
doUploadBatch(e);
|
doUploadBatch(e);
|
||||||
// console.log(formField);
|
|
||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showUploadBatchDialog === false) {
|
if (showUploadBatchDialog === false) {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchTransferTypes = async () => {
|
||||||
|
try {
|
||||||
|
const response = await GetData(`${API_URL_TRANSACTION}/transactiontype/list`, {
|
||||||
|
limit: 100,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'id',
|
||||||
|
order_direction: 'ASC',
|
||||||
|
filter: JSON.stringify({ type: "D" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const records: TransferType[] = response?.data?.list ?? [];
|
||||||
|
setTransferTypes(records);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch transfer types', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTransferTypes();
|
||||||
}, [showUploadBatchDialog]);
|
}, [showUploadBatchDialog]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showUploadBatchDialog} onOpenChange={(open) => handleUploadBatchDialog(open)}>
|
<Dialog open={showUploadBatchDialog} onOpenChange={handleUploadBatchDialog}>
|
||||||
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
||||||
<DialogHeader className="p-0 border-0">
|
<DialogHeader className="p-0 border-0">
|
||||||
<DialogTitle></DialogTitle>
|
<DialogTitle></DialogTitle>
|
||||||
@ -129,7 +157,6 @@ const UploadBatchDialog = () => {
|
|||||||
<div className="flex items-center justify-between flex-wrap grow">
|
<div className="flex items-center justify-between flex-wrap grow">
|
||||||
<div className="flex flex-col justify-center">
|
<div className="flex flex-col justify-center">
|
||||||
<h1 className="text-xl font-semibold leading-none text-gray-900">Upload Batch</h1>
|
<h1 className="text-xl font-semibold leading-none text-gray-900">Upload Batch</h1>
|
||||||
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer hover:opacity-100 opacity-50"
|
className="cursor-pointer hover:opacity-100 opacity-50"
|
||||||
@ -149,7 +176,7 @@ const UploadBatchDialog = () => {
|
|||||||
<h3>{alert.message}</h3>
|
<h3>{alert.message}</h3>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<form action="" onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="card-body grid gap-5 p-0">
|
<div className="card-body grid gap-5 p-0">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||||
@ -182,6 +209,31 @@ const UploadBatchDialog = () => {
|
|||||||
/>
|
/>
|
||||||
</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">
|
||||||
|
Transfer Type
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
required
|
||||||
|
value={formField.id_transaction_type}
|
||||||
|
onValueChange={(val) =>
|
||||||
|
setFormField((prev) => ({ ...prev, id_transaction_type: val }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="Select Transfer Type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{transferTypes.map((item) => (
|
||||||
|
<SelectItem key={item.id} value={item.id}>
|
||||||
|
{item.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex justify-end pt-2.5">
|
<div className="flex justify-end pt-2.5">
|
||||||
<Button variant="default" type="submit" disabled={isSubmitting}>
|
<Button variant="default" type="submit" disabled={isSubmitting}>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
|
|||||||
@ -36,7 +36,7 @@ interface ContextProps {
|
|||||||
// handleDetailLogDialog: (show: boolean) => void;
|
// handleDetailLogDialog: (show: boolean) => void;
|
||||||
setShowDetailLogDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowDetailLogDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setDetailLogData: React.Dispatch<React.SetStateAction<any | null>>;
|
setDetailLogData: React.Dispatch<React.SetStateAction<any | null>>;
|
||||||
detailLogData: any | null
|
detailLogData: any | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialProps: ContextProps = {
|
const initialProps: ContextProps = {
|
||||||
@ -51,7 +51,7 @@ const initialProps: ContextProps = {
|
|||||||
// handleDetailLogDialog: (show: boolean) => {},
|
// handleDetailLogDialog: (show: boolean) => {},
|
||||||
setShowDetailLogDialog: () => {},
|
setShowDetailLogDialog: () => {},
|
||||||
setDetailLogData: () => {},
|
setDetailLogData: () => {},
|
||||||
detailLogData: null,
|
detailLogData: null
|
||||||
};
|
};
|
||||||
|
|
||||||
type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y';
|
type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y';
|
||||||
@ -67,10 +67,9 @@ const statusMap: Record<StatusCode, StatusInfo> = {
|
|||||||
P: { label: 'Pending', bg: 'bg-blue-100', text: 'text-blue-600' },
|
P: { label: 'Pending', bg: 'bg-blue-100', text: 'text-blue-600' },
|
||||||
F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' },
|
F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' },
|
||||||
D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' },
|
D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' },
|
||||||
Y: { label: 'Active', bg: 'bg-green-100', text: 'text-green-600' },
|
Y: { label: 'Active', bg: 'bg-green-100', text: 'text-green-600' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const ManageTransactionContext = createContext<ContextProps>(initialProps);
|
const ManageTransactionContext = createContext<ContextProps>(initialProps);
|
||||||
const API_URL = apiConfig.service_disbursement;
|
const API_URL = apiConfig.service_disbursement;
|
||||||
|
|
||||||
@ -100,19 +99,21 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[250px]'
|
headerClassName: 'w-[250px]'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => {
|
accessorFn: (row) => {
|
||||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(row.amount);
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
|
||||||
|
row.amount
|
||||||
|
);
|
||||||
},
|
},
|
||||||
id: 'amount',
|
id: 'amount',
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Amount" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Amount" column={column} />,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[250px]',
|
headerClassName: 'w-[250px]'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'total_record',
|
accessorKey: 'total_record',
|
||||||
@ -121,7 +122,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[250px]'
|
headerClassName: 'w-[250px]'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'total_success',
|
accessorKey: 'total_success',
|
||||||
@ -130,7 +131,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[250px]'
|
headerClassName: 'w-[250px]'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'total_fail',
|
accessorKey: 'total_fail',
|
||||||
@ -139,7 +140,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[250px]'
|
headerClassName: 'w-[250px]'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'total_pending',
|
accessorKey: 'total_pending',
|
||||||
@ -148,7 +149,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[250px]'
|
headerClassName: 'w-[250px]'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.status,
|
accessorFn: (row) => row.status,
|
||||||
@ -161,21 +162,19 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const { label, bg, text } = statusMap[status] ?? {
|
const { label, bg, text } = statusMap[status] ?? {
|
||||||
label: 'Unknown',
|
label: 'Unknown',
|
||||||
bg: 'bg-gray-100',
|
bg: 'bg-gray-100',
|
||||||
text: 'text-gray-600',
|
text: 'text-gray-600'
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span className={`px-2 py-1 text-xs font-semibold rounded-full ${bg} ${text}`}>
|
||||||
className={`px-2 py-1 text-xs font-semibold rounded-full ${bg} ${text}`}
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
headerClassName: 'w-[100px]',
|
headerClassName: 'w-[100px]',
|
||||||
cellClassName: 'text-center',
|
cellClassName: 'text-center'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.execution_date,
|
accessorFn: (row) => row.execution_date,
|
||||||
@ -183,7 +182,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
header: ({ column }) => <DataGridColumnHeader title="Execution Date" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Execution Date" column={column} />,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => moment(row.original.execution_date).format('DD/MM/YYYY HH:mm:ss')
|
cell: ({ row }) => moment(row.original.execution_date).format('DD/MM/YYYY HH:mm')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.done_date,
|
accessorFn: (row) => row.done_date,
|
||||||
@ -191,7 +190,8 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
header: ({ column }) => <DataGridColumnHeader title="Done Date" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Done Date" column={column} />,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => row.original.done_date ? moment(row.original.done_date).format('DD/MM/YYYY HH:mm:ss') : ''
|
cell: ({ row }) =>
|
||||||
|
row.original.done_date ? moment(row.original.done_date).format('DD/MM/YYYY HH:mm') : ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
@ -220,7 +220,8 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[]);
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const getTransactionLists = async (page: number, limit: number, sorting: any, filter: any) => {
|
const getTransactionLists = async (page: number, limit: number, sorting: any, filter: any) => {
|
||||||
try {
|
try {
|
||||||
@ -240,15 +241,13 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
enddate = filter[0].value.to;
|
enddate = filter[0].value.to;
|
||||||
}
|
}
|
||||||
|
|
||||||
formattedFilter = {
|
formattedFilter = {};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await GetData(`${API_URL}/transaction/history`, {
|
const response = await GetData(`${API_URL}/transaction/history`, {
|
||||||
limit,
|
limit,
|
||||||
page: page + 1,
|
page: page + 1,
|
||||||
with_deleted: false,
|
with_deleted: false,
|
||||||
order_field: "execution_date",
|
order_field: 'execution_date',
|
||||||
order_direction: 'DESC',
|
order_direction: 'DESC',
|
||||||
filter: JSON.stringify(formattedFilter)
|
filter: JSON.stringify(formattedFilter)
|
||||||
});
|
});
|
||||||
|
|||||||
@ -293,7 +293,7 @@ const AddDialog = () => {
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<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">
|
<label className="form-label flex items-center gap-1 max-w-56">
|
||||||
Credit Limit<span className="text-red-500">*</span>
|
Credit Limit per Trx<span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<NumericFormat
|
<NumericFormat
|
||||||
|
|||||||
@ -302,7 +302,7 @@ const EditDialog = () => {
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<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">
|
<label className="form-label flex items-center gap-1 max-w-56">
|
||||||
Credit Limit<span className="text-red-500">*</span>
|
Credit Limit per Trx<span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<NumericFormat
|
<NumericFormat
|
||||||
className="input"
|
className="input"
|
||||||
|
|||||||
@ -115,7 +115,7 @@ const ManageWalletRuleContextProvider = ({ children }: { children: React.ReactNo
|
|||||||
{
|
{
|
||||||
accessorFn: (row) => row.credit_limit,
|
accessorFn: (row) => row.credit_limit,
|
||||||
id: 'credit_limit',
|
id: 'credit_limit',
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Credit Limit" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Credit Limit per Trx" column={column} />,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => currencyFormat(row.original.credit_limit),
|
cell: ({ row }) => currencyFormat(row.original.credit_limit),
|
||||||
|
|||||||
@ -71,6 +71,7 @@ const AgentBalance = () => {
|
|||||||
let temp = 1;
|
let temp = 1;
|
||||||
let resBalance = balances.data.data.list.map((el: any) => {
|
let resBalance = balances.data.data.list.map((el: any) => {
|
||||||
el.no = temp++;
|
el.no = temp++;
|
||||||
|
if (!el.agent_name) el.agent_name = ''
|
||||||
return el;
|
return el;
|
||||||
});
|
});
|
||||||
setDataBalance(resBalance);
|
setDataBalance(resBalance);
|
||||||
|
|||||||
@ -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) => (
|
||||||
|
|||||||
@ -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) => (
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -107,6 +107,35 @@ const DetailApprovalTransaction = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const [formattedJson, setFormattedJson] = useState('');
|
||||||
|
// const [highlightedJson, setHighlightedJson] = useState('');
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const obj = { a: 1, 'b': 'foo', c: [false, 'false', null, 'null', { d: { e: 1.3e5, f: '1.3e5' } }] };
|
||||||
|
// const str = JSON.stringify(obj, undefined, 4);
|
||||||
|
// setFormattedJson(str);
|
||||||
|
// setHighlightedJson(syntaxHighlight(str));
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// function syntaxHighlight(json: any) {
|
||||||
|
// json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
// return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])"(\s:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match: any) {
|
||||||
|
// let cls = 'number';
|
||||||
|
// if (/^"/.test(match)) {
|
||||||
|
// if (/:$/.test(match)) {
|
||||||
|
// cls = 'key';
|
||||||
|
// } else {
|
||||||
|
// cls = 'string';
|
||||||
|
// }
|
||||||
|
// } else if (/true|false/.test(match)) {
|
||||||
|
// cls = 'boolean';
|
||||||
|
// } else if (/null/.test(match)) {
|
||||||
|
// cls = 'null';
|
||||||
|
// }
|
||||||
|
// return '<span className="' + cls + '">' + match + '</span>';
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
||||||
@ -223,6 +252,7 @@ const DetailApprovalTransaction = () => {
|
|||||||
{(() => {
|
{(() => {
|
||||||
let status;
|
let status;
|
||||||
let badgeClass;
|
let badgeClass;
|
||||||
|
|
||||||
if (transactionDetails?.status === 'C') {
|
if (transactionDetails?.status === 'C') {
|
||||||
status = 'COMPLETE';
|
status = 'COMPLETE';
|
||||||
badgeClass = 'bg-green-100 text-green-800';
|
badgeClass = 'bg-green-100 text-green-800';
|
||||||
@ -232,9 +262,15 @@ const DetailApprovalTransaction = () => {
|
|||||||
} else if (transactionDetails?.status === 'O') {
|
} else if (transactionDetails?.status === 'O') {
|
||||||
status = 'ON PROCESS';
|
status = 'ON PROCESS';
|
||||||
badgeClass = 'bg-blue-100 text-blue-800';
|
badgeClass = 'bg-blue-100 text-blue-800';
|
||||||
} else {
|
} else if (transactionDetails?.status === 'P') {
|
||||||
status = 'PENDING';
|
status = 'PENDING';
|
||||||
badgeClass = 'bg-gray-100 text-gray-800';
|
badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
|
} else if (transactionDetails?.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
badgeClass = 'bg-yellow-100 text-yellow-800';
|
||||||
|
} else if (transactionDetails?.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
badgeClass = 'bg-purple-100 text-purple-800';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -419,16 +455,33 @@ const DetailApprovalTransaction = () => {
|
|||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{(() => {
|
{(() => {
|
||||||
let status;
|
let status;
|
||||||
|
let badgeClass;
|
||||||
|
|
||||||
if (transactionDetails?.status === 'C') {
|
if (transactionDetails?.status === 'C') {
|
||||||
status = 'COMPLETE';
|
status = 'COMPLETE';
|
||||||
|
badgeClass = 'bg-green-100 text-green-800';
|
||||||
} else if (transactionDetails?.status === 'F') {
|
} else if (transactionDetails?.status === 'F') {
|
||||||
status = 'FAILED';
|
status = 'FAILED';
|
||||||
|
badgeClass = 'bg-red-100 text-red-800';
|
||||||
} else if (transactionDetails?.status === 'O') {
|
} else if (transactionDetails?.status === 'O') {
|
||||||
status = 'ON PROCESS';
|
status = 'ON PROCESS';
|
||||||
} else {
|
badgeClass = 'bg-blue-100 text-blue-800';
|
||||||
|
} else if (transactionDetails?.status === 'P') {
|
||||||
status = 'PENDING';
|
status = 'PENDING';
|
||||||
|
badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
|
} else if (transactionDetails?.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
badgeClass = 'bg-yellow-100 text-yellow-800';
|
||||||
|
} else if (transactionDetails?.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
badgeClass = 'bg-purple-100 text-purple-800';
|
||||||
}
|
}
|
||||||
return status;
|
|
||||||
|
return (
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-semibold ${badgeClass}`}>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
})()}
|
})()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -630,11 +683,11 @@ const DetailApprovalTransaction = () => {
|
|||||||
<table className="min-w-full table-auto">
|
<table className="min-w-full table-auto">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-gray-100">
|
<tr className="bg-gray-100">
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -642,7 +695,25 @@ const DetailApprovalTransaction = () => {
|
|||||||
{transactionDetails?.log && transactionDetails?.log.length > 0 ? (
|
{transactionDetails?.log && transactionDetails?.log.length > 0 ? (
|
||||||
transactionDetails.log.map((log: { type: string, request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
transactionDetails.log.map((log: { type: string, request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
||||||
<tr key={index} className="border-t">
|
<tr key={index} className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
{(() => {
|
||||||
|
let status;
|
||||||
|
if (log.status === 'C') {
|
||||||
|
status = 'COMPLETE';
|
||||||
|
} else if (log.status === 'F') {
|
||||||
|
status = 'FAILED';
|
||||||
|
} else if (log.status === 'O') {
|
||||||
|
status = 'ON PROCESS';
|
||||||
|
} else if (log.status === 'P') {
|
||||||
|
status = 'PENDING';
|
||||||
|
} else if (log.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
} else if (log.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
||||||
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -665,9 +736,40 @@ const DetailApprovalTransaction = () => {
|
|||||||
hour12: false
|
hour12: false
|
||||||
})
|
})
|
||||||
: ''}</td>
|
: ''}</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">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint}</td>
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_code === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.request_body);
|
||||||
|
return JSON.stringify(parsed, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
return log.request_body; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_body === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.response_body);
|
||||||
|
return JSON.stringify(parsed, null, 2) ?? '';
|
||||||
|
} catch (e) {
|
||||||
|
return log.response_body ?? ''; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint || ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@ -756,19 +858,37 @@ const DetailApprovalTransaction = () => {
|
|||||||
<table className="min-w-full table-auto">
|
<table className="min-w-full table-auto">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-gray-100">
|
<tr className="bg-gray-100">
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{transactionDetails?.p24 && transactionDetails?.p24.length > 0 ? (
|
{transactionDetails?.p24 && transactionDetails?.p24.length > 0 ? (
|
||||||
transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
transactionDetails.p24.map((log: { status: string, request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
||||||
<tr key={index} className="border-t">
|
<tr key={index} className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
{(() => {
|
||||||
|
let status;
|
||||||
|
if (log.status === 'C') {
|
||||||
|
status = 'COMPLETE';
|
||||||
|
} else if (log.status === 'F') {
|
||||||
|
status = 'FAILED';
|
||||||
|
} else if (log.status === 'O') {
|
||||||
|
status = 'ON PROCESS';
|
||||||
|
} else if (log.status === 'P') {
|
||||||
|
status = 'PENDING';
|
||||||
|
} else if (log.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
} else if (log.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
||||||
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -792,9 +912,41 @@ const DetailApprovalTransaction = () => {
|
|||||||
hour12: false
|
hour12: false
|
||||||
})
|
})
|
||||||
: ''}</td>
|
: ''}</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">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td>
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.request_body === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.request_body);
|
||||||
|
return JSON.stringify(parsed, null, 2) ?? '';
|
||||||
|
} catch (e) {
|
||||||
|
return log.request_body ?? ''; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_code === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.response_body);
|
||||||
|
return JSON.stringify(parsed, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
return log.response_body; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import DetailApprovalTransaction from '../blocks/DetailApprovalTransaction';
|
|||||||
import ApprovalDialog from '../blocks/ApprovalDialog';
|
import ApprovalDialog from '../blocks/ApprovalDialog';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { getAuth } from '@/auth';
|
||||||
|
|
||||||
interface ApprovalTransactionProps {
|
interface ApprovalTransactionProps {
|
||||||
id: number;
|
id: number;
|
||||||
@ -158,11 +159,15 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode }
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => {
|
accessorFn: (row) => {
|
||||||
|
|
||||||
switch (row.status) {
|
switch (row.status) {
|
||||||
case 'C': return 'COMPLETE';
|
case 'C': return 'COMPLETE';
|
||||||
case 'F': return 'FAILED';
|
case 'F': return 'FAILED';
|
||||||
case 'O': return 'ON PROCESS';
|
case 'O': return 'ON PROCESS';
|
||||||
default: return 'PENDING';
|
case "P" : return 'PENDING';
|
||||||
|
case 'R': return 'ROLLBACK';
|
||||||
|
case 'S': return 'REVERSAL';
|
||||||
|
default: return 'UNKNOWN';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
id: 'status',
|
id: 'status',
|
||||||
|
|||||||
@ -127,6 +127,35 @@ const DetailTransaction = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const [formattedJson, setFormattedJson] = useState('');
|
||||||
|
// const [highlightedJson, setHighlightedJson] = useState('');
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const obj = { a: 1, 'b': 'foo', c: [false, 'false', null, 'null', { d: { e: 1.3e5, f: '1.3e5' } }] };
|
||||||
|
// const str = JSON.stringify(obj, undefined, 4);
|
||||||
|
// setFormattedJson(str);
|
||||||
|
// setHighlightedJson(syntaxHighlight(str));
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// function syntaxHighlight(json: any) {
|
||||||
|
// json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
// return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])"(\s:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match: any) {
|
||||||
|
// let cls = 'number';
|
||||||
|
// if (/^"/.test(match)) {
|
||||||
|
// if (/:$/.test(match)) {
|
||||||
|
// cls = 'key';
|
||||||
|
// } else {
|
||||||
|
// cls = 'string';
|
||||||
|
// }
|
||||||
|
// } else if (/true|false/.test(match)) {
|
||||||
|
// cls = 'boolean';
|
||||||
|
// } else if (/null/.test(match)) {
|
||||||
|
// cls = 'null';
|
||||||
|
// }
|
||||||
|
// return '<span className="' + cls + '">' + match + '</span>';
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
||||||
<DialogContent className="container-fixed max-w-[1024px] flex flex-col p-5 overflow-hidden">
|
<DialogContent className="container-fixed max-w-[1024px] flex flex-col p-5 overflow-hidden">
|
||||||
@ -137,12 +166,33 @@ const DetailTransaction = () => {
|
|||||||
<DialogBody>
|
<DialogBody>
|
||||||
{/* Tabs Navigation */}
|
{/* Tabs Navigation */}
|
||||||
<div className="flex border-b border-gray-200">
|
<div className="flex border-b border-gray-200">
|
||||||
|
{/* {transactionDetails?.kind == 'P' && ( */}
|
||||||
<button
|
<button
|
||||||
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'detail' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'detail' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
onClick={() => setActiveTab('detail')}
|
onClick={() => setActiveTab('detail')}
|
||||||
>
|
>
|
||||||
Detail Transaction
|
Detail Transaction
|
||||||
</button>
|
</button>
|
||||||
|
{/* )} */}
|
||||||
|
|
||||||
|
{transactionDetails?.kind == 'P' && (
|
||||||
|
<button
|
||||||
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'purchase' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
onClick={() => setActiveTab('purchase')}
|
||||||
|
>
|
||||||
|
Purchase Detail
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{transactionDetails?.kind == 'P' && (
|
||||||
|
<button
|
||||||
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'productdetail' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
onClick={() => setActiveTab('productdetail')}
|
||||||
|
>
|
||||||
|
Product Detail
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'origincustomer' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'origincustomer' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
onClick={() => setActiveTab('origincustomer')}
|
onClick={() => setActiveTab('origincustomer')}
|
||||||
@ -161,150 +211,48 @@ const DetailTransaction = () => {
|
|||||||
>
|
>
|
||||||
Origin Wallet
|
Origin Wallet
|
||||||
</button>
|
</button>
|
||||||
|
{transactionDetails?.kind !== 'P' && (
|
||||||
<button
|
<button
|
||||||
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'destinationwallet' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'destinationwallet' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
onClick={() => setActiveTab('destinationwallet')}
|
onClick={() => setActiveTab('destinationwallet')}
|
||||||
>
|
>
|
||||||
Destination Wallet
|
Destination Wallet
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'log' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'log' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
onClick={() => setActiveTab('log')}
|
onClick={() => setActiveTab('log')}
|
||||||
>
|
>
|
||||||
Transaction Log
|
Transaction Log
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{transactionDetails?.kind !== 'P' && (
|
||||||
<button
|
<button
|
||||||
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'approve' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'approve' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
onClick={() => setActiveTab('approve')}
|
onClick={() => setActiveTab('approve')}
|
||||||
>
|
>
|
||||||
Approval Log
|
Approval Log
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{transactionDetails?.kind !== 'P' && (
|
||||||
<button
|
<button
|
||||||
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'p24' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'p24' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
onClick={() => setActiveTab('p24')}
|
onClick={() => setActiveTab('p24')}
|
||||||
>
|
>
|
||||||
Log P24
|
Log P24
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* Tab Content */}
|
|
||||||
<div className="py-4 overflow-y-auto max-h-[400px]">
|
|
||||||
{activeTab === 'detail' && transactionDetails?.kind === 'P' && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="font-semibold flex items-center">
|
|
||||||
Transaction Information
|
|
||||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
|
||||||
Info
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">Transaction Date</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{transactionDetails?.transaction_date
|
|
||||||
? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
hour12: false
|
|
||||||
})
|
|
||||||
: ''}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">Full Name</p>
|
|
||||||
<p className="font-medium">{transactionDetails?.origin_customer?.fullname ?? transactionDetails?.origin_customer?.origin_name}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">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>
|
{activeTab === 'purchase' && (
|
||||||
<p className="text-sm text-gray-500">Fee</p>
|
<>
|
||||||
<p className="font-medium">
|
<h3 className="font-semibold flex items-center mt-4">
|
||||||
{(transactionDetails?.transfer?.fee_amount ?? transactionDetails?.purchase?.fee_amount ?? 0)
|
|
||||||
.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">Status</p>
|
|
||||||
<div>
|
|
||||||
{(() => {
|
|
||||||
let status;
|
|
||||||
let badgeClass;
|
|
||||||
if (transactionDetails?.status === 'C') {
|
|
||||||
status = 'COMPLETE';
|
|
||||||
badgeClass = 'bg-green-100 text-green-800';
|
|
||||||
} else if (transactionDetails?.status === 'F') {
|
|
||||||
status = 'FAILED';
|
|
||||||
badgeClass = 'bg-red-100 text-red-800';
|
|
||||||
} else if (transactionDetails?.status === 'O') {
|
|
||||||
status = 'ON PROCESS';
|
|
||||||
badgeClass = 'bg-blue-100 text-blue-800';
|
|
||||||
} else {
|
|
||||||
status = 'PENDING';
|
|
||||||
badgeClass = 'bg-gray-100 text-gray-800';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={`px-3 py-1 rounded-full text-xs font-semibold ${badgeClass}`}>
|
|
||||||
{status}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">Transaction Type</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{(() => {
|
|
||||||
let kind;
|
|
||||||
if (transactionDetails?.kind === 'T') {
|
|
||||||
kind = 'TRANSFER';
|
|
||||||
} else if (transactionDetails?.kind === 'P') {
|
|
||||||
kind = 'PURCHASE';
|
|
||||||
} else if (transactionDetails?.kind === 'W') {
|
|
||||||
kind = 'WITHDRAW';
|
|
||||||
} else if (transactionDetails?.kind === 'U') {
|
|
||||||
kind = 'TOP UP';
|
|
||||||
} else if (transactionDetails?.kind === 'R') {
|
|
||||||
kind = 'RETURN';
|
|
||||||
}else if (transactionDetails?.kind === 'E') {
|
|
||||||
kind = 'REWARD';
|
|
||||||
}
|
|
||||||
return kind;
|
|
||||||
})()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">Description</p>
|
|
||||||
<p className="font-medium">{transactionDetails?.description || '-'}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">Name</p>
|
|
||||||
<p className="font-medium">{transactionDetails?.type?.name || '-'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="font-semibold flex items-center">
|
|
||||||
Purchase
|
Purchase
|
||||||
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
|
||||||
Purchase
|
|
||||||
</span>
|
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">Amount</p>
|
<p className="text-sm text-gray-500">Amount</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
@ -336,17 +284,16 @@ const DetailTransaction = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'detail' && (
|
|
||||||
|
{activeTab === 'productdetail' && (
|
||||||
|
<>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="font-semibold flex items-center">
|
<h3 className="font-semibold flex items-center mt-4">
|
||||||
Product Information
|
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>
|
</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">Product Name</p>
|
<p className="text-sm text-gray-500">Product Name</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
@ -391,6 +338,140 @@ const DetailTransaction = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="py-4 overflow-y-auto max-h-[400px]">
|
||||||
|
{activeTab === 'detail' && transactionDetails?.kind === 'P' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="font-semibold flex items-center">
|
||||||
|
Transaction Information
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Transaction Date</p>
|
||||||
|
<p className="font-medium">
|
||||||
|
{transactionDetails?.transaction_date
|
||||||
|
? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
})
|
||||||
|
: ''}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Full Name</p>
|
||||||
|
<p className="font-medium">{transactionDetails?.origin_customer?.origin_name ?? ""}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">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?.transfer?.fee_amount ?? transactionDetails?.purchase?.fee_amount ?? 0)
|
||||||
|
.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Status</p>
|
||||||
|
<div>
|
||||||
|
{(() => {
|
||||||
|
let status;
|
||||||
|
let badgeClass;
|
||||||
|
|
||||||
|
|
||||||
|
if (transactionDetails?.status === 'C') {
|
||||||
|
status = 'COMPLETE';
|
||||||
|
badgeClass = 'bg-green-100 text-green-800';
|
||||||
|
} else if (transactionDetails?.status === 'F') {
|
||||||
|
status = 'FAILED';
|
||||||
|
badgeClass = 'bg-red-100 text-red-800';
|
||||||
|
} else if (transactionDetails?.status === 'O') {
|
||||||
|
status = 'ON PROCESS';
|
||||||
|
badgeClass = 'bg-blue-100 text-blue-800';
|
||||||
|
} else if (transactionDetails?.status === 'P') {
|
||||||
|
} else if (transactionDetails?.status === 'P') {
|
||||||
|
status = 'PENDING';
|
||||||
|
badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
|
} else if (transactionDetails?.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
badgeClass = 'bg-yellow-100 text-yellow-800';
|
||||||
|
} else if (transactionDetails?.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
badgeClass = 'bg-purple-100 text-purple-800';
|
||||||
|
} else if (transactionDetails?.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
badgeClass = 'bg-yellow-100 text-yellow-800';
|
||||||
|
} else if (transactionDetails?.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
badgeClass = 'bg-purple-100 text-purple-800';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-semibold ${badgeClass}`}>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Transaction Type</p>
|
||||||
|
<p className="font-medium">
|
||||||
|
{(() => {
|
||||||
|
let kind;
|
||||||
|
if (transactionDetails?.kind === 'T') {
|
||||||
|
kind = 'TRANSFER';
|
||||||
|
} else if (transactionDetails?.kind === 'P') {
|
||||||
|
kind = 'PURCHASE';
|
||||||
|
} else if (transactionDetails?.kind === 'W') {
|
||||||
|
kind = 'WITHDRAW';
|
||||||
|
} else if (transactionDetails?.kind === 'U') {
|
||||||
|
kind = 'TOP UP';
|
||||||
|
} else if (transactionDetails?.kind === 'R') {
|
||||||
|
kind = 'RETURN';
|
||||||
|
} else if (transactionDetails?.kind === 'E') {
|
||||||
|
kind = 'REWARD';
|
||||||
|
} else if (transactionDetails?.kind === 'L') {
|
||||||
|
kind = 'PURCHASE LOJA';
|
||||||
|
} else if (transactionDetails?.kind === 'B') {
|
||||||
|
kind = 'TOP UP P24';
|
||||||
|
} else if (transactionDetails?.kind === 'A') {
|
||||||
|
kind = 'TRANSFER AGENT';
|
||||||
|
} else if (transactionDetails?.kind === 'M') {
|
||||||
|
kind = 'WITHDRAWAL AGENT';
|
||||||
|
}
|
||||||
|
return kind;
|
||||||
|
})()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Description</p>
|
||||||
|
<p className="font-medium">{transactionDetails?.description || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">Name</p>
|
||||||
|
<p className="font-medium">{transactionDetails?.type?.name || '-'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'detail' && transactionDetails?.kind != 'P' && transactionDetails?.transfer != null && (
|
{activeTab === 'detail' && transactionDetails?.kind != 'P' && transactionDetails?.transfer != null && (
|
||||||
@ -418,7 +499,7 @@ const DetailTransaction = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">Full Name</p>
|
<p className="text-sm text-gray-500">Full Name</p>
|
||||||
<p className="font-medium">{transactionDetails?.origin_customer?.fullname ?? transactionDetails?.origin_customer?.origin_name}</p>
|
<p className="font-medium">{transactionDetails?.origin_customer?.origin_name ?? ""}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">Amount</p>
|
<p className="text-sm text-gray-500">Amount</p>
|
||||||
@ -434,18 +515,46 @@ const DetailTransaction = () => {
|
|||||||
<p className="text-sm text-gray-500">Status</p>
|
<p className="text-sm text-gray-500">Status</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{(() => {
|
{(() => {
|
||||||
let status;
|
let status = 'UNKNOWN';
|
||||||
if (transactionDetails?.status === 'C') {
|
let badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
|
|
||||||
|
switch (transactionDetails?.status) {
|
||||||
|
case 'C':
|
||||||
status = 'COMPLETE';
|
status = 'COMPLETE';
|
||||||
} else if (transactionDetails?.status === 'F') {
|
badgeClass = 'bg-green-100 text-green-800';
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
status = 'FAILED';
|
status = 'FAILED';
|
||||||
} else if (transactionDetails?.status === 'O') {
|
badgeClass = 'bg-red-100 text-red-800';
|
||||||
|
break;
|
||||||
|
case 'O':
|
||||||
status = 'ON PROCESS';
|
status = 'ON PROCESS';
|
||||||
} else {
|
badgeClass = 'bg-blue-100 text-blue-800';
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
status = 'PENDING';
|
status = 'PENDING';
|
||||||
|
badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
badgeClass = 'bg-yellow-100 text-yellow-800';
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
status = 'REVERSAL';
|
||||||
|
badgeClass = 'bg-purple-100 text-purple-800';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// optional: keep 'UNKNOWN' or set to null
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return status;
|
|
||||||
|
return (
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-semibold ${badgeClass}`}>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -463,6 +572,7 @@ const DetailTransaction = () => {
|
|||||||
kind = 'TOP UP';
|
kind = 'TOP UP';
|
||||||
} else if (transactionDetails?.kind === 'R') {
|
} else if (transactionDetails?.kind === 'R') {
|
||||||
kind = 'RETURN';
|
kind = 'RETURN';
|
||||||
|
} else if (transactionDetails?.kind === 'E') {
|
||||||
} else if (transactionDetails?.kind === 'E') {
|
} else if (transactionDetails?.kind === 'E') {
|
||||||
kind = 'REWARD';
|
kind = 'REWARD';
|
||||||
}
|
}
|
||||||
@ -501,7 +611,7 @@ const DetailTransaction = () => {
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">Phone Number</p>
|
<p className="text-sm text-gray-500">Phone Number</p>
|
||||||
<p className="font-medium">{transactionDetails?.origin_msisdn}</p>
|
<p className="font-medium">{transactionDetails?.origin_msisdn}</p>
|
||||||
<p className="font-medium">{transactionDetails?.origin_customer?.fullname ?? transactionDetails?.origin_customer?.origin_name}</p>
|
<p className="font-medium">{transactionDetails?.origin_customer?.origin_name ?? ""}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">Phone Number</p>
|
<p className="text-sm text-gray-500">Phone Number</p>
|
||||||
@ -592,7 +702,7 @@ const DetailTransaction = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'destinationwallet' && (
|
{activeTab === 'destinationwallet' && transactionDetails?.kind?.trim()?.toUpperCase() !== 'P' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="font-semibold">Destination Wallet</h3>
|
<h3 className="font-semibold">Destination Wallet</h3>
|
||||||
|
|
||||||
@ -638,6 +748,7 @@ const DetailTransaction = () => {
|
|||||||
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -648,11 +759,11 @@ const DetailTransaction = () => {
|
|||||||
<table className="min-w-full table-auto">
|
<table className="min-w-full table-auto">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-gray-100">
|
<tr className="bg-gray-100">
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -660,7 +771,25 @@ const DetailTransaction = () => {
|
|||||||
{transactionDetails?.log && transactionDetails?.log.length > 0 ? (
|
{transactionDetails?.log && transactionDetails?.log.length > 0 ? (
|
||||||
transactionDetails.log.map((log: { type: string, request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
transactionDetails.log.map((log: { type: string, request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
||||||
<tr key={index} className="border-t">
|
<tr key={index} className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
{(() => {
|
||||||
|
let status;
|
||||||
|
if (log.status === 'C') {
|
||||||
|
status = 'COMPLETE';
|
||||||
|
} else if (log.status === 'F') {
|
||||||
|
status = 'FAILED';
|
||||||
|
} else if (log.status === 'O') {
|
||||||
|
status = 'ON PROCESS';
|
||||||
|
} else if (log.status === 'P') {
|
||||||
|
status = 'PENDING';
|
||||||
|
} else if (log.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
} else if (log.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
||||||
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -683,9 +812,39 @@ const DetailTransaction = () => {
|
|||||||
hour12: false
|
hour12: false
|
||||||
})
|
})
|
||||||
: ''}</td>
|
: ''}</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">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint}</td>
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_code === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.request_body);
|
||||||
|
return JSON.stringify(parsed, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
return log.request_body; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_body === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.response_body);
|
||||||
|
return JSON.stringify(parsed, null, 2) ?? '';
|
||||||
|
} catch (e) {
|
||||||
|
return log.response_body ?? ''; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint || ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@ -701,7 +860,7 @@ const DetailTransaction = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'approve' && (
|
{activeTab === 'approve' && transactionDetails?.kind == "P" && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="font-semibold">Approval Logs</h3>
|
<h3 className="font-semibold">Approval Logs</h3>
|
||||||
{transactionDetails?.log_approve.length === 0 ? (
|
{transactionDetails?.log_approve.length === 0 ? (
|
||||||
@ -774,19 +933,37 @@ const DetailTransaction = () => {
|
|||||||
<table className="min-w-full table-auto">
|
<table className="min-w-full table-auto">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-gray-100">
|
<tr className="bg-gray-100">
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{transactionDetails?.p24 && transactionDetails?.p24.length > 0 ? (
|
{transactionDetails?.p24 && transactionDetails?.p24.length > 0 ? (
|
||||||
transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
transactionDetails.p24.map((log: { status: string, request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
|
||||||
<tr key={index} className="border-t">
|
<tr key={index} className="border-t">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
{(() => {
|
||||||
|
let status;
|
||||||
|
if (log.status === 'C') {
|
||||||
|
status = 'COMPLETE';
|
||||||
|
} else if (log.status === 'F') {
|
||||||
|
status = 'FAILED';
|
||||||
|
} else if (log.status === 'O') {
|
||||||
|
status = 'ON PROCESS';
|
||||||
|
} else if (log.status === 'P') {
|
||||||
|
status = 'PENDING';
|
||||||
|
} else if (log.status === 'R') {
|
||||||
|
status = 'ROLLBACK';
|
||||||
|
} else if (log.status === 'S') {
|
||||||
|
status = 'REVERSAL';
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date
|
||||||
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
? new Date(log.request_date).toLocaleDateString('en-GB', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -810,9 +987,39 @@ const DetailTransaction = () => {
|
|||||||
hour12: false
|
hour12: false
|
||||||
})
|
})
|
||||||
: ''}</td>
|
: ''}</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">
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td>
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_code === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.request_body);
|
||||||
|
return JSON.stringify(parsed, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
return log.request_body; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">
|
||||||
|
<pre style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
||||||
|
{(() => {
|
||||||
|
try {
|
||||||
|
if (log.response_body === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(log.response_body);
|
||||||
|
return JSON.stringify(parsed, null, 2) ?? '';
|
||||||
|
} catch (e) {
|
||||||
|
return log.response_body ?? ''; // fallback: tampilkan as-is jika gagal parse
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -178,6 +178,10 @@ const ListToolbar = () => {
|
|||||||
<SelectItem value="R">RETURN</SelectItem>
|
<SelectItem value="R">RETURN</SelectItem>
|
||||||
<SelectItem value="N">TOP UP PARTNER</SelectItem>
|
<SelectItem value="N">TOP UP PARTNER</SelectItem>
|
||||||
<SelectItem value="E">REWARD</SelectItem>
|
<SelectItem value="E">REWARD</SelectItem>
|
||||||
|
<SelectItem value="L">PURCHASE LOJA</SelectItem>
|
||||||
|
<SelectItem value="B">TOP UP P24</SelectItem>
|
||||||
|
<SelectItem value="A">TRANSFER AGENT</SelectItem>
|
||||||
|
<SelectItem value="M">WITHDRAWAL AGENT</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
|||||||
@ -78,6 +78,10 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
case 'R': return 'RETURN';
|
case 'R': return 'RETURN';
|
||||||
case 'N': return 'TOP UP PARTNER';
|
case 'N': return 'TOP UP PARTNER';
|
||||||
case 'E': return 'REWARD';
|
case 'E': return 'REWARD';
|
||||||
|
case 'L': return 'PURCHASE LOJA';
|
||||||
|
case 'B': return 'TOP UP P24';
|
||||||
|
case 'A': return 'TRANSFER AGENT';
|
||||||
|
case 'M': return 'WITHDRAWAL AGENT';
|
||||||
default: return '_';
|
default: return '_';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -197,10 +201,22 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
label = 'ON PROCESS';
|
label = 'ON PROCESS';
|
||||||
badgeClass = 'bg-blue-100 text-blue-800';
|
badgeClass = 'bg-blue-100 text-blue-800';
|
||||||
break;
|
break;
|
||||||
default:
|
case 'P':
|
||||||
label = 'PENDING';
|
label = 'PENDING';
|
||||||
badgeClass = 'bg-gray-100 text-gray-800';
|
badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
break;
|
break;
|
||||||
|
case 'R':
|
||||||
|
label = 'ROLLBACK';
|
||||||
|
badgeClass = 'bg-yellow-100 text-yellow-800';
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
label = 'REVERSAL';
|
||||||
|
badgeClass = 'bg-purple-100 text-purple-800';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
label = '';
|
||||||
|
badgeClass = 'bg-gray-100 text-gray-800';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -90,6 +90,10 @@ const AddFeeDialog = () => {
|
|||||||
id: customer.id,
|
id: customer.id,
|
||||||
name: customer.username
|
name: customer.username
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const [openDeductOrigin, setOpenDeductOrigin] = useState(false);
|
||||||
|
const [openCreditDestination, setOpenCreditDestination] = useState(false);
|
||||||
|
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
const initialState = {
|
const initialState = {
|
||||||
name: '',
|
name: '',
|
||||||
@ -101,20 +105,18 @@ const AddFeeDialog = () => {
|
|||||||
period_end: '',
|
period_end: '',
|
||||||
deduct_amount: 0,
|
deduct_amount: 0,
|
||||||
deduct_percentage: 0,
|
deduct_percentage: 0,
|
||||||
fee_amount: 0,
|
|
||||||
priority: '',
|
|
||||||
status: '',
|
status: '',
|
||||||
status_include: '',
|
status_include: '',
|
||||||
created_by: '',
|
created_by: '',
|
||||||
created_at: '',
|
created_at: '',
|
||||||
deduct_from: '',
|
deduct_from: '',
|
||||||
|
deduct_origin: '00000000-0000-0000-0000-000000000000',
|
||||||
deduct_from_account: '',
|
deduct_from_account: '',
|
||||||
credit_to: '',
|
credit_to: '',
|
||||||
credit_destination: '00000000-0000-0000-0000-000000000000',
|
credit_destination: '00000000-0000-0000-0000-000000000000',
|
||||||
credit_destination_account: ''
|
credit_destination_account: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const [formField, setFormField] = useState(initialState);
|
const [formField, setFormField] = useState(initialState);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
@ -122,6 +124,16 @@ const AddFeeDialog = () => {
|
|||||||
setTransactionTypeName('');
|
setTransactionTypeName('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCloseDialog = () => {
|
||||||
|
setFormField(initialState);
|
||||||
|
setAlert({ show: false, message: '' });
|
||||||
|
setCustomerSearchTerm('');
|
||||||
|
setOpenCreditDestination(false);
|
||||||
|
setOpenDeductOrigin(false);
|
||||||
|
setOpen(false);
|
||||||
|
handleEditFeeDialog(false, null);
|
||||||
|
};
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [showTransactionFeeDialog, setShowTransactionFeeDialog] = useState(false);
|
const [showTransactionFeeDialog, setShowTransactionFeeDialog] = useState(false);
|
||||||
const parsedUser = getAuth()?.user;
|
const parsedUser = getAuth()?.user;
|
||||||
@ -273,7 +285,6 @@ const AddFeeDialog = () => {
|
|||||||
'transaction_type',
|
'transaction_type',
|
||||||
'status',
|
'status',
|
||||||
'status_include',
|
'status_include',
|
||||||
'priority',
|
|
||||||
'deduct_from',
|
'deduct_from',
|
||||||
'deduct_from_account',
|
'deduct_from_account',
|
||||||
'credit_to',
|
'credit_to',
|
||||||
@ -300,6 +311,14 @@ const AddFeeDialog = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formField.deduct_from === 'I' && !formField.deduct_origin) {
|
||||||
|
setAlert({
|
||||||
|
show: true,
|
||||||
|
message: 'Deduct Origin is required when Input is selected'
|
||||||
|
});
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
|
|
||||||
const payload = { ...formField };
|
const payload = { ...formField };
|
||||||
@ -308,6 +327,9 @@ const AddFeeDialog = () => {
|
|||||||
payload.credit_destination = '00000000-0000-0000-0000-000000000000';
|
payload.credit_destination = '00000000-0000-0000-0000-000000000000';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formField.deduct_from !== 'I') {
|
||||||
|
payload.deduct_origin = '00000000-0000-0000-0000-000000000000';
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await PostData(`${API_URL}/transactionfees/create`, payload);
|
const response = await PostData(`${API_URL}/transactionfees/create`, payload);
|
||||||
|
|
||||||
@ -368,7 +390,15 @@ const AddFeeDialog = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showAddFeeDialog} onOpenChange={(open) => handleAddFeeDialog(open)}>
|
<Dialog
|
||||||
|
open={showAddFeeDialog}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
handleAddFeeDialog(open);
|
||||||
|
handleCloseDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogContent className="container-fixed max-w-[1080px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
<DialogContent className="container-fixed max-w-[1080px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
||||||
<DialogTitle></DialogTitle>
|
<DialogTitle></DialogTitle>
|
||||||
<DialogDescription></DialogDescription>
|
<DialogDescription></DialogDescription>
|
||||||
@ -554,30 +584,20 @@ const AddFeeDialog = () => {
|
|||||||
placeholder="Enter Deduct Percentage"
|
placeholder="Enter Deduct Percentage"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
|
||||||
<label className="form-label">Fee Amount</label>
|
|
||||||
<NumericFormat
|
|
||||||
className="input"
|
|
||||||
value={formField.fee_amount}
|
|
||||||
thousandSeparator="."
|
|
||||||
decimalSeparator=","
|
|
||||||
allowNegative={false}
|
|
||||||
onValueChange={(values) => {
|
|
||||||
setFormField((prev) => ({
|
|
||||||
...prev,
|
|
||||||
fee_amount: values.floatValue || 0
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
placeholder="Enter Fee Amount"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Deduct From <span className="text-red-500">*</span>
|
Deduct From <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<Select
|
||||||
value={formField.deduct_from}
|
value={formField.deduct_from}
|
||||||
onValueChange={(value) => setFormField({ ...formField, deduct_from: value })}
|
onValueChange={(value) =>
|
||||||
|
setFormField({
|
||||||
|
...formField,
|
||||||
|
deduct_from: value,
|
||||||
|
deduct_origin: value === 'I' ? '' : '00000000-0000-0000-0000-000000000000'
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select Deduct From" />
|
<SelectValue placeholder="Select Deduct From" />
|
||||||
@ -585,12 +605,82 @@ const AddFeeDialog = () => {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="D">Destination Member</SelectItem>
|
<SelectItem value="D">Destination Member</SelectItem>
|
||||||
<SelectItem value="S">Source Member</SelectItem>
|
<SelectItem value="S">Source Member</SelectItem>
|
||||||
|
<SelectItem value="I">Input</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
{formField.deduct_from === 'I' && (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Deduct From Destination <span className="text-red-500">*</span>
|
Deduct Origin <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className="flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
|
||||||
|
onClick={() => setOpenDeductOrigin(!openDeductOrigin)}
|
||||||
|
>
|
||||||
|
<span className="truncate">
|
||||||
|
{customers.find((customer) => customer.id === formField.deduct_origin)
|
||||||
|
?.username || 'Search customer...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{openDeductOrigin && (
|
||||||
|
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-52 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg">
|
||||||
|
<div className="sticky top-0 bg-white p-2 border-b">
|
||||||
|
<Input
|
||||||
|
className="h-8 text-sm"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search customer..."
|
||||||
|
value={customerSearchTerm}
|
||||||
|
onChange={(e) => setCustomerSearchTerm(e.target.value)}
|
||||||
|
autoComplete="off"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{customers
|
||||||
|
.filter(
|
||||||
|
(customer) =>
|
||||||
|
customer.username
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(customerSearchTerm.toLowerCase()) ||
|
||||||
|
customer.msisdn.includes(customerSearchTerm)
|
||||||
|
)
|
||||||
|
.map((customer) => (
|
||||||
|
<div
|
||||||
|
key={customer.id}
|
||||||
|
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
||||||
|
onClick={() => {
|
||||||
|
setFormField({ ...formField, deduct_origin: customer.id });
|
||||||
|
setOpenDeductOrigin(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{customer.username}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{customers.filter(
|
||||||
|
(customer) =>
|
||||||
|
customer.username
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(customerSearchTerm.toLowerCase()) ||
|
||||||
|
customer.msisdn.includes(customerSearchTerm)
|
||||||
|
).length === 0 && (
|
||||||
|
<div className="px-3 py-2 text-sm text-gray-500">
|
||||||
|
No customer found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<label className="form-label">
|
||||||
|
Deduct From Account <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
{renderSelectWithLoading(
|
{renderSelectWithLoading(
|
||||||
formField.deduct_from_account,
|
formField.deduct_from_account,
|
||||||
@ -600,6 +690,7 @@ const AddFeeDialog = () => {
|
|||||||
isLoadingWallets
|
isLoadingWallets
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Credit To <span className="text-red-500">*</span>
|
Credit To <span className="text-red-500">*</span>
|
||||||
@ -633,17 +724,16 @@ const AddFeeDialog = () => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div
|
||||||
className="flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
|
className="flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpenCreditDestination(!openCreditDestination)}
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{customers.find(
|
{customers.find(
|
||||||
(customer) => customer.id === formField.credit_destination
|
(customer) => customer.id === formField.credit_destination
|
||||||
)?.username || 'Search customer...'}
|
)?.username || 'Search customer...'}
|
||||||
</span>
|
</span>
|
||||||
<path d="m6 9 6 6 6-6"></path>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{open && (
|
{openCreditDestination && (
|
||||||
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-52 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg">
|
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-52 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg">
|
||||||
<div className="sticky top-0 bg-white p-2 border-b">
|
<div className="sticky top-0 bg-white p-2 border-b">
|
||||||
<Input
|
<Input
|
||||||
@ -675,7 +765,7 @@ const AddFeeDialog = () => {
|
|||||||
...formField,
|
...formField,
|
||||||
credit_destination: customer.id
|
credit_destination: customer.id
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpenCreditDestination(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.username}
|
{customer.username}
|
||||||
@ -698,6 +788,7 @@ const AddFeeDialog = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Credit Destination Account <span className="text-red-500">*</span>
|
Credit Destination Account <span className="text-red-500">*</span>
|
||||||
@ -744,23 +835,7 @@ const AddFeeDialog = () => {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
|
||||||
<label className="form-label">
|
|
||||||
Priority <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<Select
|
|
||||||
value={formField.priority}
|
|
||||||
onValueChange={(value) => setFormField({ ...formField, priority: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select Priority" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Y">Yes</SelectItem>
|
|
||||||
<SelectItem value="N">No</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end pt-2.5 gap-5">
|
<div className="flex justify-end pt-2.5 gap-5">
|
||||||
<Button
|
<Button
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
|
|||||||
@ -61,6 +61,8 @@ const EditFeeDialog = () => {
|
|||||||
const { reload } = useDataGrid();
|
const { reload } = useDataGrid();
|
||||||
const { GetData, PutData } = useCallApi();
|
const { GetData, PutData } = useCallApi();
|
||||||
const parsedUser = getAuth()?.user;
|
const parsedUser = getAuth()?.user;
|
||||||
|
const [openDeductOrigin, setOpenDeductOrigin] = useState(false);
|
||||||
|
const [openCreditDestination, setOpenCreditDestination] = useState(false);
|
||||||
|
|
||||||
const [wallets, setWallets] = useState<WalletProps[]>([]);
|
const [wallets, setWallets] = useState<WalletProps[]>([]);
|
||||||
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
||||||
@ -94,13 +96,12 @@ const EditFeeDialog = () => {
|
|||||||
period_end: '',
|
period_end: '',
|
||||||
deduct_amount: 0,
|
deduct_amount: 0,
|
||||||
deduct_percentage: 0,
|
deduct_percentage: 0,
|
||||||
fee_amount: 0,
|
|
||||||
priority: '',
|
|
||||||
status: '',
|
status: '',
|
||||||
status_include: '',
|
status_include: '',
|
||||||
updated_by: '',
|
updated_by: '',
|
||||||
updated_at: '',
|
updated_at: '',
|
||||||
deduct_from: '',
|
deduct_from: '',
|
||||||
|
deduct_origin: '00000000-0000-0000-0000-000000000000',
|
||||||
deduct_from_account: '',
|
deduct_from_account: '',
|
||||||
credit_to: '',
|
credit_to: '',
|
||||||
credit_destination: '00000000-0000-0000-0000-000000000000',
|
credit_destination: '00000000-0000-0000-0000-000000000000',
|
||||||
@ -143,7 +144,6 @@ const EditFeeDialog = () => {
|
|||||||
'transaction_type',
|
'transaction_type',
|
||||||
'status',
|
'status',
|
||||||
'status_include',
|
'status_include',
|
||||||
'priority',
|
|
||||||
'deduct_from',
|
'deduct_from',
|
||||||
'deduct_from_account',
|
'deduct_from_account',
|
||||||
'credit_to',
|
'credit_to',
|
||||||
@ -170,6 +170,15 @@ const EditFeeDialog = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formField.deduct_from === 'I' && !formField.deduct_origin) {
|
||||||
|
setAlert({
|
||||||
|
show: true,
|
||||||
|
message: 'Deduct Origin is required when Input is selected'
|
||||||
|
});
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
|
|
||||||
const payload = { ...formField };
|
const payload = { ...formField };
|
||||||
@ -178,6 +187,11 @@ const EditFeeDialog = () => {
|
|||||||
payload.credit_destination = '00000000-0000-0000-0000-000000000000';
|
payload.credit_destination = '00000000-0000-0000-0000-000000000000';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formField.deduct_from !== 'I') {
|
||||||
|
payload.deduct_origin = '00000000-0000-0000-0000-000000000000';
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(payload);
|
||||||
try {
|
try {
|
||||||
const response = await PutData(
|
const response = await PutData(
|
||||||
`${API_URL}/transactionfees/update/${selectedTransferFee}`,
|
`${API_URL}/transactionfees/update/${selectedTransferFee}`,
|
||||||
@ -296,7 +310,7 @@ const EditFeeDialog = () => {
|
|||||||
setIsLoadingTransferFee(true);
|
setIsLoadingTransferFee(true);
|
||||||
try {
|
try {
|
||||||
const response = await GetData(`${API_URL}/transactionfees/getdata/${id}`, {});
|
const response = await GetData(`${API_URL}/transactionfees/getdata/${id}`, {});
|
||||||
|
// console.log(response);
|
||||||
if (response?.status) {
|
if (response?.status) {
|
||||||
setFormField({
|
setFormField({
|
||||||
...initialState,
|
...initialState,
|
||||||
@ -309,12 +323,12 @@ const EditFeeDialog = () => {
|
|||||||
period_end: formatDate(response.data.period_end),
|
period_end: formatDate(response.data.period_end),
|
||||||
deduct_amount: response.data.deduct_amount || 0,
|
deduct_amount: response.data.deduct_amount || 0,
|
||||||
deduct_percentage: response.data.deduct_percentage || 0,
|
deduct_percentage: response.data.deduct_percentage || 0,
|
||||||
fee_amount: response.data.fee_amount || 0,
|
|
||||||
priority: response.data.priority || '',
|
|
||||||
status: response.data.status || '',
|
status: response.data.status || '',
|
||||||
status_include: response.data.status_include || '',
|
status_include: response.data.status_include || '',
|
||||||
deduct_from: response.data.deduct_from || '',
|
deduct_from: response.data.deduct_from || '',
|
||||||
deduct_from_account: response.data.deduct_from_account?.id || '',
|
deduct_from_account: response.data.deduct_from_account?.id || '',
|
||||||
|
deduct_origin:
|
||||||
|
response.data.deduct_origin?.id || '00000000-0000-0000-0000-000000000000',
|
||||||
credit_to: response.data.credit_to || '',
|
credit_to: response.data.credit_to || '',
|
||||||
credit_destination:
|
credit_destination:
|
||||||
response.data.credit_destination?.id || '00000000-0000-0000-0000-000000000000',
|
response.data.credit_destination?.id || '00000000-0000-0000-0000-000000000000',
|
||||||
@ -353,6 +367,8 @@ const EditFeeDialog = () => {
|
|||||||
setFormField(initialState);
|
setFormField(initialState);
|
||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
setCustomerSearchTerm('');
|
setCustomerSearchTerm('');
|
||||||
|
setOpenCreditDestination(false);
|
||||||
|
setOpenDeductOrigin(false);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
handleEditFeeDialog(false, null);
|
handleEditFeeDialog(false, null);
|
||||||
};
|
};
|
||||||
@ -605,31 +621,19 @@ const EditFeeDialog = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
|
||||||
<label className="form-label">Fee Amount</label>
|
|
||||||
<NumericFormat
|
|
||||||
className="input"
|
|
||||||
value={formField.fee_amount}
|
|
||||||
thousandSeparator="."
|
|
||||||
decimalSeparator=","
|
|
||||||
allowNegative={false}
|
|
||||||
onValueChange={(values) => {
|
|
||||||
setFormField((prev) => ({
|
|
||||||
...prev,
|
|
||||||
fee_amount: values.floatValue || 0
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
placeholder="Enter Fee Amount"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Deduct From <span className="text-red-500">*</span>
|
Deduct From <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<Select
|
||||||
value={formField.deduct_from}
|
value={formField.deduct_from}
|
||||||
onValueChange={(value) => setFormField({ ...formField, deduct_from: value })}
|
onValueChange={(value) =>
|
||||||
|
setFormField({
|
||||||
|
...formField,
|
||||||
|
deduct_from: value,
|
||||||
|
deduct_origin: value === 'I' ? '' : '00000000-0000-0000-0000-000000000000'
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select Deduct From" />
|
<SelectValue placeholder="Select Deduct From" />
|
||||||
@ -637,13 +641,77 @@ const EditFeeDialog = () => {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="D">Destination Member</SelectItem>
|
<SelectItem value="D">Destination Member</SelectItem>
|
||||||
<SelectItem value="S">Source Member</SelectItem>
|
<SelectItem value="S">Source Member</SelectItem>
|
||||||
|
<SelectItem value="I">Input</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{formField.deduct_from === 'I' && (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Deduct From Destination <span className="text-red-500">*</span>
|
Deduct Origin <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className="flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
|
||||||
|
onClick={() => setOpenDeductOrigin(!openDeductOrigin)}
|
||||||
|
>
|
||||||
|
<span className="truncate">
|
||||||
|
{customers.find((customer) => customer.id === formField.deduct_origin)?.username || 'Search customer...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{openDeductOrigin && (
|
||||||
|
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-52 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg">
|
||||||
|
<div className="sticky top-0 bg-white p-2 border-b">
|
||||||
|
<Input
|
||||||
|
className="h-8 text-sm"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search customer..."
|
||||||
|
value={customerSearchTerm}
|
||||||
|
onChange={(e) => setCustomerSearchTerm(e.target.value)}
|
||||||
|
autoComplete="off"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{customers
|
||||||
|
.filter(
|
||||||
|
(customer) =>
|
||||||
|
customer.username.toLowerCase().includes(customerSearchTerm.toLowerCase()) ||
|
||||||
|
customer.msisdn.includes(customerSearchTerm)
|
||||||
|
)
|
||||||
|
.map((customer) => (
|
||||||
|
<div
|
||||||
|
key={customer.id}
|
||||||
|
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
||||||
|
onClick={() => {
|
||||||
|
setFormField({ ...formField, deduct_origin: customer.id });
|
||||||
|
setOpenDeductOrigin(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{customer.username}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{customers.filter(
|
||||||
|
(customer) =>
|
||||||
|
customer.username.toLowerCase().includes(customerSearchTerm.toLowerCase()) ||
|
||||||
|
customer.msisdn.includes(customerSearchTerm)
|
||||||
|
).length === 0 && (
|
||||||
|
<div className="px-3 py-2 text-sm text-gray-500">No customer found</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<label className="form-label">
|
||||||
|
Deduct From Account <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
{renderSelectWithLoading(
|
{renderSelectWithLoading(
|
||||||
formField.deduct_from_account,
|
formField.deduct_from_account,
|
||||||
@ -653,7 +721,6 @@ const EditFeeDialog = () => {
|
|||||||
isLoadingWallets
|
isLoadingWallets
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Credit To <span className="text-red-500">*</span>
|
Credit To <span className="text-red-500">*</span>
|
||||||
@ -688,17 +755,15 @@ const EditFeeDialog = () => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div
|
||||||
className="flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
|
className="flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpenCreditDestination(!openCreditDestination)}
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{customers.find(
|
{customers.find((customer) => customer.id === formField.credit_destination)
|
||||||
(customer) => customer.id === formField.credit_destination
|
?.username || 'Search customer...'}
|
||||||
)?.username || 'Search customer...'}
|
|
||||||
</span>
|
</span>
|
||||||
<path d="m6 9 6 6 6-6"></path>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{open && (
|
{openCreditDestination && (
|
||||||
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-52 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg">
|
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-52 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg">
|
||||||
<div className="sticky top-0 bg-white p-2 border-b">
|
<div className="sticky top-0 bg-white p-2 border-b">
|
||||||
<Input
|
<Input
|
||||||
@ -730,7 +795,7 @@ const EditFeeDialog = () => {
|
|||||||
...formField,
|
...formField,
|
||||||
credit_destination: customer.id
|
credit_destination: customer.id
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpenCreditDestination(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.username}
|
{customer.username}
|
||||||
@ -743,9 +808,7 @@ const EditFeeDialog = () => {
|
|||||||
.includes(customerSearchTerm.toLowerCase()) ||
|
.includes(customerSearchTerm.toLowerCase()) ||
|
||||||
customer.msisdn.includes(customerSearchTerm)
|
customer.msisdn.includes(customerSearchTerm)
|
||||||
).length === 0 && (
|
).length === 0 && (
|
||||||
<div className="px-3 py-2 text-sm text-gray-500">
|
<div className="px-3 py-2 text-sm text-gray-500">No customer found</div>
|
||||||
No customer found
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -754,6 +817,7 @@ const EditFeeDialog = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Credit Destination Account <span className="text-red-500">*</span>
|
Credit Destination Account <span className="text-red-500">*</span>
|
||||||
@ -805,24 +869,6 @@ const EditFeeDialog = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
|
||||||
<label className="form-label">
|
|
||||||
Priority <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<Select
|
|
||||||
value={formField.priority}
|
|
||||||
onValueChange={(value) => setFormField({ ...formField, priority: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select Priority" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Y">Yes</SelectItem>
|
|
||||||
<SelectItem value="N">No</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end pt-2.5 gap-5">
|
<div className="flex justify-end pt-2.5 gap-5">
|
||||||
<Button variant={'outline'} type="button" onClick={resetForm}>
|
<Button variant={'outline'} type="button" onClick={resetForm}>
|
||||||
Reset
|
Reset
|
||||||
|
|||||||
@ -143,14 +143,6 @@ const ManageTransferFeeContextProvider = ({
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: { headerClassName: 'w-[150px]' }
|
meta: { headerClassName: 'w-[150px]' }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorFn: (row) => row.fee_amount,
|
|
||||||
id: 'fee_amount',
|
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Fee Amount" column={column} />,
|
|
||||||
enableSorting: true,
|
|
||||||
enableHiding: false,
|
|
||||||
meta: { headerClassName: 'w-[150px]' }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.deduct_amount,
|
accessorFn: (row) => row.deduct_amount,
|
||||||
id: 'deduct_amount',
|
id: 'deduct_amount',
|
||||||
@ -235,10 +227,11 @@ const ManageTransferFeeContextProvider = ({
|
|||||||
accessorFn: (row: { deduct_from: string }) => {
|
accessorFn: (row: { deduct_from: string }) => {
|
||||||
const mapping: Record<string, string> = {
|
const mapping: Record<string, string> = {
|
||||||
D: 'Destination Member',
|
D: 'Destination Member',
|
||||||
S: 'Source Member'
|
S: 'Source Member',
|
||||||
|
I: 'Input Customer'
|
||||||
};
|
};
|
||||||
|
|
||||||
return mapping[row.deduct_from];
|
return mapping[row.deduct_from] || 'Unknown';
|
||||||
},
|
},
|
||||||
id: 'deduct_from',
|
id: 'deduct_from',
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Deduct From" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Deduct From" column={column} />,
|
||||||
@ -247,38 +240,32 @@ const ManageTransferFeeContextProvider = ({
|
|||||||
meta: { headerClassName: 'w-[250px]' }
|
meta: { headerClassName: 'w-[250px]' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.deduct_from_account?.description,
|
accessorFn: (row) => {
|
||||||
|
if (row.deduct_from === 'S' || row.deduct_from === 'D' || !row.deduct_from_account) {
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
return row.deduct_from_account.description;
|
||||||
|
},
|
||||||
id: 'deduct_from_account',
|
id: 'deduct_from_account',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataGridColumnHeader title="Deduct From Account" column={column} />
|
<DataGridColumnHeader title="Deduct From Destination" column={column} />
|
||||||
),
|
),
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: { headerClassName: 'w-[250px]' }
|
meta: { headerClassName: 'w-[250px]' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.priority,
|
accessorFn: (row) => {
|
||||||
id: 'priority',
|
if (row.deduct_from === 'S' || row.deduct_from === 'D' || !row.deduct_origin) {
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Priority" column={column} />,
|
return 'N/A';
|
||||||
|
}
|
||||||
|
return row.deduct_origin?.fullname;
|
||||||
|
},
|
||||||
|
id: 'deduct_origin',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="Deduct Origin" column={column} />,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
meta: { headerClassName: 'w-[250px]' }
|
||||||
const isActive = row.original.priority === 'Y';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 text-xs font-semibold rounded-full ${
|
|
||||||
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{isActive ? 'Yes' : 'No'}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
headerClassName: 'w-[100px]',
|
|
||||||
cellClassName: 'text-center'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.status,
|
accessorFn: (row) => row.status,
|
||||||
|
|||||||
@ -378,6 +378,10 @@ const AddDialog = () => {
|
|||||||
thousandSeparator="."
|
thousandSeparator="."
|
||||||
decimalSeparator=","
|
decimalSeparator=","
|
||||||
allowNegative={false}
|
allowNegative={false}
|
||||||
|
decimalScale={0}
|
||||||
|
isAllowed={({ floatValue }) =>
|
||||||
|
floatValue === undefined || Number.isInteger(floatValue)
|
||||||
|
}
|
||||||
onValueChange={(values) => {
|
onValueChange={(values) => {
|
||||||
setFormField((prev) => ({
|
setFormField((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -561,6 +565,9 @@ const AddDialog = () => {
|
|||||||
<SelectItem value="N">Top Up Patner</SelectItem>
|
<SelectItem value="N">Top Up Patner</SelectItem>
|
||||||
<SelectItem value="E">Reward</SelectItem>
|
<SelectItem value="E">Reward</SelectItem>
|
||||||
<SelectItem value="L">Purchase Loja</SelectItem>
|
<SelectItem value="L">Purchase Loja</SelectItem>
|
||||||
|
<SelectItem value="B">Top Up P24</SelectItem>
|
||||||
|
<SelectItem value="A">Transfer Agent</SelectItem>
|
||||||
|
<SelectItem value="M">Withdraw Agent</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.status_kind && (
|
{errors.status_kind && (
|
||||||
|
|||||||
@ -574,6 +574,10 @@ const EditDialog = () => {
|
|||||||
thousandSeparator="."
|
thousandSeparator="."
|
||||||
decimalSeparator=","
|
decimalSeparator=","
|
||||||
allowNegative={false}
|
allowNegative={false}
|
||||||
|
decimalScale={0}
|
||||||
|
isAllowed={({ floatValue }) =>
|
||||||
|
floatValue === undefined || Number.isInteger(floatValue)
|
||||||
|
}
|
||||||
onValueChange={(values) => {
|
onValueChange={(values) => {
|
||||||
setFormField((prev) => ({
|
setFormField((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -759,6 +763,9 @@ const EditDialog = () => {
|
|||||||
<SelectItem value="N">Top Up Patner</SelectItem>
|
<SelectItem value="N">Top Up Patner</SelectItem>
|
||||||
<SelectItem value="E">Reward</SelectItem>
|
<SelectItem value="E">Reward</SelectItem>
|
||||||
<SelectItem value="L">Purchase Loja</SelectItem>
|
<SelectItem value="L">Purchase Loja</SelectItem>
|
||||||
|
<SelectItem value="B">Top Up P24</SelectItem>
|
||||||
|
<SelectItem value="A">Transfer Agent</SelectItem>
|
||||||
|
<SelectItem value="M">Withdraw Agent</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.status_kind && (
|
{errors.status_kind && (
|
||||||
|
|||||||
@ -207,6 +207,9 @@ const ListToolbar = () => {
|
|||||||
<SelectItem value="N">Top Up Patner</SelectItem>
|
<SelectItem value="N">Top Up Patner</SelectItem>
|
||||||
<SelectItem value="E">Reward</SelectItem>
|
<SelectItem value="E">Reward</SelectItem>
|
||||||
<SelectItem value="L">Purchase Loja</SelectItem>
|
<SelectItem value="L">Purchase Loja</SelectItem>
|
||||||
|
<SelectItem value="B">Top Up P24</SelectItem>
|
||||||
|
<SelectItem value="A">Transfer Agent</SelectItem>
|
||||||
|
<SelectItem value="M">Withdraw Agent</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -24,13 +24,19 @@ function useDebounce<T>(value: T, delay: number): T {
|
|||||||
|
|
||||||
const formatNumber = (num: number): string => {
|
const formatNumber = (num: number): string => {
|
||||||
return num.toLocaleString('en-US', {
|
return num.toLocaleString('en-US', {
|
||||||
style: 'currency',
|
style: 'decimal',
|
||||||
currency: 'USD',
|
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2
|
maximumFractionDigits: 2
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatInteger = (num: number): string => {
|
||||||
|
return num.toLocaleString('en-US', {
|
||||||
|
style: 'decimal',
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
interface AccountProps {
|
interface AccountProps {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -195,7 +201,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
|
|||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataGridColumnHeader title="Max Transaction Per Day" column={column} />
|
<DataGridColumnHeader title="Max Transaction Per Day" column={column} />
|
||||||
),
|
),
|
||||||
cell: ({ row }) => formatNumber(row.original.max_transaction_per_day),
|
cell: ({ row }) => formatInteger(row.original.max_transaction_per_day),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: { headerClassName: 'w-[250px]' }
|
meta: { headerClassName: 'w-[250px]' }
|
||||||
@ -278,7 +284,10 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
|
|||||||
U: { label: 'Top Up', className: 'bg-yellow-100 text-yellow-600' },
|
U: { label: 'Top Up', className: 'bg-yellow-100 text-yellow-600' },
|
||||||
N: { label: 'Top Up Patner', className: 'bg-purple-100 text-purple-600' },
|
N: { label: 'Top Up Patner', className: 'bg-purple-100 text-purple-600' },
|
||||||
E: { label: 'Reward', className: 'bg-rose-100 text-rose-600' },
|
E: { label: 'Reward', className: 'bg-rose-100 text-rose-600' },
|
||||||
L: { label: 'Purchase Loja', className: 'bg-rose-100 text-rose-600' }
|
L: { label: 'Purchase Loja', className: 'bg-rose-100 text-rose-600' },
|
||||||
|
B: { label: 'Top Up P24', className: 'bg-rose-100 text-rose-600' },
|
||||||
|
A: { label: 'Transfer Agent', className: 'bg-rose-100 text-rose-600' },
|
||||||
|
M: { label: 'Withdraw Agent', className: 'bg-rose-100 text-rose-600' }
|
||||||
};
|
};
|
||||||
|
|
||||||
const kindInfo = mapping[kind] || {
|
const kindInfo = mapping[kind] || {
|
||||||
@ -375,8 +384,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
|
|||||||
filterObject[filter.id] = filter.value;
|
filterObject[filter.id] = filter.value;
|
||||||
} else if (filter.id === 'type') {
|
} else if (filter.id === 'type') {
|
||||||
filterObject[filter.id] = filter.value;
|
filterObject[filter.id] = filter.value;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
filterObject[filter.id] = filter.value.toLowerCase();
|
filterObject[filter.id] = filter.value.toLowerCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user