This commit is contained in:
Raja Oktafrianto
2025-06-09 11:20:46 +07:00
4 changed files with 379 additions and 189 deletions

View File

@ -109,7 +109,8 @@ const AccountUserProfileContextProvider = ({ children }: { children: React.React
const handleSetPinCode = async (newPinCode: PinCodePayload) => { const handleSetPinCode = async (newPinCode: PinCodePayload) => {
try { try {
setPincodeState(newPinCode); setPincodeState(newPinCode);
const customerid = getAuth()?.user?.customer?.id; // const customerid = getAuth()?.user?.customer?.id;
const customerid = getAuth()?.user?.id;
const response = await PutData(`${API_URL2}/customer/pin/${customerid}`, { const response = await PutData(`${API_URL2}/customer/pin/${customerid}`, {
old_pin: newPinCode.currentpincode, old_pin: newPinCode.currentpincode,
new_pin: newPinCode.newpincode, new_pin: newPinCode.newpincode,

View File

@ -292,7 +292,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
setSelectedTransactionId(row.id); setSelectedTransactionId(row.id);
setShowRollbackDialog(true); setShowRollbackDialog(true);
}} }}
disabled={row.status !== 'C'} disabled={!['C', 'O'].includes(row.status)}
> >
<KeenIcon icon="notepad-edit" /> <KeenIcon icon="notepad-edit" />
</button> </button>
@ -360,7 +360,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
// Build formattedFilter setelah semua nilai diketahui // Build formattedFilter setelah semua nilai diketahui
const formattedFilter: any = {}; const formattedFilter: any = {};
if( status) { if (status) {
formattedFilter["Transactions.status"] = status; formattedFilter["Transactions.status"] = status;
} }

View File

@ -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>

View File

@ -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>
)} )}