import { useCallback, useEffect, useRef, useState } from 'react'; import { NumericFormat } from 'react-number-format'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Dialog, DialogBody, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { useManageTransferFeeContext } from '../hooks/useManageTransferFeeContext'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { apiConfig } from '@/config/api.config'; import { Alert, Container, DataGridInner, KeenIcon, useDataGrid } from '@/components'; import { toast } from 'sonner'; import { useCallApi } from '@/hooks'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import { getAuth } from '@/auth'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; const API_URL = apiConfig.service_transaction; const API_URL_MASTER_DATA = apiConfig.service_master_data; const API_URL_CUSTOMER = apiConfig.service_customer; interface WalletProps { id: string; name: string; } interface CustomerProps { id: string; fullname: string; msisdn: string; } interface TransactionTypeProps { id: string; name: string; } const EditFeeDialog = () => { const parentRef = useRef(null); const { showEditFeeDialog, handleEditFeeDialog, selectedTransferFee } = useManageTransferFeeContext(); const { reload } = useDataGrid(); const [deductSearchTerm, setDeductSearchTerm] = useState(''); const [creditSearchTerm, setCreditSearchTerm] = useState(''); const [deductFilteredCustomers, setDeductFilteredCustomers] = useState([]); const [creditFilteredCustomers, setCreditFilteredCustomers] = useState([]); const [isSearchingCustomers, setIsSearchingCustomers] = useState(false); const { GetData, PutData } = useCallApi(); const parsedUser = getAuth()?.user; const [openDeductOrigin, setOpenDeductOrigin] = useState(false); const [openCreditDestination, setOpenCreditDestination] = useState(false); const [wallets, setWallets] = useState([]); const [customers, setCustomers] = useState([]); const [transactionTypes, setTransactionTypes] = useState([]); const [transactionTypeName, setTransactionTypeName] = useState(''); const customersWithNames = customers.map((customer) => ({ id: customer.id, name: customer.fullname })); const [customerSearchTerm, setCustomerSearchTerm] = useState(''); const [showCustomerSearch, setShowCustomerSearch] = useState(false); const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [alert, setAlert] = useState({ show: false, message: '' }); const [selectedDeductCustomer, setSelectedDeductCustomer] = useState(null); const [selectedCreditCustomer, setSelectedCreditCustomer] = useState(null); const [isLoadingTransferFee, setIsLoadingTransferFee] = useState(false); const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false); const [isLoadingWallets, setIsLoadingWallets] = useState(false); const [isLoadingCustomers, setIsLoadingCustomers] = useState(false); const initialState = { name: '', description: '', transaction_type: '', minimum_amount: 0, maximum_amount: 0, period_start: '', period_end: '', deduct_amount: 0, deduct_percentage: 0, status: '', status_include: '', updated_by: '', updated_at: '', deduct_from: '', deduct_origin: '00000000-0000-0000-0000-000000000000', deduct_from_account: '', credit_to: '', credit_destination: '00000000-0000-0000-0000-000000000000', credit_destination_account: '', deduct_when: 'A' }; 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(() => { const updated_time = new Date(); const formattedTime = updated_time.toISOString().slice(0, 19).replace('T', ' '); if (showEditFeeDialog) { setFormField((prevState) => ({ ...prevState, updated_by: parsedUser?.username, updated_at: formattedTime })); } }, [showEditFeeDialog, parsedUser?.username]); const resetForm = () => { if (selectedTransferFee) { fetchTransactionFee(selectedTransferFee); } else { setFormField(initialState); } }; const doUpdateTransferFee = useCallback( async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); const requiredFields = [ 'name', 'description', 'period_start', 'period_end', 'transaction_type', 'status', 'status_include', 'deduct_from', 'deduct_from_account', 'credit_to', 'credit_destination_account' ]; for (const field of requiredFields) { if (formField[field as keyof typeof formField] === '') { setAlert({ show: true, message: `All required fields must be filled out. Missing: ${field.replace(/_/g, ' ')}` }); setIsSubmitting(false); return; } } if (formField.status_include === 'Y' && !formField.deduct_origin) { setAlert({ show: true, message: 'Deduct Origin is required when Input is selected' }); setIsSubmitting(false); return; } if (formField.credit_to === 'I' && !formField.credit_destination) { setAlert({ show: true, message: 'Credit destination is required when Input Customer is selected' }); setIsSubmitting(false); 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: '' }); const payload = { ...formField }; if (formField.credit_to !== 'I') { payload.credit_destination = '00000000-0000-0000-0000-000000000000'; } if (formField.deduct_from !== 'I') { payload.deduct_origin = '00000000-0000-0000-0000-000000000000'; } if (formField.status_include !== 'Y') { payload.deduct_origin = '00000000-0000-0000-0000-000000000000'; } try { const response = await PutData( `${API_URL}/transactionfees/update/${selectedTransferFee}`, payload ); if (response?.status) { reload(); handleEditFeeDialog(false, null); toast.success('Successfully updated transfer fee'); const createActivity = { module: 'Manage Transfer Fee', description: `Edit Transfer Fee => ${formField.name}`, action: 'U' }; doSaveLogActivity(createActivity); } else { setAlert({ show: true, message: response?.message || 'Failed to update transfer fee' }); } } catch (error) { console.error('Error updating transfer fee', error); setAlert({ show: true, message: 'An error occurred while updating the transfer fee' }); } finally { setIsSubmitting(false); } }, [formField, selectedTransferFee, handleEditFeeDialog] ); const fetchWallets = useCallback(async () => { setIsLoadingWallets(true); const params = { limit: 100, page: 1, with_deleted: false, order_field: 'Wallets.name', order_direction: 'ASC' }; try { const response = await GetData(`${API_URL_MASTER_DATA}/wallet/list`, params); if (response?.status && response?.data) { setWallets(response.data.list); } else { setWallets([]); } } catch (error) { console.error('Error fetching wallets', error); setWallets([]); } finally { setIsLoadingWallets(false); } }, [GetData]); useEffect(() => { if (!showEditFeeDialog) return; fetchWallets(); }, [showEditFeeDialog, fetchWallets]); const searchDeductCustomers = useCallback( 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 }); } setDeductFilteredCustomers(response?.data?.list || []); } catch (error) { console.error('Error searching deduct customers', error); setDeductFilteredCustomers([]); } finally { setIsSearchingCustomers(false); } }, [GetData] ); const searchCreditCustomers = useCallback( 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(() => { if (!showEditFeeDialog) return; const getTransactionTypeList = async (sorting: any) => { setIsLoadingTransactionType(true); try { sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; const response = await GetData(`${API_URL}/transactiontype/list `, { limit: 100, page: 1, with_deleted: false, order_field: sorting[0].id, order_direction: sorting[0].desc ? 'DESC' : 'ASC' }); setTransactionTypes(response?.data.list || []); } catch (error) { console.error('Error fetching transaction types', error); } finally { setIsLoadingTransactionType(false); } }; getTransactionTypeList([{ id: 'id', desc: false }]); }, [showEditFeeDialog, GetData]); const formatDate = (dateString: string) => { if (!dateString || dateString.includes('0001-01-01')) return ''; return dateString.split('T')[0]; }; const fetchTransactionFee = useCallback( async (id: string) => { setIsLoadingTransferFee(true); try { const response = await GetData(`${API_URL}/transactionfees/getdata/${id}`, {}); if (response?.status) { setFormField({ ...initialState, name: response.data.name || '', description: response.data.description || '', transaction_type: response.data.transaction_type?.id || '', minimum_amount: response.data.minimum_amount || 0, maximum_amount: response.data.maximum_amount || 0, period_start: formatDate(response.data.period_start), period_end: formatDate(response.data.period_end), deduct_amount: response.data.deduct_amount || 0, deduct_percentage: response.data.deduct_percentage || 0, status: response.data.status || '', status_include: response.data.status_include || '', deduct_from: response.data.deduct_from || '', 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_destination: response.data.credit_destination?.id || '00000000-0000-0000-0000-000000000000', credit_destination_account: response.data.credit_destination_account?.id || '', updated_by: parsedUser?.username, updated_at: new Date().toISOString().slice(0, 19).replace('T', ' '), deduct_when: response.data.deduct_when || 'A' }); if (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) { console.error('Error fetching transaction fee details', error); setAlert({ show: true, message: 'Failed to fetch transaction fee details' }); } finally { setIsLoadingTransferFee(false); } }, [GetData, parsedUser?.username] ); const hasFetchedRef = useRef(false); useEffect(() => { if (selectedTransferFee && showEditFeeDialog && !hasFetchedRef.current) { fetchTransactionFee(selectedTransferFee); hasFetchedRef.current = true; } if (!showEditFeeDialog) { hasFetchedRef.current = false; } }, [selectedTransferFee, showEditFeeDialog, fetchTransactionFee]); const handleCloseDialog = () => { setFormField(initialState); setAlert({ show: false, message: '' }); setCustomerSearchTerm(''); setOpenCreditDestination(false); setOpenDeductOrigin(false); setOpen(false); handleEditFeeDialog(false, null); }; useEffect(() => { if (!showEditFeeDialog) { setCustomerSearchTerm(''); setOpen(false); } }, [showEditFeeDialog]); const renderSelectWithLoading = ( value: string, onChangeHandler: (value: string) => void, options: { id: string; name: string }[] | null, placeholder: string, isLoading: boolean ) => { return ( ); }; return ( { if (!open) { handleCloseDialog(); } }} >

Edit Transfer Fee

{alert.show && (

{alert.message}

)} {isLoadingTransferFee ? (

Loading transfer fee details...

) : (
{isLoadingTransactionType && (
Loading transaction type...
)}
setFormField((prev) => ({ ...prev, name: target.value })) } />
setFormField((prev) => ({ ...prev, description: target.value })) } />
{ setFormField((prev) => ({ ...prev, minimum_amount: values.floatValue || 0 })); }} placeholder="Enter Minimum Amount" />
{ setFormField((prev) => ({ ...prev, maximum_amount: values.floatValue || 0 })); }} placeholder="Enter Maximum Amount" />
setFormField((prev) => ({ ...prev, period_start: target.value })) } />
setFormField((prev) => ({ ...prev, period_end: target.value })) } />
{ setFormField((prev) => ({ ...prev, deduct_amount: values.floatValue || 0 })); }} placeholder="Enter Deduct Amount" />
{ setFormField((prev) => ({ ...prev, deduct_percentage: values.floatValue || 0 })); }} placeholder="Enter Deduct Percentage" />
{formField.deduct_from === 'I' && (
{ setOpenDeductOrigin(!openDeductOrigin); if (!openDeductOrigin && deductSearchTerm.trim() === '') { searchDeductCustomers(''); } }} > {selectedDeductCustomer ? selectedDeductCustomer.fullname : deductFilteredCustomers.find( (customer) => customer.id === formField.deduct_origin )?.fullname || 'Search customer...'}
{openDeductOrigin && (
{ const value = e.target.value; setDeductSearchTerm(value); }} autoComplete="off" onClick={(e) => e.stopPropagation()} autoFocus />
{isSearchingCustomers ? (
Searching...
) : ( deductFilteredCustomers.map((customer) => (
{ setFormField({ ...formField, deduct_origin: customer.id }); setSelectedDeductCustomer(customer); setDeductSearchTerm(''); setOpenDeductOrigin(false); }} > {customer.fullname}
)) )} {!isSearchingCustomers && deductFilteredCustomers.length === 0 && deductSearchTerm.trim() && (
No customer found
)}
)}
)}
{renderSelectWithLoading( formField.deduct_from_account, (value) => setFormField({ ...formField, deduct_from_account: value }), wallets, 'Select Wallet', isLoadingWallets )}
{formField.credit_to === 'I' && (
{ setOpenCreditDestination(!openCreditDestination); if (!openCreditDestination && creditSearchTerm.trim() === '') { searchCreditCustomers(''); } }} > {selectedCreditCustomer ? selectedCreditCustomer.fullname : creditFilteredCustomers.find( (customer) => customer.id === formField.credit_destination )?.fullname || 'Search customer...'}
{openCreditDestination && (
{ const value = e.target.value; setCreditSearchTerm(value); }} autoComplete="off" onClick={(e) => e.stopPropagation()} autoFocus />
{isSearchingCustomers ? (
Searching...
) : ( creditFilteredCustomers.map((customer) => (
{ setFormField({ ...formField, credit_destination: customer.id }); setOpenCreditDestination(false); }} > {customer.fullname}
)) )} {!isSearchingCustomers && creditFilteredCustomers.length === 0 && creditSearchTerm.trim() && (
No customer found
)}
)}
)}
{renderSelectWithLoading( formField.credit_destination_account, (value) => setFormField({ ...formField, credit_destination_account: value }), wallets, 'Select Wallet', isLoadingWallets )}
{formField.status_include === 'Y' && (
)}
)}
); }; export { EditFeeDialog };