diff --git a/src/components/ui/DataTable.tsx b/src/components/ui/DataTable.tsx index 19cde1c..ff672e5 100644 --- a/src/components/ui/DataTable.tsx +++ b/src/components/ui/DataTable.tsx @@ -36,6 +36,7 @@ import { interface DataTableProps { columns: ColumnDef[]; data: TData[]; + createData: any; onUpdate: any; onDelete: any; } @@ -46,7 +47,7 @@ export function DataTable({ createData, onUpdate, onDelete -}: DataTableProps & { createData?: () => void }) { +}: DataTableProps) { const [columnFilters, setColumnFilters] = useState([]); const [sorting, setSorting] = useState([]); @@ -74,7 +75,7 @@ export function DataTable({ onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)} className="max-w-sm" /> - {/* */} + {createData?() : ""}
diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx index 28df134..dfa33ec 100644 --- a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx @@ -1,7 +1,10 @@ +// disbursement/history-transaction/blocks/DetailTransaction.tsx +import { KeenIcon } from '@/components'; import { useTransactionContext } from '../hooks/useTransactionContext'; import { useCallApi } from '@/hooks'; import { apiConfig } from '@/config/api.config'; import { useEffect, useState } from 'react'; +import moment from 'moment'; import { Dialog, DialogBody, @@ -10,10 +13,11 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import TransactionLogViewer from './DetailTransactionLog'; const API_URL = apiConfig.service_disbursement; -type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y'; +type StatusCode = 'W' | 'O' | 'F' | 'D'; interface StatusInfo { label: string; @@ -22,11 +26,10 @@ interface StatusInfo { } const statusMap: Record = { - W: { label: 'Waiting', bg: 'bg-yellow-100', text: 'text-yellow-600' }, - P: { label: 'Pending', bg: 'bg-blue-100', text: 'text-blue-600' }, + W: { label: 'Waiting Schedule', bg: 'bg-yellow-100', text: 'text-yellow-600' }, + O: { label: 'On Process', bg: 'bg-blue-100', text: 'text-blue-600' }, F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' }, D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' }, - Y: { label: 'Active', bg: 'bg-green-100', text: 'text-green-600' }, }; export const renderStatusBadge = (statusRaw: string | null | undefined) => { @@ -49,7 +52,9 @@ const DetailTransaction = () => { const { showDetailDialog, setShowDetailDialog, - selectedTransactionId + selectedTransactionId, + setShowDetailLogDialog, + setDetailLogData } = useTransactionContext(); const [transactionDetails, setTransactionDetails] = useState(null); @@ -92,27 +97,39 @@ const DetailTransaction = () => { - - - - - - + + + + + + {transactionDetails?.log && transactionDetails?.log.length > 0 ? ( - transactionDetails.log.map((log: { customer: any, amount: number, remark: string, reference: string, response_date: string, payment_response: string, status: string}, index: number) => ( + transactionDetails.log.map((log: { id: number, customer: any, amount: number, remark: string, reference: string, request_date: string, payment_response: string, status: string}, index: number) => ( - - - - + + + + )) ) : ( diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx new file mode 100644 index 0000000..7b26b05 --- /dev/null +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react'; +import moment from 'moment'; +import { useTransactionContext } from '../hooks/useTransactionContext'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody } from '@/components/ui/dialog'; + +interface LogType { + id: string; + amount: number; + remark: string; + reference: string; + response_date: string; + status: string; + customer?: { + username: string; + fullname: string; + }; + payment_response?: string; +} + +type StatusCode = 'W' | 'O' | 'F' | 'D'; + +interface StatusInfo { + label: string; + bg: string; + text: string; +} + +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' }, +}; + +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 TransactionLogViewer = () => { + const { + showDetailLogDialog, + setShowDetailLogDialog, + detailLogData + } = useTransactionContext(); + + console.log('detailLogData: ', detailLogData) + + + + return ( + + + + Detail Record + + + {/* Tab Content */} + {detailLogData && detailLogData != null ? ( +
+
+
+
UsernameNameTransfer AmountDescriptionInvoice NumberProcess DateResponseFullnameAmount StatusProcess DateInvoice NumberActions
{log.customer.username ?? '-'} {log.customer.fullname ?? '-'} {log.amount ?? '-'}{log.remark ?? '-'}{log.reference ?? '-'}{log.response_date ?? '-'}{log.payment_response ?? '-'} {renderStatusBadge(log.status) ?? '-'}{log.request_date && moment(log.request_date).isValid() ? moment(log.request_date).format('YYYY-MM-DD HH:mm:ss') : '-'}{log.reference ?? '-'} +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{detailLogData.customer.id}
Username{detailLogData.customer.username}
Name{detailLogData.customer.fullname}
Amount{detailLogData.amount}
Remark{detailLogData.remark}
Payment Request
{detailLogData.payment_request}
Payment Response
{detailLogData.payment_response}
Status{renderStatusBadge(detailLogData.status)}
Prosess Date{detailLogData.request_date && moment(detailLogData.request_date).isValid() ? moment(detailLogData.request_date).format('YYYY-MM-DD HH:mm:ss') : '-'}
Response Date{detailLogData.response_date && moment(detailLogData.response_date).isValid() ? moment(detailLogData.response_date).format('YYYY-MM-DD HH:mm:ss') : '-'}
Reference Number{detailLogData.reference}
+
+ + + ) : (
)} + + + + ); +}; + +export default TransactionLogViewer; diff --git a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx index 19e2382..b1807a1 100644 --- a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx @@ -10,6 +10,7 @@ import { Button } from '@/components/ui/button'; import { useNavigate } from 'react-router'; import moment from 'moment'; import DetailTransaction from '../blocks/DetailTransaction'; +import TransactionLogViewer from '../blocks/DetailTransactionLog'; interface TransactionProps { id: number; @@ -31,6 +32,11 @@ interface ContextProps { setSelectedTransactionId: React.Dispatch>; showUploadBatchDialog: boolean; handleUploadBatchDialog: (show: boolean) => void; + showDetailLogDialog: boolean; + // handleDetailLogDialog: (show: boolean) => void; + setShowDetailLogDialog: React.Dispatch>; + setDetailLogData: React.Dispatch>; + detailLogData: any | null } const initialProps: ContextProps = { @@ -41,6 +47,11 @@ const initialProps: ContextProps = { setSelectedTransactionId: () => { }, showUploadBatchDialog: false, handleUploadBatchDialog: (show: boolean) => {}, + showDetailLogDialog: false, + // handleDetailLogDialog: (show: boolean) => {}, + setShowDetailLogDialog: () => { }, + setDetailLogData: () => {}, + detailLogData: null, }; type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y'; @@ -67,6 +78,8 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const [showDetailDialog, setShowDetailDialog] = useState(false); const [selectedTransactionId, setSelectedTransactionId] = useState(null); const [showUploadBatchDialog, setShowUploadBatchDialog] = useState(false); + const [showDetailLogDialog, setShowDetailLogDialog] = useState(false); + const [detailLogData, setDetailLogData] = useState(null); const [transaction, setTransaction] = useState([]); const { GetData } = useCallApi(); const navigate = useNavigate(); @@ -74,6 +87,9 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const handleUploadBatchDialog = useCallback((show: boolean) => { setShowUploadBatchDialog(show); }, []); + // const handleDetailLogDialog = useCallback((show: boolean) => { + // setShowDetailLogDialog(show); + // }, []); const columns = useMemo[]>( () => [ @@ -262,11 +278,17 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { selectedTransactionId, setSelectedTransactionId, handleUploadBatchDialog, - showUploadBatchDialog + showUploadBatchDialog, + // handleDetailLogDialog, + showDetailLogDialog, + setShowDetailLogDialog, + setDetailLogData, + detailLogData }} > + void; + setSelectedLog: (log: LogDetail | null) => void; +} + +const TransactionDialogContext = createContext(undefined); + +export const TransactionDialogProvider = ({ children }: { children: React.ReactNode }) => { + const [showLogDialog, setShowLogDialog] = useState(false); + const [selectedLog, setSelectedLog] = useState(null); + + return ( + + {children} + + ); +}; + +export const useTransactionDialog = () => { + const context = useContext(TransactionDialogContext); + if (!context) throw new Error("useTransactionDialog must be used within TransactionDialogProvider"); + return context; +}; diff --git a/src/pages/master/wallet/blocks/ListToolbar.tsx b/src/pages/master/wallet/blocks/ListToolbar.tsx index 11b5767..1b35ce4 100644 --- a/src/pages/master/wallet/blocks/ListToolbar.tsx +++ b/src/pages/master/wallet/blocks/ListToolbar.tsx @@ -16,8 +16,10 @@ const ListToolbar = () => { table.getColumn('name')?.setFilterValue(event.target.value)} + value={(table.getColumn('wallets.name')?.getFilterValue() as string) ?? ''} + onChange={(event) => + table.getColumn('wallets.name')?.setFilterValue(event.target.value) + } /> {/* diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx index 65c7215..3245347 100644 --- a/src/pages/members/kyc/Kyc.tsx +++ b/src/pages/members/kyc/Kyc.tsx @@ -157,7 +157,8 @@ const Kyc = () => { } if (dialogType === 'update') { await axios.put(`${BASE_URL}/customer/update/${customerId}`, form); - if (updateData.isneedapproval == 1) await axios.post(`${BASE_URL}/customer/approve`, { customerid: customerId, description: description}); + if (updateData.isneedapproval == 1&&destinationGroup === "Premium") await axios.post(`${BASE_URL}/customer/approve`, { customerid: customerId, description: updateData.approval_description_premium}); + if (updateData.isneedapproval == 1&&destinationGroup === "Agent") await axios.post(`${BASE_URL}/customer/approve`, { customerid: customerId, description: updateData.approval_description_agent}); } await fetchCustomers(); setDialogOpen(false); @@ -233,6 +234,7 @@ const Kyc = () => {
{ let temp = 1; let resMembers = customers.data.data.list.map((el: any) => { el.no = temp++; + if (el.date_birth) { + const d = new Date(el.date_birth); + el.date_birth = d.toLocaleString("sv-SE"); + } el.name = el.fullname; return el; }); @@ -77,6 +81,7 @@ const ManageMembers = () => { function createMember() { setDialogType('create'); + setSelectedMember('') setMember(initialMember); setIsDialogOpen(true); } @@ -132,13 +137,35 @@ const ManageMembers = () => { if (updateData[property]) form.append(property, updateData[property]); } - if (dialogType === 'update') await axios.put(`${BASE_URL}/customer/update/${selectedMember}`, form, { + if (dialogType === 'update') { + await axios.put(`${BASE_URL}/customer/update/${selectedMember}`, form, { headers: { 'Content-Type': 'multipart/form-data' } }); - // if (dialogType === 'create') await axios.post(`${BASE_URL}/customers/create`, member) - toast.success('Success Update Member'); + toast.success('Success Edit Member'); + } + if (dialogType === 'create') { + const createMember:any = member + createMember.pin = "admin" + delete createMember.password; + delete createMember.try_pin; + delete createMember.license_number; + delete createMember.isneedapproval; + delete createMember.isapproved; + delete createMember.approveddate; + delete createMember.approvedby; + delete createMember.updated_by; + delete createMember.deleted_by; + delete createMember.deleted_at; + delete createMember.group; + delete createMember.point_tier; + delete createMember.approval_description_premium; + delete createMember.approval_description_agent; + delete createMember.language; + await axios.post(`${BASE_URL}/customers/create`, member) + toast.success('Success Create Member. PIN sent to email'); + } } catch (error: any) { toast.error(error.message); } finally { @@ -170,7 +197,7 @@ const ManageMembers = () => { onYes={handleYes} onNo={() => setDialogOpen(false)} /> - { member.id !== '' ? ( + { (member.id!=='' || dialogType==='create') ? ( { initialData={member} fetchCustomers={fetchCustomers} profession={profession} + dialogType={dialogType} /> ): ""}

Manage Members

diff --git a/src/pages/members/manage-members/blocks/AdmAccess.tsx b/src/pages/members/manage-members/blocks/AdmAccess.tsx index 735e832..0c4ceec 100644 --- a/src/pages/members/manage-members/blocks/AdmAccess.tsx +++ b/src/pages/members/manage-members/blocks/AdmAccess.tsx @@ -21,14 +21,7 @@ import ConfirmDialog from '@/components/confirm'; const BASE_URL_CUSTOMER = apiConfig.service_customer; // ACCESS ADM -export default function AdmAccess( - page: string, - data: any, - handleClose: any, - fetchCustomers: any, - viewOnly: any, - setViewOnly: any -) { +export default function AdmAccess({page,formData,handleClose,fetchCustomers,viewOnly,setViewOnly}: any) { const [dialogOpen, setDialogOpen] = useState(false); const [dialogType, setDialogType] = useState(''); const [changeGroup, setChangeGroup] = useState(''); @@ -59,10 +52,10 @@ export default function AdmAccess( const handleYes = async () => { try { if (dialogType === 'update status') { - let statusNext = getPinStatus(data.status).res; + let statusNext = getPinStatus(formData.status).res; if (statusNext) await axios.put(`${BASE_URL_CUSTOMER}/customer/statuspin`, { - customerid: data.id, + customerid: formData.id, status: statusNext }); else toast.error('Handle Active/Suspend only'); @@ -70,8 +63,8 @@ export default function AdmAccess( toast.success('Success Update Status'); } if (dialogType === 'reset pin') { - if (data.id) - await axios.post(`${BASE_URL_CUSTOMER}/customer/resetpin`, { customerid: data.id }); + if (formData.id) + await axios.post(`${BASE_URL_CUSTOMER}/customer/resetpin`, { customerid: formData.id }); else throw { message: 'data.id not found' }; await fetchCustomers(); toast.success('Pin will send to customer MSISDN'); @@ -99,10 +92,10 @@ export default function AdmAccess( async function buttonChangeGroup() { try { let dataObj = { - customerid: data.id, + customerid: formData.id, destination_group: changeGroup }; - if (data.group_id === changeGroup) + if (formData.group_id === changeGroup) throw { message: `You update same group as the exist customer group` }; if (dataObj.customerid && dataObj.destination_group) { await axios.post(`${BASE_URL_CUSTOMER}/customer/change-group`, dataObj); @@ -119,14 +112,14 @@ export default function AdmAccess( function openChangeGroupDialog(e: any) { e.preventDefault(); - setChangeGroup(data.group_id); + setChangeGroup(formData.group_id); setChangeGroupD(true); } function btnConfirmDialog(status: boolean) { setDialogOpen(status); } - + if (!formData.id) return ''; if (page !== 'kyc') { return (
@@ -136,9 +129,9 @@ export default function AdmAccess( {/* Left Side */}
-

Pin Status: {getPinStatus(data.status).msg}

+

Pin Status: {getPinStatus(formData.status).msg}

diff --git a/src/pages/members/manage-members/blocks/CustomerWallet.tsx b/src/pages/members/manage-members/blocks/CustomerWallet.tsx index 11d4bbc..3522cb7 100644 --- a/src/pages/members/manage-members/blocks/CustomerWallet.tsx +++ b/src/pages/members/manage-members/blocks/CustomerWallet.tsx @@ -6,7 +6,7 @@ import { apiConfig } from '@/config/api.config'; import { toast } from 'sonner'; const BASE_URL_CUSTOMER = apiConfig.service_customer; -export default function CustomerWallet(customerid: any) { +export default function CustomerWallet({customerid}: any) { const [customerWallet, setCustomerWallet] = useState([]); if (!customerid) return ''; @@ -21,7 +21,7 @@ export default function CustomerWallet(customerid: any) { }); setCustomerWallet(getCustWallet.data.data.data); } catch (error: any) { - // toast.error(error.message); + setCustomerWallet([]) toast.error(`Wallet Not Found`); } } @@ -30,7 +30,7 @@ export default function CustomerWallet(customerid: any) {

Wallet Member

- {customerWallet.length ? ( + {customerWallet ? (
diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 370f957..8749113 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -29,7 +29,7 @@ import AdmAccess from './AdmAccess'; import CustomerWallet from './CustomerWallet'; const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialData, handleReject, page, fetchCustomers, - handleClose, profession + handleClose, profession, dialogType }: any) => { const [formData, setFormData] = useState(initialData || initialMember); const [viewOnly, setViewOnly] = useState(false); @@ -168,10 +168,17 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa function buttonOnSubmit(e:any) { e.preventDefault(); - if (page === 'kyc' && formData.destinationGroup === "Premium" && !formData.approval_description_premium) return toast.warning(`Approval Premium Description needed!`) - if (page === 'kyc' && formData.destinationGroup === "Agent" && !formData.approval_description_premium) return toast.warning(`Approval Agent Description needed!`) - if (!formData.msisdn||!formData.email||!formData.fullname||!formData.username||!formData.mother_fullname||!formData.address||!formData.nationality||!formData.date_birth||!formData.gender) { - return toast.warning(`Required fields cannot be empty: msisdn, email, fullname, username, mother Full Name, Address, Nationality, Date Of Birth, Gender.!`) + if (dialogType === 'update') { + if (page === 'kyc' && formData.destinationGroup === "Premium" && !formData.approval_description_premium) return toast.warning(`Approval Premium Description needed!`) + if (page === 'kyc' && formData.destinationGroup === "Agent" && !formData.approval_description_premium) return toast.warning(`Approval Agent Description needed!`) + if (!formData.msisdn||!formData.email||!formData.fullname||!formData.username||!formData.mother_fullname||!formData.address||!formData.nationality||!formData.date_birth||!formData.gender) { + return toast.warning(`Required fields cannot be empty: msisdn, email, fullname, username, mother Full Name, Address, Nationality, Date Of Birth, Gender.!`) + } + } + if (dialogType === 'create') { + if (!formData.msisdn||!formData.email||!formData.fullname||!formData.username||!formData.mother_fullname||!formData.address||!formData.nationality||!formData.date_birth||!formData.gender) { + return toast.warning(`Required fields cannot be empty: msisdn, email, fullname, username, mother Full Name, Address, Nationality, Date Of Birth, Gender.!`) + } } // if (page === 'kyc' && !formData.description) return toast.warning(`Description for approval needed!`) handleSubmit(formData); @@ -204,79 +211,81 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa )} - {/* */} - {/* onSubmit={(e) => buttonOnSubmit(e, formData)} */} -
+
- {formData.id ? generateInput(formData, handleChange, 'Group', 'group_name', 'text', true, true): ''} - {formData.id ? generateInput(formData, handleChange, 'Username', 'username', 'text', true, true): ''} - {formData.id ? generateInput(formData, handleChange, 'Full Name', 'fullname', 'text', true, viewOnly): ''} - {formData.id ? generateImage(formData, handleChange, 'Photo', 'photouser'): ''} - {formData.id ? generateList(formData, handleChange, genders, 'gender', 'Gender', null, true): ''} - {formData.id ? generateInput(formData, handleChange, 'Date of Birth', 'date_birth', 'date', true, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Phone Number', 'msisdn', 'text', true, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Email', 'email', 'text', true, viewOnly): ''} - {/* {generateList(formData, handleChange, status, 'status', 'Status', null, true)} */} - {formData.id ? generateList(formData, handleChange, profession, 'profession', 'Profession', null, false): ''} - {formData.id ? generateList(formData, handleChange, municipios, 'municipio', 'Municipio', null, false): ''} - {formData.id ? generateList(formData, handleChange, postoAdm, 'posto_adms', 'Posto', 'posto_adms_id', false): ''} - {formData.id ? generateList(formData, handleChange, sucos, 'suco', 'Suco', 'sucos_id', false): ''} - {formData.id ? generateList(formData, handleChange, aldeias, 'aldeia', 'Aldeia', null, false): ''} - {formData.id ? generateInput(formData, handleChange, 'Nationality', 'nationality', 'text', true, viewOnly): ''} -
- -
    - {nationality.map((item: any, index) => ( -
  • handleChange({target:{name: 'nationality', value: item.name }})} - className="bg-gray-100 px-4 py-2 rounded hover:bg-gray-200 cursor-default transition"> - {item.name} -
  • - ))} - {formData.nationality && nationality.length === 0 && ( -
  • No results found.
  • - )} -
-
- {formData.id ? generateInput(formData, handleChange, 'Address', 'address', 'text', true, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Mother Fullname', 'mother_fullname', 'text', true, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Identity Number', 'identity_number', 'text', false, viewOnly): ''} - - {formData.id ? generateInput(formData, handleChange, 'License Number', 'license_number', 'text', false, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Merchant Name', 'agent_name', 'text', false, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Merchant Address', 'merchantaddress', 'text', false, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Longitude Merchant', 'longitudemerchant', 'text', false, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Latitude Merchant', 'latitudemerchant', 'text', false, viewOnly): ''} - {formData.id ? generateImage(formData, handleChange, 'File Selfie', 'file_selfie'): ''} - {formData.id ? generateImage(formData, handleChange, 'File Document', 'file_document_id'): ''} - {formData.id ? generateImage(formData, handleChange, 'File Document & Selfie', 'file_document_id_selfie'): ''} - {formData.id ? generateImage(formData, handleChange, 'File Commercial License', 'file_commercial_license'): ''} - - {formData.id ? generateList(formData, handleChange, banks, 'bank_name', 'Bank Name', null, false): ''} - {formData.id ? generateInput(formData, handleChange, 'Bank Account', 'bank_account', 'text', false, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'iBank Number', 'ibank_number', 'text', false, viewOnly): ''} - - {(formData.id&&(!page||formData.destinationGroup === "Premium")) ? generateInput(formData, handleChange, 'Approval Premium Description', 'approval_description_premium', 'text', false, viewOnly): ''} - {(formData.id&&(!page||formData.destinationGroup === "Agent")) ? generateInput(formData, handleChange, 'Approval Agent Description', 'approval_description_agent', 'text', false, viewOnly): ''} - - {(formData.id && page!=='kyc') ? AdmAccess(page, formData, handleClose,fetchCustomers, viewOnly, setViewOnly): ""} - {(formData.id && page!=='kyc') ? CustomerWallet(formData.id) : ""} - -
- - - {/* */} - { - formData.isneedapproval == 1 && page === 'kyc' ? ( - - ) : ('') - } - -
+ {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Group', 'group_name', 'text', true, true): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Username', 'username', 'text', true, dialogType==='create'?false:true): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Full Name', 'fullname', 'text', true, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'Photo', 'photouser'): ''} + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, genders, 'gender', 'Gender', null, true): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Date of Birth', 'date_birth', 'date', true, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Phone Number', 'msisdn', 'text', true, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Email', 'email', 'text', true, viewOnly): ''} + {/* {generateList(formData, handleChange, status, 'status', 'Status', null, true)} */} + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, profession, 'profession', 'Profession', null, false): ''} + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, municipios, 'municipio', 'Municipio', null, false): ''} + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, postoAdm, 'posto_adms', 'Posto', 'posto_adms_id', false): ''} + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, sucos, 'suco', 'Suco', 'sucos_id', false): ''} + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, aldeias, 'aldeia', 'Aldeia', null, false): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Nationality', 'nationality', 'text', true, viewOnly): ''} +
+ +
    + {nationality.map((item: any, index) => ( +
  • handleChange({target:{name: 'nationality', value: item.name }})} + className="bg-gray-100 px-4 py-2 rounded hover:bg-gray-200 cursor-default transition"> + {item.name} +
  • + ))} + {formData.nationality && nationality.length === 0 && ( +
  • No results found.
  • + )} +
- {/* */} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Address', 'address', 'text', true, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Mother Fullname', 'mother_fullname', 'text', true, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Identity Number', 'identity_number', 'text', false, viewOnly): ''} + + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'License Number', 'license_number', 'text', false, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Merchant Name', 'agent_name', 'text', false, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Merchant Address', 'merchantaddress', 'text', false, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Longitude Merchant', 'longitudemerchant', 'text', false, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Latitude Merchant', 'latitudemerchant', 'text', false, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Selfie', 'file_selfie'): ''} + {(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Document', 'file_document_id'): ''} + {(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Document & Selfie', 'file_document_id_selfie'): ''} + {(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Commercial License', 'file_commercial_license'): ''} + + {(formData.id || dialogType === "create") ? generateList(formData, handleChange, banks, 'bank_name', 'Bank Name', null, false): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Bank Account', 'bank_account', 'text', false, viewOnly): ''} + {(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'iBank Number', 'ibank_number', 'text', false, viewOnly): ''} + + {(formData.id&&(!page||formData.destinationGroup === "Premium")) ? generateInput(formData, handleChange, 'Approval Premium Description', 'approval_description_premium', 'text', false, viewOnly): ''} + {(formData.id&&(!page||formData.destinationGroup === "Agent")) ? generateInput(formData, handleChange, 'Approval Agent Description', 'approval_description_agent', 'text', false, viewOnly): ''} + + {(formData.id && page!=='kyc') ? ( + + ): ""} + {(formData.id && page!=='kyc') ? ( + + ) : ""} + +
+ + + {/* */} + { + formData.isneedapproval == 1 && page === 'kyc' ? ( + + ) : ('') + } + +
+
+
@@ -289,8 +298,7 @@ export default DetailMember; function generateInput(formData:any, handleChange:any, label:string, name:string, type: string, required: boolean, disabled: boolean) { function generateDate(isoString: string) { - const date = new Date(isoString); - return date.toISOString().slice(0, 10); // "2000-01-18" + return isoString.slice(0, 10); // "2000-01-18" } return ( <> diff --git a/src/pages/menu/manage-menu/blocks/ListToolbar.tsx b/src/pages/menu/manage-menu/blocks/ListToolbar.tsx index 9ed6b64..8d1915c 100644 --- a/src/pages/menu/manage-menu/blocks/ListToolbar.tsx +++ b/src/pages/menu/manage-menu/blocks/ListToolbar.tsx @@ -1,10 +1,22 @@ import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; import { useManageMenusContext } from '../hooks/useManageMenusContext'; import { Button } from '@/components/ui/button'; +import React, { useState } from 'react'; const ListToolbar = () => { const { table, reload } = useDataGrid(); const { handleAddDialog } = useManageMenusContext(); + const [searchValue, setSearchValue] = useState(''); + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleSearch(); + } + }; + + const handleSearch = () => { + table.getColumn('name')?.setFilterValue(searchValue); + }; return (
@@ -16,10 +28,16 @@ const ListToolbar = () => { table.getColumn('name')?.setFilterValue(event.target.value)} + value={searchValue} + onChange={(event) => setSearchValue(event.target.value)} + onKeyDown={handleKeyDown} /> + + + {/* -
diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index 895a1dd..6123f3f 100644 --- a/src/pages/transfer/transferfee/blocks/EditDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/EditDialog.tsx @@ -7,7 +7,8 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select'; - +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; import { Dialog, DialogBody, @@ -59,6 +60,7 @@ const EditFeeDialog = () => { const [transactionTypes, setTransactionTypes] = useState([]); const parsedUser = getAuth()?.user; const [customers, setCustomers] = useState([]); + const [open, setOpen] = useState(false); const [alert, setAlert] = useState({ show: false, message: '' @@ -94,24 +96,22 @@ const EditFeeDialog = () => { const formattedTime = updated_time.toISOString().slice(0, 19).replace('T', ' '); if (showEditFeeDialog) { - setFormField({ - ...formField, + setFormField(prevState => ({ + ...prevState, updated_by: parsedUser?.username, updated_at: formattedTime - }); + })); } }, [showEditFeeDialog]); const resetForm = () => { if (selectedTransferFee) { - // Re-fetch the current data to reset the form to original values fetchTransactionFee(selectedTransferFee); } else { setFormField(initialState); } }; - /* actions */ const doUpdateTransferFee = useCallback( async (e: React.FormEvent) => { e.preventDefault(); @@ -167,6 +167,11 @@ const EditFeeDialog = () => { toast.success('Successfully updated transfer fee'); reload(); handleEditFeeDialog(false, null); + const createActivity = { + module: 'Manage Transfer Type', + description: `Edit Transfer Type => ${selectedTransferFee}`, + action: 'U' + }; } else { setAlert({ show: true, message: response?.message || 'Failed to update transfer fee' }); } @@ -257,15 +262,10 @@ const EditFeeDialog = () => { const fetchTransactionFee = useCallback(async (id: string) => { try { - const response = await GetData(`${API_URL}/transactionfees/getdata/${id}`, { id }); + const response = await GetData(`${API_URL}/transactionfees/getdata/${id}`, { }); if (response?.status) { - let creditTo = ''; - if (response.data.credit_destination?.id === '00000000-0000-0000-0000-000000000000') { - creditTo = 'D'; - } else { - creditTo = 'I'; - } + setFormField({ ...initialState, @@ -282,23 +282,21 @@ const EditFeeDialog = () => { priority: response.data.priority || '', status: response.data.status || '', status_include: response.data.status_include || '', - deduct_from: 'S', + deduct_from: response.data.deduct_from||'', deduct_from_account: response.data.deduct_from_account?.id || '', - credit_to: creditTo, + 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', ' ') }); } - console.log(response) } catch (error) { console.error('Error fetching transaction fee details', error); setAlert({ show: true, message: 'Failed to fetch transaction fee details' }); } - }, [GetData, parsedUser?.username]); // only use username if that's all you need + }, [GetData, parsedUser?.username]); - // This flag ensures it only fetches once per open const hasFetchedRef = useRef(false); useEffect(() => { @@ -307,15 +305,23 @@ const EditFeeDialog = () => { hasFetchedRef.current = true; } - // Reset fetch flag if dialog is closed if (!showEditFeeDialog) { hasFetchedRef.current = false; } }, [selectedTransferFee, showEditFeeDialog, fetchTransactionFee]); + const handleCloseDialog = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + handleEditFeeDialog(false, null); + }; return ( - handleEditFeeDialog(open, null)}> + { + if (!open) { + handleCloseDialog(); + } + }}> @@ -328,7 +334,7 @@ const EditFeeDialog = () => {
handleEditFeeDialog(false, null)} + onClick={handleCloseDialog} >
@@ -654,13 +660,6 @@ const EditFeeDialog = () => {
- diff --git a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx index 6f6bee5..15b650f 100644 --- a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx +++ b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx @@ -1,300 +1,391 @@ -import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; -import { Toaster } from '@/components/ui/sonner'; -import { apiConfig } from '@/config/api.config'; -import { useCallApi } from '@/hooks'; -import { ColumnDef } from '@tanstack/react-table'; -import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; -import ListToolbar from '../blocks/ListToolBar'; -import DeleteDialog from '../blocks/DeleteDialog'; -import { EditFeeDialog } from '../blocks/EditDialog'; -import { useManageTransferTypeContext } from '../../transfertype/hooks/useManageTransferTypeContext'; + import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; + import { Toaster } from '@/components/ui/sonner'; + import { apiConfig } from '@/config/api.config'; + import { useCallApi } from '@/hooks'; + import { ColumnDef } from '@tanstack/react-table'; + import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; + import ListToolbar from '../blocks/ListToolBar'; + import DeleteDialog from '../blocks/DeleteDialog'; + import { EditFeeDialog } from '../blocks/EditDialog'; + import { useManageTransferTypeContext } from '../../transfertype/hooks/useManageTransferTypeContext'; -interface ContextProps { - showEditFeeDialog: boolean; - handleEditFeeDialog: (show: boolean, selected_TransferFee: string | null) => void; - showAddFeeDialog: boolean; - handleAddFeeDialog: (show: boolean) => void; - handleDeleteFeeDialog: (show: boolean, selected_TransferFee: string | null) => void; - showDeleteFeeDialog: boolean; - selectedTransferFee: string | null; -} + interface ContextProps { + showEditFeeDialog: boolean; + handleEditFeeDialog: (show: boolean, selected_TransferFee: string | null) => void; + showAddFeeDialog: boolean; + handleAddFeeDialog: (show: boolean) => void; + handleDeleteFeeDialog: (show: boolean, selected_TransferFee: string | null) => void; + showDeleteFeeDialog: boolean; + selectedTransferFee: string | null; + transactionTypeId: string | null; -const initialProps: ContextProps = { - showEditFeeDialog: false, - handleEditFeeDialog: () => {}, - showAddFeeDialog: false, - handleAddFeeDialog: () => {}, - showDeleteFeeDialog: false, - handleDeleteFeeDialog: () => {}, - selectedTransferFee: null -}; + } -interface TransferFeeProps { - name: string; - description: string; - minimum_amount: number; - maximum_amount:number; - period_start:string; - period_end:string; - deduct_amount: number; - deduct_percentage: number; - transaction_type: string; - status: string; - status_include: string; - fee: number; -} + const initialProps: ContextProps = { + showEditFeeDialog: false, + handleEditFeeDialog: () => {}, + showAddFeeDialog: false, + handleAddFeeDialog: () => {}, + showDeleteFeeDialog: false, + handleDeleteFeeDialog: () => {}, + selectedTransferFee: null, + transactionTypeId: null, -const ManageTransferFeeContext = createContext(initialProps); -const API_URL = apiConfig.service_transaction; - -const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactNode }) => { - const [showEditFeeDialog, setShowEditFeeDialog] = useState(false); - const [showAddFeeDialog, setShowAddFeeDialog] = useState(false); - const [showDeleteFeeDialog, setShowDeleteFeeDialog] = useState(false); - const { showAddDialog, handleAddDialog, selectedTransferType } = useManageTransferTypeContext(); - - const [selectedTransferFee, setSelectedTransferFee] = useState(null); - const { GetData } = useCallApi(); - - const handleEditFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => { - setSelectedTransferFee(show ? selected_TransferFee : null); - setShowEditFeeDialog(show); - }, []); - - const handleAddFeeDialog = useCallback((show: boolean) => { - setShowAddFeeDialog(show); - }, []); - - const handleDeleteFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => { - setSelectedTransferFee(show ? selected_TransferFee : null); - setShowDeleteFeeDialog(show); - }, []); - const doGetTransferFeeListData = async ( - page: number, - limit: number, - sorting: any, - filter: any - ) => { - if (!selectedTransferType) { - return { data: [], totalCount: 0 }; - } - - sorting = sorting.length === 0 ? [{ id: 'id', desc: false }] : sorting; - filter = filter.length === 0 ? {} : { any: filter[0].value.toLowerCase() }; - - try { - const response = await GetData(`${API_URL}/transactionfees/getdatabytransactiontype/${selectedTransferType}`, {}); - - console.log('API Response:', response?.data); - - return { - data: response?.data , - totalCount: 1 - }; - } catch (error) { - console.error("Error fetching transaction fees by transaction type:", error); - return { data: [], totalCount: 0 }; - } }; - - const columns = useMemo[]>( - () => [ - { - accessorFn: (row) => row.name, - id: 'name', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[200px]' } - }, - { - accessorFn: (row) => row.description, - id: 'description', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[250px]' } - }, - { - accessorFn: (row) => row.minimum_amount, - id: 'minimum_amount', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - { - accessorFn: (row) => row.maximum_amount, - id: 'maximum_amount', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - { - accessorFn: (row) => row.fee_amount, - id: 'fee_amount', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - { - accessorFn: (row) => row.deduct_amount, - id: 'deduct_amount', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - { - accessorFn: (row) => row.deduct_percentage, - id: 'deduct_percentage', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - - { - accessorFn: (row) => row.period_start?.split('T')[0], - id: 'period_start', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[200px]' } - }, - { - accessorFn: (row) => row.period_end?.split('T')[0], - id: 'period_end', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[200px]' } - }, - { - accessorFn: (row) => row.transaction_type?.name, - id: 'transactionTypeId', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[250px]' } - }, - { - accessorFn: (row) => row.credit_destination?.fullname, - id: 'credit_destination', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[250px]' } - }, - { - accessorFn: (row) => row.credit_destination_account?.description, - id: 'credit_destination_account', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[250px]' } - }, - { - accessorFn: (row) => row.deduct_from_account?.description, - id: 'deduct_from_account', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[250px]' } - }, - { - accessorFn: (row) => (row.priority === 'Y' ? 'Yes' : 'No'), - id: 'priority', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[100px]' } - }, - { - accessorFn: (row) => (row.status === 'Y' ? 'Active' : 'Inactive'), - id: 'status', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - { - accessorFn: (row) => (row.status_include === 'Y' ? 'Yes' : 'No'), - id: 'status_include', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[150px]' } - }, - { - id: 'actions', - header: ({ column }) => , - enableSorting: false, - enableHiding: false, - cell: (data) => { - const row = data.row.original; - return ( - <> - - - - ); - }, - meta: { - headerClassName: 'w-[100px]', - cellClassName: 'text-center' - } + + interface TransferFeeProps { + name: string; + description: string; + minimum_amount: number; + maximum_amount:number; + period_start:string; + period_end:string; + deduct_amount: number; + deduct_percentage: number; + transaction_type: string; + status: string; + status_include: string; + fee: number; + } + + const ManageTransferFeeContext = createContext(initialProps); + const API_URL = apiConfig.service_transaction; + + const ManageTransferFeeContextProvider = ({ children, transactionTypeId = null }: { children: React.ReactNode; transactionTypeId?: string | null }) => { + const [showEditFeeDialog, setShowEditFeeDialog] = useState(false); + const [showAddFeeDialog, setShowAddFeeDialog] = useState(false); + const [showDeleteFeeDialog, setShowDeleteFeeDialog] = useState(false); + const { showAddDialog, handleAddDialog, selectedTransferType } = useManageTransferTypeContext(); + const [selectedTransferFee, setSelectedTransferFee] = useState(null); + const { GetData } = useCallApi(); + const handleEditFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => { + setSelectedTransferFee(show ? selected_TransferFee : null); + setShowEditFeeDialog(show); + }, []); + + const handleAddFeeDialog = useCallback((show: boolean) => { + setShowAddFeeDialog(show); + }, []); + + const handleDeleteFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => { + setSelectedTransferFee(show ? selected_TransferFee : null); + setShowDeleteFeeDialog(show); + }, []); + const doGetTransferFeeListData = async ( + page: number, + limit: number, + sorting: any, + filter: any + ) => { + if (!selectedTransferType) { + return { data: [], totalCount: 0 }; } - ], - [handleEditFeeDialog, handleDeleteFeeDialog] - ); - - return ( -
-
-

Manage Transaction Fee

-
- - - } - sorting={[{ id: 'id', desc: true }]} - serverSide={true} - onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => - doGetTransferFeeListData(pageIndex, pageSize, sorting, columnFilters) + + sorting = sorting.length === 0 ? [{ id: 'id', desc: false }] : sorting; + filter = filter.length === 0 ? {} : { any: filter[0].value.toLowerCase() }; + + try { + const response = await GetData(`${API_URL}/transactionfees/getdatabytransactiontype/${selectedTransferType}`, {}); + + // console.log('API Response:', response?.data); + + return { + data: response?.data , + totalCount: 1 + }; + } catch (error) { + console.error("Error fetching transaction fees by transaction type:", error); + return { data: [], totalCount: 0 }; + } + }; + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[200px]' } + }, + { + accessorFn: (row) => row.description, + id: 'description', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row) => row.minimum_amount, + id: 'minimum_amount', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[150px]' } + }, + { + accessorFn: (row) => row.maximum_amount, + id: 'maximum_amount', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[150px]' } + }, + { + accessorFn: (row) => row.fee_amount, + id: 'fee_amount', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[150px]' } + }, + { + accessorFn: (row) => row.deduct_amount, + id: 'deduct_amount', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[150px]' } + }, + { + accessorFn: (row) => row.deduct_percentage, + id: 'deduct_percentage', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[150px]' } + }, + + { + accessorFn: (row) => row.period_start?.split('T')[0], + id: 'period_start', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[200px]' } + }, + { + accessorFn: (row) => row.period_end?.split('T')[0], + id: 'period_end', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[200px]' } + }, + { + accessorFn: (row) => row.transaction_type?.name, + id: 'transactionTypeId', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row: { credit_to: string }) => { + const mapping: Record = { + D: 'Destination Member', + S: 'Source Member', + I: 'Input Customer' + }; + + return mapping[row.credit_to] || 'Unknown'; + }, + id: 'credit_to', + header: ({ column }) => ( + + ), + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row) => { + if (row.credit_to === 'S' || row.credit_to === 'D' || !row.credit_destination) { + return 'N/A'; + } + return row.credit_destination?.fullname; + }, + id: 'credit_destination', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row) => row.credit_destination_account?.description, + id: 'credit_destination_account', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row: { deduct_from: string }) => { + const mapping: Record = { + D: 'Destination Member', + S: 'Source Member', + }; + + return mapping[row.deduct_from]; + }, + id: 'deduct_from', + header: ({ column }) => ( + + ), + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row) => row.deduct_from_account?.description, + id: 'deduct_from_account', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, + { + accessorFn: (row) => row.priority, + id: 'priority', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + cell: ({ row }) => { + const isActive = row.original.priority === 'Y'; + + return ( + + {isActive ? 'Yes' : 'No'} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' } + }, + { + accessorFn: (row) => row.status, + id: 'status', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + cell: ({ row }) => { + const isActive = row.original.status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + }, + { + accessorFn: (row) => row.status_include, + id: 'status_include', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + cell: ({ row }) => { + const isActive = row.original.status_include === 'Y'; + + return ( + + {isActive ? 'Yes' : 'No'} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleEditFeeDialog, handleDeleteFeeDialog] + ); + + return ( +
+
+

Manage Transaction Fee

+
+ - {children} + + } + sorting={[{ id: 'id', desc: true }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + doGetTransferFeeListData(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} - - -
- ); -}; +
+
+
+ ); + }; -export { ManageTransferFeeContext, ManageTransferFeeContextProvider }; + export { ManageTransferFeeContext, ManageTransferFeeContextProvider }; diff --git a/src/pages/transfer/transfertype/blocks/AddDialog.tsx b/src/pages/transfer/transfertype/blocks/AddDialog.tsx index bff4222..6d35adc 100644 --- a/src/pages/transfer/transfertype/blocks/AddDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/AddDialog.tsx @@ -39,6 +39,7 @@ import { CommandItem, CommandList } from '@/components/ui/command'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; interface WalletProps { id: string; @@ -95,7 +96,6 @@ const AddDialog = () => { const parsedUser = getAuth()?.user; - // Updated validation function to make only certain fields required const validateForm = () => { const requiredFields = [ 'name', @@ -141,6 +141,13 @@ const AddDialog = () => { toast.success('Success Create Transfer Type'); reload(); resetForm(); + const createActivity = { + module: 'Manage Transfer Type', + description: `Create Transfer Type => ${formField.name}`, + action: 'C' + }; + + doSaveLogActivity(createActivity); } }) .finally(() => { @@ -218,7 +225,7 @@ const AddDialog = () => { order_direction: 'ASC', }; const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, params); - console.log(response) + // console.log(response) if (response?.status && response?.data) { setWallets(response.data.list); } else { @@ -230,7 +237,7 @@ const AddDialog = () => { if (!showAddDialog) return; fetchWallets(); }, [showAddDialog]); - console.log(formField) + // console.log(formField) return ( handleAddDialog(open)}> @@ -436,7 +443,7 @@ const AddDialog = () => {
@@ -451,14 +458,17 @@ const AddDialog = () => { - Disbursement + Disbursement Other - Change Customer to Agent - Change Agent to Customer + Change Group Emoney Customer to Agent + Change Group Emoney Agent to Customer + Change Group Point Agent to Customer + Change Group Point Customer to Agent Return Customer Emoney Return Agent Deposit Return Agent Merchant Return Agent Emoney + Reward Point
diff --git a/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx b/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx index 19e4d78..e4c51ad 100644 --- a/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx @@ -7,6 +7,7 @@ import { toast } from 'sonner'; import { useCallApi } from '@/hooks'; import { useManageTransferTypeContext } from '../hooks/useManageTransferTypeContext'; import { DialogDescription } from '@radix-ui/react-dialog'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; const API_URL = apiConfig.service_transaction; @@ -33,6 +34,12 @@ const DeleteDialog = () => { setAlert({ show: false, message: '' }); handleDeleteDialog(false, null); reload(); + const createActivity = { + module: 'Manage Transfer Type', + description: `Delete Transfer Type => ${selectedTransferType}`, + action: 'D' + }; + doSaveLogActivity(createActivity); // setTimeout(() => toast.success('Success Delete Transaction Type'), 0); } else { setAlert({ show: true, message: response?.message }); diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index d646905..c4b9e8f 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -54,8 +54,7 @@ interface PermissionObject { const EditDialog = () => { const parentRef = useRef(null); - const { showEditDialog, handleEditDialog, selectedTransferType } = - useManageTransferTypeContext(); + const { showEditDialog, handleEditDialog, selectedTransferType } = useManageTransferTypeContext(); const { reload } = useDataGrid(); const [wallets, setWallets] = useState([]); const [groups, setGroups] = useState([]); @@ -68,7 +67,7 @@ const EditDialog = () => { show: false, message: '' }); - + const initialState: { name: string; description: string; @@ -98,9 +97,9 @@ const EditDialog = () => { updated_by: '', updated_at: '' }; - + const [formField, setFormField] = useState(initialState); - + const resetForm = () => { setFormField(initialState); setSelectedGroups([]); @@ -112,13 +111,11 @@ const EditDialog = () => { const isSelected = prevState.permission.includes(groupId); if (isSelected) { - // Remove the permission if already selected return { ...prevState, permission: prevState.permission.filter((id) => id !== groupId) }; } else { - // Add the permission if not selected return { ...prevState, permission: [...prevState.permission, groupId] @@ -138,13 +135,13 @@ const EditDialog = () => { 'type' ]; - 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 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 + ); + }); if (missingFields.length > 0) { setAlert({ @@ -154,7 +151,6 @@ const EditDialog = () => { return false; } - // Validate that at least one permission is selected if (formField.permission.length === 0) { setAlert({ show: true, @@ -179,12 +175,12 @@ const EditDialog = () => { })); } }, [showEditDialog, parsedUser]); - + const selectedPermissionNames = groups .filter((g) => formField.permission.includes(g.id)) .map((g) => g.name) .join(', '); - + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); @@ -200,7 +196,7 @@ const EditDialog = () => { handleEditDialog(false, null); toast.success('Success Update Transfer Type'); reload(); - + const createActivity = { module: 'Manage Transfer Type', description: `Edit Transfer Type => ${selectedTransferType}`, @@ -214,17 +210,19 @@ const EditDialog = () => { setIsSubmitting(false); }); }; - + const doUpdateTransferType = useCallback(async () => { try { - // Create a clean copy of the form data with properly formatted permissions const formDataToSend = { ...formField, - permission: formField.permission.filter(id => typeof id === 'string') + permission: formField.permission.filter((id) => typeof id === 'string') }; - - const response = await PutData(`${API_URL}/transactiontype/update/${selectedTransferType}`, formDataToSend); - + + const response = await PutData( + `${API_URL}/transactiontype/update/${selectedTransferType}`, + formDataToSend + ); + if (response?.status) { setAlert({ show: false, message: '' }); return true; @@ -236,18 +234,15 @@ const EditDialog = () => { setAlert({ show: true, message: - error instanceof Error - ? error.message - : 'An error occurred while updating transfer type' + error instanceof Error ? error.message : 'An error occurred while updating transfer type' }); return false; } }, [formField, selectedTransferType, PutData]); - // Fetch customers useEffect(() => { if (!showEditDialog) return; - + const getCustomerList = async () => { try { const sorting = [{ id: 'id', desc: false }]; @@ -258,7 +253,7 @@ const EditDialog = () => { order_field: sorting[0].id, order_direction: sorting[0].desc ? 'DESC' : 'ASC' }); - + if (response?.status && response?.data) { setCustomers(response.data.list); } @@ -270,10 +265,9 @@ const EditDialog = () => { getCustomerList(); }, [showEditDialog, GetData]); - // Fetch groups useEffect(() => { if (!showEditDialog) return; - + const getGroupList = async () => { try { const response = await GetData(`${API_URL_MASTERDATA}/groups/list`, { @@ -283,7 +277,7 @@ const EditDialog = () => { order_field: 'name', order_direction: 'ASC' }); - + if (response?.status && response?.data) { setGroups(response.data.list); } @@ -295,10 +289,9 @@ const EditDialog = () => { getGroupList(); }, [showEditDialog, GetData]); - // Fetch wallets useEffect(() => { if (!showEditDialog) return; - + const getWalletList = async () => { try { const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, { @@ -306,9 +299,9 @@ const EditDialog = () => { page: 1, with_deleted: false, order_field: 'Wallets.name', - order_direction: 'ASC', + order_direction: 'ASC' }); - + if (response?.status && response?.data) { setWallets(response.data.list); } @@ -320,28 +313,28 @@ const EditDialog = () => { getWalletList(); }, [showEditDialog, GetData]); - // Fetch transaction type data useEffect(() => { if (!showEditDialog || !selectedTransferType) return; - + const fetchTransactionType = async () => { try { - const response = await GetData(`${API_URL}/transactiontype/getdata/${selectedTransferType}`, {}); - console.log(response); + const response = await GetData( + `${API_URL}/transactiontype/getdata/${selectedTransferType}`, + {} + ); + // console.log(response); if (response?.status) { - // Process permissions to ensure they're always string IDs let permissionIds: string[] = []; - + if (Array.isArray(response.data.permission)) { permissionIds = response.data.permission.map((perm: any) => { - // Check if permission is an object or string if (typeof perm === 'object' && perm !== null) { return perm.id; } return perm; }); } - + setFormField((prev) => ({ ...prev, name: response.data.name, @@ -354,7 +347,7 @@ const EditDialog = () => { status_approval: response.data.status_approval, type: response.data.type || '', status: response.data.status, - permission: permissionIds + permission: permissionIds })); } } catch (error) { @@ -365,11 +358,11 @@ const EditDialog = () => { }); } }; - + const timer = setTimeout(() => { fetchTransactionType(); }, 150); - + return () => clearTimeout(timer); }, [showEditDialog, selectedTransferType, GetData]); useEffect(() => { @@ -582,7 +575,7 @@ const EditDialog = () => {
@@ -596,14 +589,17 @@ const EditDialog = () => { - Disbursement + Disbursement Other - Change Customer to Agent - Change Agent to Customer + Change Group Emoney Customer to Agent + Change Group Emoney Agent to Customer + Change Group Point Agent to Customer + Change Group Point Customer to Agent Return Customer Emoney Return Agent Deposit Return Agent Merchant Return Agent Emoney + Reward Point
@@ -659,8 +655,7 @@ const EditDialog = () => {
- - {/* Group Permission Section - Read Only Display */} +
- +
-
- {/* Transaction Fee Section */} - +
- - {/* */} + +
@@ -734,4 +718,4 @@ const EditDialog = () => { ); }; -export { EditDialog }; \ No newline at end of file +export { EditDialog }; diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index 2fde89a..efa6279 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -147,15 +147,31 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React meta: { headerClassName: 'w-[250px]' } }, { - accessorFn: (row) => row.type, + accessorFn: (row: {type: string }) => { + const mapping: Record = { + D: 'Disbursement', + O: 'Other', + CA: 'Change Group Emoney Customer to Agent', + AC:'Change Group Emoney Agent to Customer', + PC: 'Change Group Point Agent to Customer', + PA: 'Change Group Point Customer to Agent', + CE:'Return Customer Emoney', + AD:'Return Agent Deposit', + AM: 'Return Agent Merchant', + AE: 'Return Agent Emoney', + R: 'Reward Point', + }; + + return mapping[row.type] || 'Unknown'; + }, id: 'type', header: ({ column }) => ( - + ), - enableSorting: false, + enableSorting: true, enableHiding: false, meta: { headerClassName: 'w-[250px]' } - }, + }, { accessorFn: (row) => (row.status === 'Y' ? 'Active' : 'Inactive'), id: 'status', @@ -223,7 +239,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React order_direction: orderDirection, filter: JSON.stringify(filter) }); - console.log(response?.data.list); + // console.log(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; }; @@ -250,7 +266,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React pagination={{ size: 10 }} layout={{ card: true }} toolbar={} - sorting={[{ id: 'created_at', desc: true }]} // Default sorting + sorting={[{ id: 'created_at', desc: true }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => doGetTransferTypeListData(pageIndex, pageSize, sorting, columnFilters)