[skip ci] update search customer on add and edit transferfee
This commit is contained in:
@ -9,7 +9,7 @@ import {
|
|||||||
KeenIcon,
|
KeenIcon,
|
||||||
useDataGrid
|
useDataGrid
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCallApi } from '@/hooks';
|
import { useCallApi } from '@/hooks';3
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogBody,
|
DialogBody,
|
||||||
@ -41,6 +41,7 @@ import {
|
|||||||
TableRow
|
TableRow
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { apiConfig } from '@/config/api.config';
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { set } from 'date-fns';
|
||||||
|
|
||||||
interface TransactionTypeProps {
|
interface TransactionTypeProps {
|
||||||
id: string;
|
id: string;
|
||||||
@ -52,7 +53,7 @@ interface WalletProps {
|
|||||||
}
|
}
|
||||||
interface CustomerProps {
|
interface CustomerProps {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
fullname: string;
|
||||||
msisdn: string;
|
msisdn: string;
|
||||||
}
|
}
|
||||||
const API_URL = apiConfig.service_transaction;
|
const API_URL = apiConfig.service_transaction;
|
||||||
@ -70,7 +71,9 @@ const AddFeeDialog = () => {
|
|||||||
selectedTransferFee,
|
selectedTransferFee,
|
||||||
transactionTypeId
|
transactionTypeId
|
||||||
} = useManageTransferFeeContext();
|
} = useManageTransferFeeContext();
|
||||||
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
|
const [deductSearchTerm, setDeductSearchTerm] = useState('');
|
||||||
|
const [creditSearchTerm, setCreditSearchTerm] = useState('');
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const [alert, setAlert] = useState({
|
const [alert, setAlert] = useState({
|
||||||
@ -79,17 +82,14 @@ const AddFeeDialog = () => {
|
|||||||
});
|
});
|
||||||
const [transactionTypes, setTransactionTypes] = useState<TransactionTypeProps[]>([]);
|
const [transactionTypes, setTransactionTypes] = useState<TransactionTypeProps[]>([]);
|
||||||
const [wallets, setWallets] = useState<WalletProps[]>([]);
|
const [wallets, setWallets] = useState<WalletProps[]>([]);
|
||||||
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
const [deductFilteredCustomers, setDeductFilteredCustomers] = useState<CustomerProps[]>([]);
|
||||||
const [defaultCustomer, setDefaultCustomer] = useState('');
|
const [creditFilteredCustomers, setCreditFilteredCustomers] = useState<CustomerProps[]>([]);
|
||||||
|
const [isSearchingDeductCustomers, setIsSearchingDeductCustomers] = useState(false);
|
||||||
|
const [isSearchingCreditCustomers, setIsSearchingCreditCustomers] = useState(false);
|
||||||
const [transactionTypeName, setTransactionTypeName] = useState('');
|
const [transactionTypeName, setTransactionTypeName] = useState('');
|
||||||
|
|
||||||
const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false);
|
const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false);
|
||||||
const [isLoadingWallets, setIsLoadingWallets] = useState(false);
|
const [isLoadingWallets, setIsLoadingWallets] = useState(false);
|
||||||
const [isLoadingCustomers, setIsLoadingCustomers] = useState(false);
|
|
||||||
const customersWithNames = customers.map((customer) => ({
|
|
||||||
id: customer.id,
|
|
||||||
name: customer.username
|
|
||||||
}));
|
|
||||||
|
|
||||||
const [openDeductOrigin, setOpenDeductOrigin] = useState(false);
|
const [openDeductOrigin, setOpenDeductOrigin] = useState(false);
|
||||||
const [openCreditDestination, setOpenCreditDestination] = useState(false);
|
const [openCreditDestination, setOpenCreditDestination] = useState(false);
|
||||||
@ -120,15 +120,20 @@ const AddFeeDialog = () => {
|
|||||||
|
|
||||||
const [formField, setFormField] = useState(initialState);
|
const [formField, setFormField] = useState(initialState);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormField(initialState);
|
setFormField(initialState);
|
||||||
setTransactionTypeName('');
|
setTransactionTypeName('');
|
||||||
};
|
setDeductFilteredCustomers([]);
|
||||||
|
setCreditFilteredCustomers([]);
|
||||||
|
setDeductSearchTerm('');
|
||||||
|
setCreditSearchTerm('');
|
||||||
|
};
|
||||||
|
|
||||||
const handleCloseDialog = () => {
|
const handleCloseDialog = () => {
|
||||||
setFormField(initialState);
|
setFormField(initialState);
|
||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
setCustomerSearchTerm('');
|
setDeductSearchTerm('');
|
||||||
|
setCreditSearchTerm('');
|
||||||
setOpenCreditDestination(false);
|
setOpenCreditDestination(false);
|
||||||
setOpenDeductOrigin(false);
|
setOpenDeductOrigin(false);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@ -139,6 +144,24 @@ const AddFeeDialog = () => {
|
|||||||
const [showTransactionFeeDialog, setShowTransactionFeeDialog] = useState(false);
|
const [showTransactionFeeDialog, setShowTransactionFeeDialog] = useState(false);
|
||||||
const parsedUser = getAuth()?.user;
|
const parsedUser = getAuth()?.user;
|
||||||
|
|
||||||
|
|
||||||
|
const useDebounce = (value: string, delay: number) => {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
};
|
||||||
|
const debouncedDeductSearchTerm = useDebounce(deductSearchTerm, 500);
|
||||||
|
const debouncedCreditSearchTerm = useDebounce(creditSearchTerm, 500);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showAddFeeDialog && transactionTypeId) {
|
if (showAddFeeDialog && transactionTypeId) {
|
||||||
setIsLoadingTransactionType(true);
|
setIsLoadingTransactionType(true);
|
||||||
@ -170,7 +193,8 @@ const AddFeeDialog = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showAddFeeDialog) {
|
if (!showAddFeeDialog) {
|
||||||
setCustomerSearchTerm('');
|
setDeductSearchTerm('');
|
||||||
|
setCreditSearchTerm('');
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
}, [showAddFeeDialog]);
|
}, [showAddFeeDialog]);
|
||||||
@ -241,37 +265,89 @@ const AddFeeDialog = () => {
|
|||||||
if (!showAddFeeDialog) return;
|
if (!showAddFeeDialog) return;
|
||||||
fetchWallets();
|
fetchWallets();
|
||||||
}, [showAddFeeDialog, fetchWallets]);
|
}, [showAddFeeDialog, fetchWallets]);
|
||||||
|
|
||||||
useEffect(() => {
|
const searchDeductCustomers = useCallback(
|
||||||
if (!showAddFeeDialog) return;
|
async (searchTerm: string) => {
|
||||||
const getCustomerList = async (sorting: any) => {
|
setIsSearchingDeductCustomers(true);
|
||||||
setIsLoadingCustomers(true);
|
|
||||||
try {
|
try {
|
||||||
sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting;
|
let response;
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
limit: 100,
|
limit: 20,
|
||||||
page: 1,
|
page: 1,
|
||||||
with_deleted: false,
|
with_deleted: false,
|
||||||
order_field: sorting[0].id,
|
order_field: 'username',
|
||||||
order_direction: sorting[0].desc ? 'DESC' : 'ASC'
|
order_direction: 'ASC'
|
||||||
});
|
});
|
||||||
const customerList = response?.data.list || [];
|
} else {
|
||||||
setCustomers(customerList);
|
const filters = [{ id: 'fullname', value: searchTerm.trim() }];
|
||||||
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
if (customerList.length > 0) {
|
limit: 20,
|
||||||
setDefaultCustomer(customerList[0].id);
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'username',
|
||||||
|
order_direction: 'ASC',
|
||||||
|
filter: JSON.stringify(filters),
|
||||||
|
specialFilter: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
setDeductFilteredCustomers(response?.data?.list || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching customers', error);
|
console.error('Error searching deduct customers', error);
|
||||||
setCustomers([]);
|
setDeductFilteredCustomers([]);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingCustomers(false);
|
setIsSearchingDeductCustomers(false);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[GetData]
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (openDeductOrigin && formField.deduct_from === 'I') {
|
||||||
|
searchDeductCustomers(debouncedDeductSearchTerm);
|
||||||
|
}
|
||||||
|
}, [debouncedDeductSearchTerm, openDeductOrigin, formField.deduct_from, searchDeductCustomers]);
|
||||||
|
|
||||||
getCustomerList([{ id: 'id', desc: false }]);
|
|
||||||
}, [showAddFeeDialog, GetData]);
|
const searchCreditCustomers = useCallback(
|
||||||
|
async (searchTerm: string) => {
|
||||||
|
setIsSearchingCreditCustomers(true);
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
|
limit: 20,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'username',
|
||||||
|
order_direction: 'ASC'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const filters = [{ id: 'fullname', value: searchTerm.trim() }];
|
||||||
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
|
limit: 20,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'username',
|
||||||
|
order_direction: 'ASC',
|
||||||
|
filter: JSON.stringify(filters),
|
||||||
|
specialFilter: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setCreditFilteredCustomers(response?.data?.list || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching credit customers', error);
|
||||||
|
setCreditFilteredCustomers([]);
|
||||||
|
} finally {
|
||||||
|
setIsSearchingCreditCustomers(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[GetData]
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (openCreditDestination && formField.credit_to === 'I') {
|
||||||
|
searchCreditCustomers(debouncedCreditSearchTerm);
|
||||||
|
}
|
||||||
|
}, [debouncedCreditSearchTerm, openCreditDestination, formField.credit_to, searchCreditCustomers]);
|
||||||
|
|
||||||
const doCreateTransferType = useCallback(
|
const doCreateTransferType = useCallback(
|
||||||
async (e: React.FormEvent<HTMLFormElement>) => {
|
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
@ -624,11 +700,17 @@ 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={() => setOpenDeductOrigin(!openDeductOrigin)}
|
onClick={() => {
|
||||||
|
setOpenDeductOrigin(!openDeductOrigin);
|
||||||
|
if (!openDeductOrigin && deductSearchTerm.trim() === '') {
|
||||||
|
searchDeductCustomers('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{customers.find((customer) => customer.id === formField.deduct_origin)
|
{deductFilteredCustomers.find(
|
||||||
?.username || 'Search customer...'}
|
(customer) => customer.id === formField.deduct_origin
|
||||||
|
)?.fullname || 'Search customer...'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -639,45 +721,41 @@ const AddFeeDialog = () => {
|
|||||||
className="h-8 text-sm"
|
className="h-8 text-sm"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search customer..."
|
placeholder="Search customer..."
|
||||||
value={customerSearchTerm}
|
value={deductSearchTerm}
|
||||||
onChange={(e) => setCustomerSearchTerm(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setDeductSearchTerm(value);
|
||||||
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{customers
|
{isSearchingDeductCustomers ? (
|
||||||
.filter(
|
<div className="px-3 py-2 text-sm text-gray-500">Searching...</div>
|
||||||
(customer) =>
|
) : (
|
||||||
customer.username
|
deductFilteredCustomers.map((customer) => (
|
||||||
.toLowerCase()
|
|
||||||
.includes(customerSearchTerm.toLowerCase()) ||
|
|
||||||
customer.msisdn.includes(customerSearchTerm)
|
|
||||||
)
|
|
||||||
.map((customer) => (
|
|
||||||
<div
|
<div
|
||||||
key={customer.id}
|
key={customer.id}
|
||||||
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFormField({ ...formField, deduct_origin: customer.id });
|
setFormField({ ...formField, deduct_origin: customer.id });
|
||||||
setOpenDeductOrigin(false);
|
setOpenDeductOrigin(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.username}
|
{customer.fullname}
|
||||||
</div>
|
</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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isSearchingDeductCustomers &&
|
||||||
|
deductFilteredCustomers.length === 0 &&
|
||||||
|
deductSearchTerm.trim() && (
|
||||||
|
<div className="px-3 py-2 text-sm text-gray-500">
|
||||||
|
No customer found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -723,7 +801,7 @@ const AddFeeDialog = () => {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
{formField.credit_to === 'I' && (
|
{formField.credit_to === 'I' && (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="form-label">
|
<label className="form-label">
|
||||||
Credit Destination <span className="text-red-500">*</span>
|
Credit Destination <span className="text-red-500">*</span>
|
||||||
@ -731,12 +809,17 @@ 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={() => setOpenCreditDestination(!openCreditDestination)}
|
onClick={() => {
|
||||||
|
setOpenCreditDestination(!openCreditDestination);
|
||||||
|
if (!openCreditDestination && creditSearchTerm.trim() === '') {
|
||||||
|
searchCreditCustomers('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{customers.find(
|
{creditFilteredCustomers.find(
|
||||||
(customer) => customer.id === formField.credit_destination
|
(customer) => customer.id === formField.credit_destination
|
||||||
)?.username || 'Search customer...'}
|
)?.fullname || 'Search customer...'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -747,48 +830,41 @@ const AddFeeDialog = () => {
|
|||||||
className="h-8 text-sm"
|
className="h-8 text-sm"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search customer..."
|
placeholder="Search customer..."
|
||||||
value={customerSearchTerm}
|
value={creditSearchTerm}
|
||||||
onChange={(e) => setCustomerSearchTerm(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setCreditSearchTerm(value);
|
||||||
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{customers
|
{isSearchingCreditCustomers ? (
|
||||||
.filter(
|
<div className="px-3 py-2 text-sm text-gray-500">Searching...</div>
|
||||||
(customer) =>
|
) : (
|
||||||
customer.username
|
creditFilteredCustomers.map((customer) => (
|
||||||
.toLowerCase()
|
|
||||||
.includes(customerSearchTerm.toLowerCase()) ||
|
|
||||||
customer.msisdn.includes(customerSearchTerm)
|
|
||||||
)
|
|
||||||
.map((customer) => (
|
|
||||||
<div
|
<div
|
||||||
key={customer.id}
|
key={customer.id}
|
||||||
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFormField({
|
setFormField({ ...formField, credit_destination: customer.id });
|
||||||
...formField,
|
setOpenCreditDestination(false);
|
||||||
credit_destination: customer.id
|
|
||||||
});
|
|
||||||
setOpenCreditDestination(false);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.username}
|
{customer.fullname}
|
||||||
</div>
|
</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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isSearchingCreditCustomers &&
|
||||||
|
creditFilteredCustomers.length === 0 &&
|
||||||
|
creditSearchTerm.trim() && (
|
||||||
|
<div className="px-3 py-2 text-sm text-gray-500">
|
||||||
|
No customer found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -875,12 +951,7 @@ const AddFeeDialog = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant={'default'}
|
variant={'default'}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={
|
disabled={isSubmitting || isLoadingTransactionType || isLoadingWallets}
|
||||||
isSubmitting ||
|
|
||||||
isLoadingTransactionType ||
|
|
||||||
isLoadingWallets ||
|
|
||||||
isLoadingCustomers
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -45,7 +45,7 @@ interface WalletProps {
|
|||||||
|
|
||||||
interface CustomerProps {
|
interface CustomerProps {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
fullname: string;
|
||||||
msisdn: string;
|
msisdn: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +59,12 @@ const EditFeeDialog = () => {
|
|||||||
const { showEditFeeDialog, handleEditFeeDialog, selectedTransferFee } =
|
const { showEditFeeDialog, handleEditFeeDialog, selectedTransferFee } =
|
||||||
useManageTransferFeeContext();
|
useManageTransferFeeContext();
|
||||||
const { reload } = useDataGrid();
|
const { reload } = useDataGrid();
|
||||||
|
const [deductSearchTerm, setDeductSearchTerm] = useState('');
|
||||||
|
const [creditSearchTerm, setCreditSearchTerm] = useState('');
|
||||||
|
const [deductFilteredCustomers, setDeductFilteredCustomers] = useState<CustomerProps[]>([]);
|
||||||
|
const [creditFilteredCustomers, setCreditFilteredCustomers] = useState<CustomerProps[]>([]);
|
||||||
|
const [isSearchingCustomers, setIsSearchingCustomers] = useState(false);
|
||||||
|
|
||||||
const { GetData, PutData } = useCallApi();
|
const { GetData, PutData } = useCallApi();
|
||||||
const parsedUser = getAuth()?.user;
|
const parsedUser = getAuth()?.user;
|
||||||
const [openDeductOrigin, setOpenDeductOrigin] = useState(false);
|
const [openDeductOrigin, setOpenDeductOrigin] = useState(false);
|
||||||
@ -70,7 +76,7 @@ const EditFeeDialog = () => {
|
|||||||
const [transactionTypeName, setTransactionTypeName] = useState('');
|
const [transactionTypeName, setTransactionTypeName] = useState('');
|
||||||
const customersWithNames = customers.map((customer) => ({
|
const customersWithNames = customers.map((customer) => ({
|
||||||
id: customer.id,
|
id: customer.id,
|
||||||
name: customer.username
|
name: customer.fullname
|
||||||
}));
|
}));
|
||||||
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
|
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
|
||||||
const [showCustomerSearch, setShowCustomerSearch] = useState(false);
|
const [showCustomerSearch, setShowCustomerSearch] = useState(false);
|
||||||
@ -80,6 +86,8 @@ const EditFeeDialog = () => {
|
|||||||
show: false,
|
show: false,
|
||||||
message: ''
|
message: ''
|
||||||
});
|
});
|
||||||
|
const [selectedDeductCustomer, setSelectedDeductCustomer] = useState<CustomerProps | null>(null);
|
||||||
|
const [selectedCreditCustomer, setSelectedCreditCustomer] = useState<CustomerProps | null>(null);
|
||||||
|
|
||||||
const [isLoadingTransferFee, setIsLoadingTransferFee] = useState(false);
|
const [isLoadingTransferFee, setIsLoadingTransferFee] = useState(false);
|
||||||
const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false);
|
const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false);
|
||||||
@ -110,7 +118,24 @@ const EditFeeDialog = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [formField, setFormField] = useState(initialState);
|
const [formField, setFormField] = useState(initialState);
|
||||||
|
const useDebounce = (value:string, delay:number) => {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedDeductSearch = useDebounce(deductSearchTerm, 300);
|
||||||
|
const debouncedCreditSearch = useDebounce(creditSearchTerm, 300);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updated_time = new Date();
|
const updated_time = new Date();
|
||||||
const formattedTime = updated_time.toISOString().slice(0, 19).replace('T', ' ');
|
const formattedTime = updated_time.toISOString().slice(0, 19).replace('T', ' ');
|
||||||
@ -204,7 +229,6 @@ const EditFeeDialog = () => {
|
|||||||
payload.deduct_origin = '00000000-0000-0000-0000-000000000000';
|
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}`,
|
||||||
@ -262,31 +286,89 @@ const EditFeeDialog = () => {
|
|||||||
if (!showEditFeeDialog) return;
|
if (!showEditFeeDialog) return;
|
||||||
fetchWallets();
|
fetchWallets();
|
||||||
}, [showEditFeeDialog, fetchWallets]);
|
}, [showEditFeeDialog, fetchWallets]);
|
||||||
|
const searchDeductCustomers = useCallback(
|
||||||
useEffect(() => {
|
async (searchTerm: string) => {
|
||||||
if (!showEditFeeDialog) return;
|
setIsSearchingCustomers(true);
|
||||||
|
|
||||||
const getCustomerList = async (sorting: any) => {
|
|
||||||
setIsLoadingCustomers(true);
|
|
||||||
try {
|
try {
|
||||||
sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting;
|
let response;
|
||||||
const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
if (!searchTerm.trim()) {
|
||||||
limit: 100,
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
page: 1,
|
limit: 20,
|
||||||
with_deleted: false,
|
page: 1,
|
||||||
order_field: sorting[0].id,
|
with_deleted: false,
|
||||||
order_direction: sorting[0].desc ? 'DESC' : 'ASC'
|
order_field: 'username',
|
||||||
});
|
order_direction: 'ASC'
|
||||||
setCustomers(response?.data.list || []);
|
});
|
||||||
|
} else {
|
||||||
|
const filters = [{ id: 'fullname', value: searchTerm.trim() }];
|
||||||
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
|
limit: 20,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'username',
|
||||||
|
order_direction: 'ASC',
|
||||||
|
filter: JSON.stringify(filters),
|
||||||
|
specialFilter: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setDeductFilteredCustomers(response?.data?.list || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching customers', error);
|
console.error('Error searching deduct customers', error);
|
||||||
|
setDeductFilteredCustomers([]);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingCustomers(false);
|
setIsSearchingCustomers(false);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[GetData]
|
||||||
|
);
|
||||||
|
|
||||||
getCustomerList([{ id: 'id', desc: false }]);
|
const searchCreditCustomers = useCallback(
|
||||||
}, [showEditFeeDialog, GetData]);
|
async (searchTerm: string) => {
|
||||||
|
setIsSearchingCustomers(true);
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
|
limit: 20,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'username',
|
||||||
|
order_direction: 'ASC'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const filters = [{ id: 'fullname', value: searchTerm.trim() }];
|
||||||
|
response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
|
limit: 20,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'username',
|
||||||
|
order_direction: 'ASC',
|
||||||
|
filter: JSON.stringify(filters),
|
||||||
|
specialFilter: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setCreditFilteredCustomers(response?.data?.list || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching credit customers', error);
|
||||||
|
setCreditFilteredCustomers([]);
|
||||||
|
} finally {
|
||||||
|
setIsSearchingCustomers(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[GetData]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openDeductOrigin) {
|
||||||
|
searchDeductCustomers(debouncedDeductSearch);
|
||||||
|
}
|
||||||
|
}, [debouncedDeductSearch, openDeductOrigin, searchDeductCustomers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openCreditDestination) {
|
||||||
|
searchCreditCustomers(debouncedCreditSearch);
|
||||||
|
}
|
||||||
|
}, [debouncedCreditSearch, openCreditDestination, searchCreditCustomers]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showEditFeeDialog) return;
|
if (!showEditFeeDialog) return;
|
||||||
@ -323,7 +405,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,
|
||||||
@ -354,6 +436,32 @@ const EditFeeDialog = () => {
|
|||||||
if (response.data.transaction_type?.name) {
|
if (response.data.transaction_type?.name) {
|
||||||
setTransactionTypeName(response.data.transaction_type.name);
|
setTransactionTypeName(response.data.transaction_type.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
response.data.deduct_origin &&
|
||||||
|
response.data.deduct_origin.id !== '00000000-0000-0000-0000-000000000000'
|
||||||
|
) {
|
||||||
|
setSelectedDeductCustomer({
|
||||||
|
id: response.data.deduct_origin.id,
|
||||||
|
fullname: response.data.deduct_origin.fullname || '',
|
||||||
|
msisdn: response.data.deduct_origin.msisdn || ''
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setSelectedDeductCustomer(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
response.data.credit_destination &&
|
||||||
|
response.data.credit_destination.id !== '00000000-0000-0000-0000-000000000000'
|
||||||
|
) {
|
||||||
|
setSelectedCreditCustomer({
|
||||||
|
id: response.data.credit_destination.id,
|
||||||
|
fullname: response.data.credit_destination.fullname || '',
|
||||||
|
msisdn: response.data.credit_destination.msisdn || ''
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setSelectedCreditCustomer(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching transaction fee details', error);
|
console.error('Error fetching transaction fee details', error);
|
||||||
@ -658,11 +766,19 @@ 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={() => setOpenDeductOrigin(!openDeductOrigin)}
|
onClick={() => {
|
||||||
|
setOpenDeductOrigin(!openDeductOrigin);
|
||||||
|
if (!openDeductOrigin && deductSearchTerm.trim() === '') {
|
||||||
|
searchDeductCustomers('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{customers.find((customer) => customer.id === formField.deduct_origin)
|
{selectedDeductCustomer
|
||||||
?.username || 'Search customer...'}
|
? selectedDeductCustomer.fullname
|
||||||
|
: deductFilteredCustomers.find(
|
||||||
|
(customer) => customer.id === formField.deduct_origin
|
||||||
|
)?.fullname || 'Search customer...'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -673,45 +789,44 @@ const EditFeeDialog = () => {
|
|||||||
className="h-8 text-sm"
|
className="h-8 text-sm"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search customer..."
|
placeholder="Search customer..."
|
||||||
value={customerSearchTerm}
|
value={deductSearchTerm}
|
||||||
onChange={(e) => setCustomerSearchTerm(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setDeductSearchTerm(value);
|
||||||
|
|
||||||
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{customers
|
{isSearchingCustomers ? (
|
||||||
.filter(
|
<div className="px-3 py-2 text-sm text-gray-500">Searching...</div>
|
||||||
(customer) =>
|
) : (
|
||||||
customer.username
|
deductFilteredCustomers.map((customer) => (
|
||||||
.toLowerCase()
|
|
||||||
.includes(customerSearchTerm.toLowerCase()) ||
|
|
||||||
customer.msisdn.includes(customerSearchTerm)
|
|
||||||
)
|
|
||||||
.map((customer) => (
|
|
||||||
<div
|
<div
|
||||||
key={customer.id}
|
key={customer.id}
|
||||||
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFormField({ ...formField, deduct_origin: customer.id });
|
setFormField({ ...formField, deduct_origin: customer.id });
|
||||||
|
setSelectedDeductCustomer(customer);
|
||||||
|
setDeductSearchTerm('');
|
||||||
setOpenDeductOrigin(false);
|
setOpenDeductOrigin(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.username}
|
{customer.fullname}
|
||||||
</div>
|
</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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isSearchingCustomers &&
|
||||||
|
deductFilteredCustomers.length === 0 &&
|
||||||
|
deductSearchTerm.trim() && (
|
||||||
|
<div className="px-3 py-2 text-sm text-gray-500">
|
||||||
|
No customer found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -763,12 +878,19 @@ 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={() => setOpenCreditDestination(!openCreditDestination)}
|
onClick={() => {
|
||||||
|
setOpenCreditDestination(!openCreditDestination);
|
||||||
|
if (!openCreditDestination && creditSearchTerm.trim() === '') {
|
||||||
|
searchCreditCustomers('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{customers.find(
|
{selectedCreditCustomer
|
||||||
(customer) => customer.id === formField.credit_destination
|
? selectedCreditCustomer.fullname
|
||||||
)?.username || 'Search customer...'}
|
: creditFilteredCustomers.find(
|
||||||
|
(customer) => customer.id === formField.credit_destination
|
||||||
|
)?.fullname || 'Search customer...'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -779,23 +901,21 @@ const EditFeeDialog = () => {
|
|||||||
className="h-8 text-sm"
|
className="h-8 text-sm"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search customer..."
|
placeholder="Search customer..."
|
||||||
value={customerSearchTerm}
|
value={creditSearchTerm}
|
||||||
onChange={(e) => setCustomerSearchTerm(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setCreditSearchTerm(value);
|
||||||
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{customers
|
{isSearchingCustomers ? (
|
||||||
.filter(
|
<div className="px-3 py-2 text-sm text-gray-500">Searching...</div>
|
||||||
(customer) =>
|
) : (
|
||||||
customer.username
|
creditFilteredCustomers.map((customer) => (
|
||||||
.toLowerCase()
|
|
||||||
.includes(customerSearchTerm.toLowerCase()) ||
|
|
||||||
customer.msisdn.includes(customerSearchTerm)
|
|
||||||
)
|
|
||||||
.map((customer) => (
|
|
||||||
<div
|
<div
|
||||||
key={customer.id}
|
key={customer.id}
|
||||||
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
className="cursor-pointer px-3 py-1.5 text-sm hover:bg-gray-100"
|
||||||
@ -803,24 +923,22 @@ const EditFeeDialog = () => {
|
|||||||
setFormField({
|
setFormField({
|
||||||
...formField,
|
...formField,
|
||||||
credit_destination: customer.id
|
credit_destination: customer.id
|
||||||
});
|
});
|
||||||
setOpenCreditDestination(false);
|
setOpenCreditDestination(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.username}
|
{customer.fullname}
|
||||||
</div>
|
</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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isSearchingCustomers &&
|
||||||
|
creditFilteredCustomers.length === 0 &&
|
||||||
|
creditSearchTerm.trim() && (
|
||||||
|
<div className="px-3 py-2 text-sm text-gray-500">
|
||||||
|
No customer found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user