diff --git a/src/App.tsx b/src/App.tsx index 44c0337..a72d810 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,7 +27,7 @@ const App = () => { - + ); }; diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx index 5f5ff7b..65c070c 100644 --- a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx @@ -6,12 +6,12 @@ import { apiConfig } from '@/config/api.config'; import { useEffect, useState } from 'react'; import moment from 'moment'; import { - Dialog, - DialogBody, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle } from '@/components/ui/dialog'; import TransactionLogViewer from './DetailTransactionLog'; @@ -29,125 +29,182 @@ const statusMap: Record = { W: { label: 'Waiting Schedule', bg: 'bg-yellow-100', text: 'text-yellow-600' }, O: { label: 'On Process', bg: 'bg-blue-100', text: 'text-blue-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) => { - const status = statusRaw as StatusCode; - const { label, bg, text } = statusMap[status] ?? { - label: 'Unknown', - bg: 'bg-gray-100', - text: 'text-gray-600', - }; + const status = statusRaw as StatusCode; + const { label, bg, text } = statusMap[status] ?? { + label: 'Unknown', + bg: 'bg-gray-100', + text: 'text-gray-600' + }; - return ( - - {label} - - ); + return ( + {label} + ); }; const DetailTransaction = () => { - const { GetData } = useCallApi(); - const { - showDetailDialog, - setShowDetailDialog, - selectedTransactionId, - setShowDetailLogDialog, - setDetailLogData - } = useTransactionContext(); + const { GetData } = useCallApi(); + const { + showDetailDialog, + setShowDetailDialog, + selectedTransactionId, + setShowDetailLogDialog, + setDetailLogData + } = useTransactionContext(); - const [transactionDetails, setTransactionDetails] = useState(null); + const [transactionDetails, setTransactionDetails] = useState(null); + const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - const fetchTransactionDetails = async () => { - if (selectedTransactionId) { - try { - const response = await GetData(`${API_URL}/transaction/history/${selectedTransactionId}`, { - id: selectedTransactionId - }); - // console.log(response?.data); - setTransactionDetails(response?.data); - } catch (error) { - console.error('Error fetching transaction', error); - } + useEffect(() => { + const fetchTransactionDetails = async () => { + setIsLoading(true); + if (selectedTransactionId) { + try { + const response = await GetData( + `${API_URL}/transaction/history/${selectedTransactionId}`, + { + id: selectedTransactionId } - }; - - if (showDetailDialog && selectedTransactionId) { - fetchTransactionDetails(); + ); + // console.log(response?.data); + setTransactionDetails(response?.data); + } catch (error) { + console.error('Error fetching transaction', error); + } finally { + setIsLoading(false); } - }, [showDetailDialog, selectedTransactionId, GetData]); + } + }; - const [activeTab, setActiveTab] = useState('detail'); // 'detail', 'log', 'approve' + if (showDetailDialog && selectedTransactionId) { + fetchTransactionDetails(); + } + }, [showDetailDialog, selectedTransactionId, GetData]); - return ( - - - - Transaction Details - - - {/* Tab Content */} -
-
-
- - - - - - - - - - - - - - - {transactionDetails?.log && transactionDetails?.log.length > 0 ? ( - transactionDetails.log.map((log: { id: number, customer: any, amount: number, remark: string, reference: string, request_date: string, payment_response: string, status: string}, index: number) => ( - - - - - - - - - - - )) - ) : ( - - - - )} - -
UsernameFullnameAmountStatusProcess DateInvoice NumberActions
{log.customer.username ?? '-'}{log.customer.fullname ?? '-'}{log.amount ?? '-'}{renderStatusBadge(log.status) ?? '-'}{log.request_date && moment(log.request_date).isValid() ? moment(log.request_date).format('DD/MM/YYYY HH:mm:ss') : '-'}{log.reference ?? '-'} -
- -
-
- No logs available -
-
+ const [activeTab, setActiveTab] = useState('detail'); // 'detail', 'log', 'approve' + + const resetForm = () => { + setTransactionDetails(null); + }; + + useEffect(() => { + if (!showDetailDialog) { + resetForm(); + } + }, [showDetailDialog]); + + return ( + + + + Transaction Details + + + {/* Tab Content */} +
+
+
+ {isLoading ? ( +
+
+
+
+
+
+
- +
- - -
- ); +

Loading Logs Details...

+
+ ) : ( + + + + + + + + + + + + + + {transactionDetails?.log && transactionDetails.log.length > 0 ? ( + transactionDetails.log.map( + ( + log: { + id: number; + customer: any; + amount: number; + remark: string; + reference: string; + request_date: string; + payment_response: string; + status: string; + }, + index: number + ) => ( + + + + + + + + + + ) + ) + ) : ( + + + + )} + +
UsernameFullnameAmountStatusProcess Date + Invoice Number + Actions
+ {log.customer?.username ?? 'Not Found'} + + {log.customer?.fullname ?? 'Not Found'} + + {log.amount ?? '-'} + + {renderStatusBadge(log.status) ?? '-'} + + {log.request_date && moment(log.request_date).isValid() + ? moment(log.request_date).format('DD/MM/YYYY HH:mm:ss') + : '-'} + + {log.reference ?? '-'} + +
+ +
+
+ No logs available +
+ )} +
+ + +
+
+
+ ); }; export default DetailTransaction; diff --git a/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx b/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx index f810d53..1c6d73b 100644 --- a/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx +++ b/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx @@ -31,7 +31,7 @@ const ListToolbar = () => { const handleFilterData = useCallback(() => { try { - table.getColumn('transaction_date')?.setFilterValue(trxDate); + table.getColumn('execution_date')?.setFilterValue(trxDate); } catch (error) { toast.error('Error applying filter'); console.error('Error applying filter:', error); diff --git a/src/pages/master/conversion/blocks/AddDialog.tsx b/src/pages/master/conversion/blocks/AddDialog.tsx index 4b3197d..e249997 100644 --- a/src/pages/master/conversion/blocks/AddDialog.tsx +++ b/src/pages/master/conversion/blocks/AddDialog.tsx @@ -35,6 +35,7 @@ import { import { useManageConversionContext } from '../hooks/useManageConversionContext'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import { RefreshCw } from 'lucide-react'; +import { initialStateConversion, validateFormConversion } from './Types'; interface CurrencyProps { ID: string; name: string; @@ -50,27 +51,15 @@ const AddDialog = () => { const parsedUser = getAuth()?.user; const [currencies, setCurrencies] = useState([]); const [open, setOpen] = useState(false); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); - const initialState = { - status: '', - id_currency_origin: '', - id_currency_destination: '', - buy: 0, - sell: 0, - created_by: '', - created_at: '' - }; - const [formField, setFormField] = useState(initialState); + const [errors, setErrors] = useState>({}); + const [formField, setFormField] = useState(initialStateConversion); const created_time = new Date(); const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); const [isSubmitting, setIsSubmitting] = useState(false); const resetForm = () => { - setFormField(initialState); - setAlert({ show: false, message: '' }); + setFormField(initialStateConversion); + setErrors({}); }; const doCreateConversion = useCallback( @@ -94,8 +83,7 @@ const AddDialog = () => { doSaveLogActivity(createActivity); reload(); } else { - toast.error('Error Create Conversion'); - setAlert({ show: true, message: response?.message }); + toast.error(response?.message); } } catch (error) { toast.error('Something Went Wrong'); @@ -126,20 +114,11 @@ const AddDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if ( - formField.id_currency_origin === '' || - formField.id_currency_destination === '' || - formField.buy === 0 || - formField.sell === 0 || - formField.status === '' - ) { - setAlert({ show: true, message: 'Please fill in all required fields.' }); + if (!validateFormConversion(formField, setErrors)) { return; } doCreateConversion(e); - // console.log(formField); - setAlert({ show: false, message: '' }); }; useEffect(() => { @@ -171,12 +150,6 @@ const AddDialog = () => {
- {alert.show && ( - -

{alert.message}

-
- )} -
@@ -185,11 +158,14 @@ const AddDialog = () => { + {errors.id_currency_destination && ( + + {errors.id_currency_destination} + + )}
+ {errors.id_currency_origin && ( + {errors.id_currency_origin} + )}
{ onValueChange={(values) => { setFormField((prev) => ({ ...prev, - buy: values.floatValue || 0 + buy: values.floatValue ?? null })); + setErrors((prev) => ({ ...prev, buy: '' })); }} placeholder="Enter Buy" /> + {errors.buy && {errors.buy}}
{ onValueChange={(values) => { setFormField((prev) => ({ ...prev, - sell: values.floatValue || 0 + sell: values.floatValue ?? null })); + setErrors((prev) => ({ ...prev, sell: '' })); }} placeholder="Enter Sell" /> + {errors.sell && {errors.sell}}
diff --git a/src/pages/master/conversion/blocks/EditDialog.tsx b/src/pages/master/conversion/blocks/EditDialog.tsx index a178229..1d83ab0 100644 --- a/src/pages/master/conversion/blocks/EditDialog.tsx +++ b/src/pages/master/conversion/blocks/EditDialog.tsx @@ -35,6 +35,7 @@ import { import { useManageConversionContext } from '../hooks/useManageConversionContext'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import { RefreshCw } from 'lucide-react'; +import { initialStateConversion, validateFormConversion } from './Types'; interface CurrencyProps { ID: string; name: string; @@ -52,26 +53,15 @@ const EditDialog = () => { const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); - const initialState = { - status: '', - id_currency_origin: '', - id_currency_destination: '', - buy: 0, - sell: 0, - created_by: '', - created_at: '' - }; - const [formField, setFormField] = useState(initialState); + const [errors, setErrors] = useState>({}); + + const [formField, setFormField] = useState(initialStateConversion); const updated_time = new Date(); const formattedTime = updated_time.toISOString().slice(0, 19).replace('T', ' '); const resetForm = () => { - setFormField(initialState); - setAlert({ show: false, message: '' }); + setFormField(initialStateConversion); + setErrors({}); }; const doUpdateConversion = useCallback( @@ -79,6 +69,11 @@ const EditDialog = () => { e.preventDefault(); setIsSubmitting(true); + if (!validateFormConversion(formField, setErrors)) { + setIsSubmitting(false); + return; + } + try { const response = await PutData(`${API_URL}/dashboard/conversion/${selectedConversion}`, { ...formField @@ -97,8 +92,7 @@ const EditDialog = () => { doSaveLogActivity(createActivity); reload(); } else { - toast.error('Error Create Conversion'); - setAlert({ show: true, message: response?.message }); + toast.error(response?.message); } } catch (error) { toast.error('Something went wrong'); @@ -185,12 +179,6 @@ const EditDialog = () => {
- {alert.show && ( - -

{alert.message}

-
- )} - {isLoading ? (
@@ -213,11 +201,12 @@ const EditDialog = () => { + {errors.id_currency_origin && ( + {errors.id_currency_origin} + )}
+ {errors.id_currency_destination && ( + + {errors.id_currency_destination} + + )}
diff --git a/src/pages/master/conversion/blocks/Types.ts b/src/pages/master/conversion/blocks/Types.ts new file mode 100644 index 0000000..ec88aa9 --- /dev/null +++ b/src/pages/master/conversion/blocks/Types.ts @@ -0,0 +1,50 @@ +import { toast } from 'sonner'; + +export const validateFormConversion = ( + formField: typeof initialStateConversion, + setErrors: React.Dispatch>> +) => { + const requiredFields = [ + { key: 'id_currency_origin', label: 'Currency Origin' }, + { key: 'id_currency_destination', label: 'Currency Destination' }, + { key: 'buy', label: 'Buy' }, + { key: 'sell', label: 'Sell' }, + { key: 'status', label: 'Status' } + ]; + + const newErrors: Record = {}; + let isValid = true; + + requiredFields.forEach(({ key, label }) => { + if ( + formField[key as keyof typeof formField] === '' || + formField[key as keyof typeof formField] === null || + formField[key as keyof typeof formField] === undefined + ) { + newErrors[key] = `${label} is required`; + toast.error(`${label} is required`); + isValid = false; + } + }); + + setErrors(newErrors); + return isValid; +}; + +export const initialStateConversion: { + status: string; + id_currency_origin: string; + id_currency_destination: string; + buy: number | null; + sell: number | null; + created_by: string; + created_at: string; +} = { + status: '', + id_currency_origin: '', + id_currency_destination: '', + buy: null, + sell: null, + created_by: '', + created_at: '' +}; diff --git a/src/pages/master/currency/blocks/AddDialog.tsx b/src/pages/master/currency/blocks/AddDialog.tsx index 9371a44..e95baa4 100644 --- a/src/pages/master/currency/blocks/AddDialog.tsx +++ b/src/pages/master/currency/blocks/AddDialog.tsx @@ -15,16 +15,6 @@ import { import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { NumericFormat } from 'react-number-format'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList -} from '@/components/ui/command'; -import { set } from 'date-fns'; import { Select, SelectContent, @@ -33,9 +23,9 @@ import { SelectValue } from '@/components/ui/select'; import { useManageCurrencyContext } from '../hooks/useManageCurrencyContext'; -import { prefix } from 'stylis'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import { RefreshCw } from 'lucide-react'; +import { initialStateCurrency, validateFormCurrency } from './Types'; interface CurrencyProps { ID: string; name: string; @@ -50,27 +40,15 @@ const AddDialog = () => { const { PostData, GetData } = useCallApi(); const parsedUser = getAuth()?.user; const [currencies, setCurrencies] = useState([]); - const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); - const initialState = { - code: '', - name: '', - prefix: '', - status: '', - created_by: '', - created_at: '' - }; - const [formField, setFormField] = useState(initialState); + const [errors, setErrors] = useState>({}); + const [formField, setFormField] = useState(initialStateCurrency); const created_time = new Date(); const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); const resetForm = () => { - setFormField(initialState); - setAlert({ show: false, message: '' }); + setFormField(initialStateCurrency); + setErrors({}); }; const doCreateCurrency = useCallback( @@ -94,8 +72,7 @@ const AddDialog = () => { doSaveLogActivity(createActivity); reload(); } else { - toast.error('Error Create Currency'); - setAlert({ show: true, message: response?.message }); + toast.error(response?.message); } } catch (error) { toast.error('Something went wrong'); @@ -109,19 +86,11 @@ const AddDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if ( - formField.code === '' || - formField.name === '' || - formField.prefix === '' || - formField.status === '' - ) { - setAlert({ show: true, message: 'Please fill in all required fields.' }); + if (!validateFormCurrency(formField, setErrors)) { return; } doCreateCurrency(e); - // console.log(formField); - setAlert({ show: false, message: '' }); }; useEffect(() => { @@ -144,17 +113,11 @@ const AddDialog = () => { handleAddDialog(open)}> - Cuurency - Create + Currency - Create
- {alert.show && ( - -

{alert.message}

-
- )} -
@@ -163,14 +126,20 @@ const AddDialog = () => { - setFormField((prev) => ({ ...prev, code: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, code: target.value })); + setErrors((prev) => ({ ...prev, code: '' })); + }} /> + {errors.code && ( + + {errors.code} + + )}
- setFormField((prev) => ({ ...prev, name: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, name: target.value })); + setErrors((prev) => ({ ...prev, name: '' })); + }} /> + {errors.name && ( + + {errors.name} + + )}
- setFormField((prev) => ({ ...prev, prefix: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, prefix: target.value })); + setErrors((prev) => ({ ...prev, prefix: '' })); + }} /> + {errors.prefix && ( + + {errors.prefix} + + )}
@@ -211,11 +192,12 @@ const AddDialog = () => {
+ {errors.status && ( + {errors.status} + )}
-
diff --git a/src/pages/master/currency/blocks/Types.ts b/src/pages/master/currency/blocks/Types.ts new file mode 100644 index 0000000..8ee67ea --- /dev/null +++ b/src/pages/master/currency/blocks/Types.ts @@ -0,0 +1,47 @@ +import { toast } from 'sonner'; + +export const initialStateCurrency: { + code: string; + name: string; + prefix: string; + status: string; + created_by: string; + created_at: string; +} = { + code: '', + name: '', + prefix: '', + status: '', + created_by: '', + created_at: '' +}; + +export const validateFormCurrency = ( + formField: typeof initialStateCurrency, + setErrors: React.Dispatch>> +) => { + const requiredFields = [ + { key: 'code', label: 'Code' }, + { key: 'name', label: 'Name' }, + { key: 'prefix', label: 'Prefix' }, + { key: 'status', label: 'Status' } + ]; + + const newErrors: Record = {}; + let isValid = true; + + requiredFields.forEach(({ key, label }) => { + if ( + formField[key as keyof typeof formField] === '' || + formField[key as keyof typeof formField] === null || + formField[key as keyof typeof formField] === undefined + ) { + newErrors[key] = `${label} is required`; + toast.error(`${label} is required`); + isValid = false; + } + }); + + setErrors(newErrors); + return isValid; +}; diff --git a/src/pages/master/currency/hooks/ManageCurrencyContext.tsx b/src/pages/master/currency/hooks/ManageCurrencyContext.tsx index 2795014..67f320e 100644 --- a/src/pages/master/currency/hooks/ManageCurrencyContext.tsx +++ b/src/pages/master/currency/hooks/ManageCurrencyContext.tsx @@ -155,7 +155,6 @@ const ManageCurrencyContextProvider = ({ children }: { children: React.ReactNode order_direction: sorting[0].desc ? 'ASC' : 'DESC' // filter: JSON.stringify(filter) }); - // console.log(response?.data); return { data: response?.data.list, totalCount: response?.data.total_count }; }; @@ -181,7 +180,7 @@ const ManageCurrencyContextProvider = ({ children }: { children: React.ReactNode pagination={{ size: 10 }} layout={{ card: true }} toolbar={} - sorting={[{ id: 'ID', desc: true }]} + sorting={[{ id: 'created_at', desc: false }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting }) => doGetCurrency(pageIndex, pageSize, sorting) diff --git a/src/pages/master/reward/blocks/AddDialog.tsx b/src/pages/master/reward/blocks/AddDialog.tsx index 71727b8..83b9169 100644 --- a/src/pages/master/reward/blocks/AddDialog.tsx +++ b/src/pages/master/reward/blocks/AddDialog.tsx @@ -25,6 +25,7 @@ import { } from '@/components/ui/select'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import { RefreshCw } from 'lucide-react'; +import { initialStateReward, validateFormReward } from './Types'; const API_URL = apiConfig.service_master_data; @@ -34,28 +35,16 @@ const AddDialog = () => { const { reload } = useDataGrid(); const { PostData } = useCallApi(); const parsedUser = getAuth()?.user; - const [alert, setAlert] = useState({ - show: false, - message: '' - }); + const [errors, setErrors] = useState>({}); - const initialState = { - name: '', - type: '', - amount: 0, - status: '', - created_by: '', - created_at: '' - }; - - const [formField, setFormField] = useState(initialState); + const [formField, setFormField] = useState(initialStateReward); const [isSubmitting, setIsSubmitting] = useState(false); const created_time = new Date(); const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); const resetForm = () => { - setFormField(initialState); - setAlert({ show: false, message: '' }); + setFormField(initialStateReward); + setErrors({}); }; const RewardType = { @@ -88,8 +77,7 @@ const AddDialog = () => { doSaveLogActivity(createActivity); } else { - toast.error('Failed to create reward.'); - setAlert({ show: true, message: response?.message }); + toast.error(response?.message); } } catch (error) { toast.error('Something went wrong, please try again.'); @@ -103,19 +91,11 @@ const AddDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if ( - formField.name.trim() === '' || - formField.type.trim() === '' || - formField.amount === 0 || - formField.status.trim() === '' - ) { - setAlert({ show: true, message: 'Please fill name field.' }); + if (!validateFormReward(formField, setErrors)) { return; } doCreateReward(e); - // console.log(formField); - setAlert({ show: false, message: '' }); }; useEffect(() => { @@ -135,43 +115,52 @@ const AddDialog = () => { }, [showAddDialog]); return ( - handleAddDialog(open)}> + Reward - Create - +
- {alert.show && {alert.message}}
-
+ {/* Reward Name */} +
- - setFormField((prev) => ({ ...prev, name: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, name: target.value })); + setErrors((prev) => ({ ...prev, name: '' })); + }} /> + {errors.name && ( + + {errors.name} + + )}
-
+ + {/* Reward Type */} +
-
+ {errors.type && ( + + {errors.type} + + )}
-
+ + {/* Amount */} +
- { onValueChange={(values) => { setFormField((prev) => ({ ...prev, - amount: values.floatValue || 0 + amount: values.floatValue ?? null })); + setErrors((prev) => ({ ...prev, amount: '' })); }} placeholder="Enter Amount" /> + {errors.amount && ( + + {errors.amount} + + )}
-
+ + {/* Status */} +
-
+ {errors.status && ( + + {errors.status} + + )}
+ + {/* Actions */}
diff --git a/src/pages/master/reward/blocks/Types.ts b/src/pages/master/reward/blocks/Types.ts new file mode 100644 index 0000000..28e068c --- /dev/null +++ b/src/pages/master/reward/blocks/Types.ts @@ -0,0 +1,47 @@ +import { toast } from 'sonner'; + +export const initialStateReward: { + name: string; + type: string; + amount: number | null; + status: string; + created_by: string; + created_at: string; +} = { + name: '', + type: '', + amount: null, + status: '', + created_by: '', + created_at: '' +}; + +export const validateFormReward = ( + formField: typeof initialStateReward, + setErrors: React.Dispatch>> +) => { + const requiredFields = [ + { key: 'name', label: 'Name' }, + { key: 'type', label: 'Type' }, + { key: 'amount', label: 'Amount' }, + { key: 'status', label: 'Status' } + ]; + + const newErrors: Record = {}; + let isValid = true; + + requiredFields.forEach(({ key, label }) => { + if ( + formField[key as keyof typeof formField] === '' || + formField[key as keyof typeof formField] === null || + formField[key as keyof typeof formField] === undefined + ) { + newErrors[key] = `${label} is required`; + toast.error(`${label} is required`); + isValid = false; + } + }); + + setErrors(newErrors); + return isValid; +}; diff --git a/src/pages/master/reward/hooks/ManageRewardContext.tsx b/src/pages/master/reward/hooks/ManageRewardContext.tsx index 1d4d2f3..509d643 100644 --- a/src/pages/master/reward/hooks/ManageRewardContext.tsx +++ b/src/pages/master/reward/hooks/ManageRewardContext.tsx @@ -159,8 +159,6 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', filter: JSON.stringify(filter) }); - // console.log('API Response:', response?.data.list); - // console.log('reward list :', response); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { console.error('Error fethcing reward', error); @@ -185,7 +183,7 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } pagination={{ size: 5 }} toolbar={} layout={{ card: true }} - sorting={[{ id: 'id', desc: false }]} + sorting={[{ id: 'created_at', desc: true }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => getRewardList(pageIndex, pageSize, sorting, columnFilters) diff --git a/src/pages/transaction/history-transaction/blocks/RollbackTransaction.tsx b/src/pages/transaction/history-transaction/blocks/RollbackTransaction.tsx new file mode 100644 index 0000000..3985aaa --- /dev/null +++ b/src/pages/transaction/history-transaction/blocks/RollbackTransaction.tsx @@ -0,0 +1,187 @@ +import { useTransactionContext } from '../hooks/useTransactionContext'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; +import { useCallback, useEffect, useState } from 'react'; +import { + Dialog, + DialogBody, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; +import { toast } from 'sonner'; +import { Input } from '@/components/ui/input'; +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import Swal from 'sweetalert2'; // ✅ IMPORT sweetalert2 + +const API_URL = apiConfig.transaction; + +const RollbackTransaction = () => { + const { GetData, PostData } = useCallApi(); + const { reload } = useDataGrid(); + + const { + showRollbackDialog, + setShowRollbackDialog, + selectedTransactionId, + } = useTransactionContext(); + + + const [formField, setFormField] = useState({ + transaction_code: '', + notes: '', + pin: '' + }); + + const [alert, setAlert] = useState({ + show: false, + message: '', + }); + + const doApproval = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + // ✅ TUTUP Dialog sebelum munculkan SweetAlert + setShowRollbackDialog(false); + + // ✅ TAMPILKAN SWEETALERT + const result = await Swal.fire({ + title: 'Are you sure?', + text: "You want to save changes?", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, save it!', + cancelButtonText: 'Cancel' + }); + + if (result.isConfirmed) { + // ✅ Kalau tekan YES, baru tembak API + const response = await PostData(`${API_URL}/transaction/rollback`, { + id_transaction: selectedTransactionId, + notes: formField.notes, + pin: formField.pin, + }); + + // console.log(response?.message?.error?.message,response?.status); + + if (response?.status === false) { + // console.log(response); + toast.error(JSON.stringify(response)); + return; + } + + if (response?.status) { + toast.success('Success Update Approval'); + const createActivity = { + module: 'History Transaction', + description: `Rollback Transaction for transaction => ${selectedTransactionId}`, + action: 'U', + }; + doSaveLogActivity(createActivity); + reload(); + } + } else { + // ✅ Kalau tekan Cancel + console.log('User cancelled'); + } + }, + [formField] + ); + + useEffect(() => { + if (showRollbackDialog) { + setFormField({ + transaction_code: '', + notes: '', + pin: '' + }); + setAlert({ show: false, message: '' }); + } + }, [showRollbackDialog]); + + + return ( + + + + Rollback Transaction + + + + +
+
+
+ +
+ + setFormField((prev) => ({ + ...prev, + notes: e.target.value, + })) + } + /> +
+
+
+ +
+ + setFormField((prev) => ({ + ...prev, + pin: e.target.value, + })) + } + /> +
+
+ {alert.show && ( +
+ + {alert.message} + +
+ )} +
+ +
+
+ +
+
+ +
+
+
+ ); +}; + +export default RollbackTransaction; diff --git a/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx b/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx index 39bc60f..fbbd9ed 100644 --- a/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx @@ -8,6 +8,7 @@ import ListToolbar from '../blocks/ListToolbar'; import { useNavigate } from 'react-router'; import DetailTransaction from '../blocks/DetailTransaction'; import ResendTransaction from '../blocks/ResendTransaction'; +import RollbackTransaction from '../blocks/RollbackTransaction'; interface TransactionProps { id: number; @@ -25,6 +26,8 @@ interface ContextProps { ) => Promise<{ data: TransactionProps[]; totalCount: number } | undefined>; showDetailDialog: boolean; setShowDetailDialog: React.Dispatch>; + showRollbackDialog: boolean; + setShowRollbackDialog: React.Dispatch>; selectedTransactionId: number | null; setSelectedTransactionId: React.Dispatch>; } @@ -33,6 +36,8 @@ const initialProps: ContextProps = { getTransactionLists: async () => ({ data: [], totalCount: 0 }), showDetailDialog: false, setShowDetailDialog: () => { }, + showRollbackDialog: false, + setShowRollbackDialog: () => { }, selectedTransactionId: null, setSelectedTransactionId: () => { } }; @@ -42,6 +47,7 @@ const API_URL = apiConfig.transaction; const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const [showDetailDialog, setShowDetailDialog] = useState(false); + const [showRollbackDialog, setShowRollbackDialog] = useState(false); const [selectedTransactionId, setSelectedTransactionId] = useState(null); const [transaction, setTransaction] = useState([]); const { GetData } = useCallApi(); @@ -222,7 +228,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { enableSorting: false, enableHiding: false, cell: (data) => { - const row = data.row.original; + const row = data.row.original; const isVisible = (row.status === 'F' ? true : false || row.status === 'P' ? true : false) && row.status_approve !== 'W' ? true : false; return (
@@ -235,6 +241,17 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { > + +
); }, @@ -339,6 +356,8 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { getTransactionLists, showDetailDialog, setShowDetailDialog, + showRollbackDialog, + setShowRollbackDialog, selectedTransactionId, setSelectedTransactionId }} @@ -358,6 +377,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { } > + console.log('Retry Transaction closed')} diff --git a/src/pages/transaction/withdrawl-saldo/TransactionWithdraw.tsx b/src/pages/transaction/withdrawl-saldo/TransactionWithdraw.tsx new file mode 100644 index 0000000..9b30b1b --- /dev/null +++ b/src/pages/transaction/withdrawl-saldo/TransactionWithdraw.tsx @@ -0,0 +1,359 @@ +import { Alert, Container, DataGridInner } from '@/components'; +import { TransactionWithdrawProvider } from './hooks/TransactionWithdrawContext'; +import { Breadcrumbs, Link } from '@mui/material'; +import { Helmet } from 'react-helmet'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { useState, useEffect, useRef } from 'react'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; +import { toast } from 'sonner'; +import { getAuth } from '@/auth'; +import { RefreshCw } from 'lucide-react'; + +const TransactionWithdraw = () => { + const initialForm: { + msisdn: string; + amount: string; + pin: string; + purpose: string; + } = { + msisdn: '', + amount: '', + pin: '', + purpose: '' + }; + + const [form, setForm] = useState(initialForm); + const [wallets, setWallets] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [customerMsisdn, setCustomerMsisdn] = useState<{ value: string; label: string }[]>([]); + const [searchTerm, setSearchTerm] = useState(''); + const [dropdownOpen, setDropdownOpen] = useState(false); + const { GetData, PostData } = useCallApi(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + const parsedUser = getAuth()?.user; + const API_URL = apiConfig.transaction; + const API_URL_WALLET = apiConfig.service_wallet; + const API_URL_CUSTOMER = apiConfig.service_customer; + const dropdownRef = useRef(null); + + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const fetchWallets = async () => { + try { + const response = await GetData( + `${API_URL_WALLET}/dashboard/balance/account/${parsedUser.customer.id}`, + {} + ); + if (response?.status === true) { + setWallets(response.data || []); + } else { + toast.warning(response?.message || 'Failed to fetch wallet data'); + } + } catch (error) { + toast.warning('Failed to fetch wallet data'); + } + }; + + const fetchCustomerMsisdn = async (sorting: any, filterValue: string) => { + const filter: any = + filterValue.trim().length === 0 + ? {} + : { + or: [ + { msisdn: { like: `%${filterValue}%` } }, + { fullname: { like: `%${filterValue}%` } } + ] + }; + + const query: any = { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + }; + + if (filter && Object.keys(filter).length > 0) { + query.filter = JSON.stringify(filter); + // query.page = page + 1; + } + + try { + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, query); + setCustomerMsisdn( + response?.data.list.map((item: any) => ({ + value: item.msisdn, + label: `${item.msisdn} - ${item.fullname}` + })) + ); + } catch (error) { + toast.error('Failed to fetch customer msisdn'); + } finally { + setIsLoading(false); + } + }; + + const doPostData = async (form: typeof initialForm) => { + setIsSubmitting(true); + + try { + let response = await PostData(`${API_URL}/transaction/transfer`, { + msisdn_destination: form.msisdn, + amount: form.amount, + pin: form.pin, + purpose: form.purpose, + id_transaction_type: "20c8a690-dc02-463d-b391-324184d1fefa", + id_origin_customer: getAuth()?.id + }); + if (response?.status == true) { + await fetchWallets(); + toast.success('Success Request Topup'); + } else { + toast.error(`${response?.message?.message}`); + } + } catch (error: any) { + const errorMessage = + error?.response?.data?.message || error?.message || 'Something went wrong'; + toast.error(errorMessage); + setAlert({ show: true, message: errorMessage }); + } finally { + setIsSubmitting(false); + setShowConfirmation(false); + ResetForm(); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (form.amount == '' || form.msisdn == '' || form.pin == '') { + setAlert({ + show: true, + message: 'Please fill in all required fields.' + }); + return; + } + setAlert({ show: false, message: '' }); + setShowConfirmation(true); + // TODO: Kirim ke backend atau proses lainnya + }; + + const ResetForm = () => { + setForm(initialForm); + setAlert({ show: false, message: '' }); + setSearchTerm(''); + }; + + const handleCancelSubmit = () => { + setShowConfirmation(false); + }; + + const handleMsisdnSearch = (e: React.ChangeEvent) => { + setIsLoading(true); + setSearchTerm(e.target.value); + setDropdownOpen(true); + const timer = setTimeout(() => { + fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], e.target.value); + }, 500); + return () => clearTimeout(timer); + }; + + const handleMsisdnSelect = (msisdn: string) => { + setForm({ ...form, msisdn }); + setDropdownOpen(false); + setSearchTerm(msisdn); + }; + + useEffect(() => { + fetchWallets(); + fetchCustomerMsisdn([{ id: 'msisdn', desc: false }], ''); + + const handleClickOutside = (event: any) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const filteredMsisdn = customerMsisdn + .filter((item) => item.label.toLowerCase().includes(searchTerm.toLowerCase())) + .slice(0, 10); + + return ( + <> + + TPAY | Transaction Withdraw Saldo + + + +

+ MANAGE TRANSACTION WITHDRAW SALDO +

+ + + Dashboard + + + Transaction + + + Withdraw Saldo + + + {/* Wallet Section */} +
+

Your Wallets

+
+ {wallets.map((wallet: any) => ( +
+

{wallet.wallet}

+

+ {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format( + wallet.amount + )} +

+
+ ))} +
+
+ + +
+
+ {alert.show && ( + +

{alert.message}

+
+ )} + {/* form */} +
+
+ + * +
+ setDropdownOpen(true)} + /> + {dropdownOpen && ( +
+ {filteredMsisdn.length > 0 ? ( + filteredMsisdn.map((item, index) => ( +
handleMsisdnSelect(item.value)} + > + {item.label} +
+ )) + ) : ( +
+ {isLoading ? 'Loading...' : 'No results found'} +
+ )} +
+ )} +
+
+ +
+ + * + { + const value = Number(e.target.value); + if (value >= 0) { + setForm({ ...form, amount: String(value) }); + } + }} + /> +
+
+ + * + setForm({ ...form, pin: e.target.value })} + /> +
+
+ + * + setForm({ ...form, purpose: e.target.value })} + /> +
+
+ +
+
+
+
+
+ + {showConfirmation && ( +
+
+

Confirm Transaction

+

+ Are you sure you want to withdraw saldo of{' '} + + {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format( + Number(form.amount) + )}{' '} + + ? +

+
+ + +
+
+
+ )} +
+
+ + ); +}; + +export default TransactionWithdraw; diff --git a/src/pages/transaction/withdrawl-saldo/blocks/ListToolbar.tsx b/src/pages/transaction/withdrawl-saldo/blocks/ListToolbar.tsx new file mode 100644 index 0000000..013a15c --- /dev/null +++ b/src/pages/transaction/withdrawl-saldo/blocks/ListToolbar.tsx @@ -0,0 +1,61 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { Button } from '@/components/ui/button'; +import { useCallback, useState, useEffect } from 'react'; +import { toast } from 'sonner'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + + // Set the initial state for trxDate + const [trxDate, settrxDate] = useState({ from: '', to: '' }); + + // Function to format date to YYYY-MM-DD + const formatDate = (date: Date): string => { + return date.toISOString().split('T')[0]; + }; + + // useEffect to set the default date values + useEffect(() => { + const today = new Date(); + const nextWeek = new Date(today); + nextWeek.setDate(today.getDate() + 7); + + settrxDate({ + from: formatDate(today), // Set 'from' to today + to: formatDate(nextWeek), // Set 'to' to 7 days later + }); + }, []); + + const handleFilterData = useCallback(() => { + try { + table.getColumn('transaction_date')?.setFilterValue(trxDate); + } catch (error) { + toast.error('Error applying filter'); + console.error('Error applying filter:', error); + } + }, [trxDate, table]); + + useEffect(() => { + if (trxDate.from && trxDate.to) { + handleFilterData(); + } + }, [trxDate]); + + return ( +
+
+
+
+ + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/transaction/withdrawl-saldo/hooks/TransactionWithdrawContext.tsx b/src/pages/transaction/withdrawl-saldo/hooks/TransactionWithdrawContext.tsx new file mode 100644 index 0000000..468c825 --- /dev/null +++ b/src/pages/transaction/withdrawl-saldo/hooks/TransactionWithdrawContext.tsx @@ -0,0 +1,80 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { toast } from 'sonner'; +import { apiConfig } from '@/config/api.config'; +import { ColumnDef } from '@tanstack/react-table'; +import { createContext, useCallback, useMemo, useState } from 'react'; +import ListToolbar from '../blocks/ListToolbar'; +import { useCallApi } from '@/hooks'; +import moment from 'moment'; + +interface TransactionWithdrawProps { + id: string; + customers_id: string; + group_id: string; + username: string; + fullname: string; + email: string; + status: string; + created_at: Date; +} + +interface ContextProps { + +} + +const initialProps: ContextProps = { + +}; + +const TransactionWithdrawContext = createContext(initialProps); +const API_URL = apiConfig.service_customer; + +type StatusCode = 'W' | 'Y' | 'N' | 'T'; + +interface StatusInfo { + label: string; + bg: string; + text: string; +} + +const statusMap: Record = { + W: { label: 'Waiting Approval', bg: 'bg-yellow-100', text: 'text-yellow-600' }, + T: { label: 'No Need', bg: 'bg-blue-100', text: 'text-blue-600' }, + N: { label: 'Reject', bg: 'bg-red-100', text: 'text-red-600' }, + Y: { label: 'Approve', bg: 'bg-green-100', text: 'text-green-600' }, +}; + +export const renderStatusBadge = (statusRaw: string | null | undefined) => { + const status = statusRaw as StatusCode; + const { label, bg, text } = statusMap[status] ?? { + label: 'Unknown', + bg: 'bg-gray-100', + text: 'text-gray-600', + }; + + return ( + + {label} + + ); +}; + +// const { reload } = useDataGrid(); + +const TransactionWithdrawProvider = ({ children }: { children: React.ReactNode }) => { + + return ( + + +
+ {children} +
+
+ ); +}; + +export { TransactionWithdrawProvider, TransactionWithdrawContext }; +export type { TransactionWithdrawProps }; diff --git a/src/pages/transaction/withdrawl-saldo/hooks/index.tsx b/src/pages/transaction/withdrawl-saldo/hooks/index.tsx new file mode 100644 index 0000000..3354df9 --- /dev/null +++ b/src/pages/transaction/withdrawl-saldo/hooks/index.tsx @@ -0,0 +1,2 @@ +export * from './TransactionWithdrawContext'; +export * from './useTransactionWithdrawContext'; diff --git a/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx b/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx new file mode 100644 index 0000000..64fe126 --- /dev/null +++ b/src/pages/transaction/withdrawl-saldo/hooks/useTransactionWithdrawContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { TransactionWithdrawContext } from './TransactionWithdrawContext'; + +const useTransactionWithdrawContext = () => { + const context = useContext(TransactionWithdrawContext); + + if (!context) throw new Error('useTransactionWithdrawContext must be used within AuthProvider'); + + return context; +}; + +export { useTransactionWithdrawContext }; diff --git a/src/pages/transfer/transfertype/blocks/AddDialog.tsx b/src/pages/transfer/transfertype/blocks/AddDialog.tsx index b71d9b7..b57fcab 100644 --- a/src/pages/transfer/transfertype/blocks/AddDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/AddDialog.tsx @@ -2,7 +2,6 @@ import { apiConfig } from '@/config/api.config'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useManageTransferTypeContext } from '../hooks/useManageTransferTypeContext'; import { - Alert, Container, DataGridColumnHeader, DataGridInner, @@ -65,17 +64,14 @@ const AddDialog = () => { const [customers, setCustomers] = useState([]); const { reload } = useDataGrid(); const { PostData, PutData } = useCallApi(); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); + + const [errors, setErrors] = useState>({}); const initialState = { name: '', description: '', wallet_origin: '', wallet_destination: '', - minimum_amount: 0, maximum_amount: 0, max_transaction_per_day: 0, @@ -92,38 +88,39 @@ const AddDialog = () => { const resetForm = () => { setFormField(initialState); + setErrors({}); }; const parsedUser = getAuth()?.user; const validateForm = () => { const requiredFields = [ - 'name', - 'description', - 'wallet_origin', - 'wallet_destination', - 'status', - 'status_approval', - 'status_kind' + { key: 'name', label: 'Transfer Type Name' }, + { key: 'description', label: 'Description' }, + { key: 'wallet_origin', label: 'From Account' }, + { key: 'wallet_destination', label: 'To Account' }, + { key: 'status', label: 'Status' }, + { key: 'status_approval', label: 'Status Approval' }, + { key: 'status_kind', label: 'Status Kind' } ]; - const missingFields = requiredFields.filter( - (field) => - formField[field as keyof typeof formField] === '' || - formField[field as keyof typeof formField] === null || - formField[field as keyof typeof formField] === undefined - ); + const newErrors: Record = {}; + let isValid = true; - if (missingFields.length > 0) { - setAlert({ - show: true, - message: `Please fill out all required fields: ${missingFields.join(', ')}` - }); - return false; - } + requiredFields.forEach(({ key, label }) => { + if ( + formField[key as keyof typeof formField] === '' || + formField[key as keyof typeof formField] === null || + formField[key as keyof typeof formField] === undefined + ) { + newErrors[key] = `${label} is required`; + toast.error(`${label} is required`); + isValid = false; + } + }); - setAlert({ show: false, message: '' }); - return true; + setErrors(newErrors); + return isValid; }; const handleSubmit = (e: React.FormEvent) => { @@ -162,20 +159,17 @@ const AddDialog = () => { const response = await PostData(`${API_URL}/transactiontype/create`, data); if (response?.status) { - setAlert({ show: false, message: '' }); return true; } else { - setAlert({ show: true, message: response?.message || 'Failed to create transfer type' }); + toast.error(response?.message || 'Failed to create transfer type'); return false; } } catch (error) { - setAlert({ - show: true, - message: - error instanceof Error - ? error.message - : 'An error occurred while creating transfer type' - }); + toast.error( + error instanceof Error + ? error.message + : 'An error occurred while creating transfer type' + ); return false; } }, @@ -226,7 +220,6 @@ const AddDialog = () => { order_direction: 'ASC' }; const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, params); - // console.log(response) if (response?.status && response?.data) { setWallets(response.data.list); } else { @@ -238,7 +231,13 @@ const AddDialog = () => { if (!showAddDialog) return; fetchWallets(); }, [showAddDialog]); - // console.log(formField) + + useEffect(() => { + if (!showAddDialog) { + resetForm(); + } + }, [showAddDialog]); + return ( handleAddDialog(open)}> @@ -256,7 +255,6 @@ const AddDialog = () => { className="cursor-pointer hover:opacity-100 opacity-50" onClick={() => { handleAddDialog(false); - resetForm(); }} > @@ -265,13 +263,6 @@ const AddDialog = () => {
- {alert.show && ( -
- -

{alert.message}

-
-
- )}
@@ -280,16 +271,20 @@ const AddDialog = () => { Transfer Type Name * -
+
- setFormField((prev) => ({ ...prev, name: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, name: target.value })); + if (target.value) { + setErrors((prev) => ({ ...prev, name: '' })); + } + }} /> + {errors.name && {errors.name}}
@@ -300,16 +295,20 @@ const AddDialog = () => { Description * -
+
- setFormField((prev) => ({ ...prev, description: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, description: target.value })); + if (target.value) { + setErrors((prev) => ({ ...prev, description: '' })); + } + }} /> + {errors.description && {errors.description}}
@@ -390,24 +389,26 @@ const AddDialog = () => { From Account * -
+
+ {errors.wallet_origin && {errors.wallet_origin}}
@@ -418,24 +419,26 @@ const AddDialog = () => { To Account * -
+
+ {errors.wallet_destination && {errors.wallet_destination}}
@@ -447,14 +450,15 @@ const AddDialog = () => { * -
+
+ {errors.type && {errors.type}}
@@ -490,14 +495,15 @@ const AddDialog = () => { * -
+
+ {errors.status_approval && {errors.status_approval}}
@@ -515,14 +522,15 @@ const AddDialog = () => { * -
+
+ {errors.status_kind && {errors.status_kind}}
@@ -545,14 +554,15 @@ const AddDialog = () => { * -
+
+ {errors.status && {errors.status}}
@@ -587,4 +598,4 @@ const AddDialog = () => { ); }; -export default AddDialog; +export default AddDialog; \ No newline at end of file diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index fedac18..4ba5874 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -67,6 +67,10 @@ const EditDialog = () => { show: false, message: '' }); + const [errors, setErrors] = useState>({}); + + const [isLoadingWallets, setIsLoadingWallets] = useState(false); + const initialState: { name: string; @@ -106,6 +110,7 @@ const EditDialog = () => { setFormField(initialState); setSelectedGroups([]); setAlert({ show: false, message: '' }); + setErrors({}); }; const handleGroupChange = (groupId: string) => { @@ -128,42 +133,33 @@ const EditDialog = () => { const validateForm = () => { const requiredFields = [ - 'name', - 'description', - 'wallet_origin', - 'wallet_destination', - 'status', - 'status_approval', - 'status_kind', - 'type' + { key: 'name', label: 'Transfer Type Name' }, + { key: 'description', label: 'Description' }, + { key: 'wallet_origin', label: 'From Account' }, + { key: 'wallet_destination', label: 'To Account' }, + { key: 'status', label: 'Status' }, + { key: 'status_approval', label: 'Status Approval' }, + { key: 'status_kind', label: 'Status Kind' } ]; - const missingFields = requiredFields.filter((field) => { - return ( - formField[field as keyof typeof formField] === '' || - formField[field as keyof typeof formField] === null || - formField[field as keyof typeof formField] === undefined - ); + const newErrors: Record = {}; + let isValid = true; + + requiredFields.forEach(({ key, label }) => { + if ( + formField[key as keyof typeof formField] === '' || + formField[key as keyof typeof formField] === null || + formField[key as keyof typeof formField] === undefined + ) { + newErrors[key] = `${label} is required`; + // Show toast for each required field + toast.error(`${label} is required`); + isValid = false; + } }); - if (missingFields.length > 0) { - setAlert({ - show: true, - message: `Please fill out all required fields: ${missingFields.join(', ')}` - }); - return false; - } - - if (formField.permission.length === 0) { - setAlert({ - show: true, - message: 'Please select at least one group permission' - }); - return false; - } - - setAlert({ show: false, message: '' }); - return true; + setErrors(newErrors); + return isValid; }; useEffect(() => { @@ -202,7 +198,7 @@ const EditDialog = () => { const createActivity = { module: 'Manage Transfer Type', - description: `Edit Transfer Type => ${selectedTransferType}`, + description: `Edit Transfer Type => ${formField.name}`, action: 'U' }; @@ -227,17 +223,18 @@ const EditDialog = () => { ); if (response?.status) { - setAlert({ show: false, message: '' }); return true; } else { - setAlert({ show: true, message: response?.message || 'Failed to update transfer type' }); + toast.error(response?.message || 'Failed to update transfer type'); return false; } } catch (error) { + toast.error( + error instanceof Error ? error.message : 'Failed to Update Transfer Type' + ); setAlert({ show: true, - message: - error instanceof Error ? error.message : 'An error occurred while updating transfer type' + message: error instanceof Error ? error.message : 'Failed to Update Transfer Type' }); return false; } @@ -376,6 +373,37 @@ const EditDialog = () => { resetForm(); } }, [showEditDialog]); + const renderSelectWithLoading = ( + value: string, + onChangeHandler: (value: string) => void, + options: { id: string; name: string }[] | null, + placeholder: string, + isLoading: boolean + ) => { + return ( + + ); + }; + return ( handleEditDialog(open, null)}> @@ -418,36 +446,48 @@ const EditDialog = () => { Transfer Type Name * -
+
- setFormField((prev) => ({ ...prev, name: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, name: target.value })); + if (target.value) { + setErrors((prev) => ({ ...prev, name: '' })); + } + }} /> + {errors.name && ( + {errors.name} + )}
-
+
-
+
- setFormField((prev) => ({ ...prev, description: target.value })) - } + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, description: target.value })); + if (target.value) { + setErrors((prev) => ({ ...prev, description: '' })); + } + }} /> + {errors.description && ( + {errors.description} + )}
@@ -528,24 +568,17 @@ const EditDialog = () => { From Account * -
- +
+ {renderSelectWithLoading( + formField.wallet_origin, + (value) => setFormField({ ...formField, wallet_origin: value }), + wallets, + 'Select Wallet', + isLoadingWallets + )} + {errors.wallet_origin && ( + {errors.wallet_origin} + )}
@@ -556,14 +589,15 @@ const EditDialog = () => { To Account * -
+
+ {errors.wallet_destination && ( + {errors.wallet_destination} + )}
@@ -584,14 +621,15 @@ const EditDialog = () => { Status Transaction Type * -
+
+ {errors.type && ( + {errors.type} + )}
@@ -627,14 +668,15 @@ const EditDialog = () => { Status Approval * -
+
+ {errors.status_approval && ( + {errors.status_approval} + )}
@@ -652,14 +697,15 @@ const EditDialog = () => { * -
+
+ {errors.status_kind && ( + {errors.status_kind} + )}
@@ -680,14 +729,15 @@ const EditDialog = () => { Status * -
+
+ {errors.status && ( + {errors.status} + )}
@@ -738,6 +791,13 @@ const EditDialog = () => {
+ @@ -761,4 +821,4 @@ const EditDialog = () => { ); }; -export { EditDialog }; +export { EditDialog }; \ No newline at end of file diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index 55fa2a6..517b956 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -346,7 +346,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React setSearchTerm }} > - + {/* */}
{ @@ -102,6 +104,7 @@ const AppRoutingSetup = (): ReactElement => { } /> } /> } /> + } /> } /> } /> } />