From 6d4c659f9acb7d58018f2874991f32a69781ad86 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 15:50:08 +0700 Subject: [PATCH 01/52] fix detail member --- src/components/confirm.tsx | 32 +- src/components/loaders/LoaderTransparant.tsx | 16 + src/components/loaders/index.ts | 1 + src/config/api.config.ts | 7 +- src/pages/members/kyc/Kyc.tsx | 53 ++- .../manage-members/CustomerDetailModal.tsx | 19 +- .../members/manage-members/ManageMembers.tsx | 64 ++- .../manage-members/blocks/AdmAccess.tsx | 248 ++++++++++++ .../manage-members/blocks/CustomerWallet.tsx | 69 ++++ .../manage-members/blocks/DetailMember.tsx | 364 ++++++++++++++++++ 10 files changed, 814 insertions(+), 59 deletions(-) create mode 100644 src/components/loaders/LoaderTransparant.tsx create mode 100644 src/pages/members/manage-members/blocks/AdmAccess.tsx create mode 100644 src/pages/members/manage-members/blocks/CustomerWallet.tsx create mode 100644 src/pages/members/manage-members/blocks/DetailMember.tsx diff --git a/src/components/confirm.tsx b/src/components/confirm.tsx index b3a5596..67e18e9 100644 --- a/src/components/confirm.tsx +++ b/src/components/confirm.tsx @@ -1,23 +1,21 @@ -import React from "react"; -import Dialog from "@mui/material/Dialog"; -import DialogActions from "@mui/material/DialogActions"; -import DialogContent from "@mui/material/DialogContent"; -import DialogTitle from "@mui/material/DialogTitle"; import Button from "@mui/material/Button"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogDescription, DialogBody } from '@/components/ui/dialog'; -const ConfirmDialog = ({ open, onClose, title, content, onYes, onNo }: any) => { +const ConfirmDialog = ({ open, onClose, title, content, onYes, onNo, onOpenChange }: any) => { return ( - - {title && {title}} - {content && {content}} - - - - + + + + {title && {title}} + + {content && {content}} + + + + + + + ); }; diff --git a/src/components/loaders/LoaderTransparant.tsx b/src/components/loaders/LoaderTransparant.tsx new file mode 100644 index 0000000..b1eb099 --- /dev/null +++ b/src/components/loaders/LoaderTransparant.tsx @@ -0,0 +1,16 @@ +import { toAbsoluteUrl } from '@/utils'; + +const LoaderTransparant = () => { + return ( +
+ logo +
Loading...
+
+ ); +}; + +export { LoaderTransparant }; diff --git a/src/components/loaders/index.ts b/src/components/loaders/index.ts index 1d1dad1..1b8a61e 100644 --- a/src/components/loaders/index.ts +++ b/src/components/loaders/index.ts @@ -1,3 +1,4 @@ export * from './ContentLoader'; export * from './ProgressBarLoader'; export * from './ScreenLoader'; +export * from './LoaderTransparant'; diff --git a/src/config/api.config.ts b/src/config/api.config.ts index 535dc2a..2c6cef5 100644 --- a/src/config/api.config.ts +++ b/src/config/api.config.ts @@ -4,7 +4,8 @@ interface apiConfigProps { service_master_data: string; service_transaction: string; service_wallet: string; - transaction: string + transaction: string; + nationality: string; } const API_URL = import.meta.env.VITE_APP_API_URL; @@ -16,7 +17,9 @@ const apiConfig: apiConfigProps = { service_master_data: `${API_URL}/t`, service_transaction: `${API_URL}/tt`, service_wallet: `${API_URL}/w`, - transaction: `${API_URL}/x` + transaction: `${API_URL}/x`, + nationality: `https://tpay.shiblysolution.id/cms/api/mobile/list-country/ +` }; export { apiConfig }; diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx index 3aa6c6b..452b632 100644 --- a/src/pages/members/kyc/Kyc.tsx +++ b/src/pages/members/kyc/Kyc.tsx @@ -5,12 +5,15 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { ManageKycContextProvider } from './hooks'; import { columns, initialMember } from './Columns'; import { useAuthContext } from '@/auth'; +import { LoaderTransparant } from '@/components'; import { apiConfig } from '@/config/api.config'; import ConfirmDialog from '@/components/confirm'; import axios from 'axios'; import { toast } from 'sonner'; const BASE_URL = apiConfig.service_customer; -import CustomerDialog from '../manage-members/CustomerDetailModal'; +const BASE_URL_MASTER_DATA = apiConfig.service_master_data; +// import CustomerDialog from '../manage-members/CustomerDetailModal'; +import DetailMember from '../manage-members/blocks/DetailMember'; import { Breadcrumbs, Link } from '@mui/material'; import { Helmet } from 'react-helmet'; @@ -37,6 +40,7 @@ const Kyc = () => { const [loading, setLoading] = useState(false); const [members, setMembers] = useState([]); const [member, setMember] = useState(initialMember); + const [profession, setProfession] = useState([]); const [isDialogOpen, setIsDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [dialogType, setDialogType] = useState(''); @@ -44,12 +48,12 @@ const Kyc = () => { const { getUser } = useAuthContext(); useEffect(() => { - fetchGroups(); + fetchCustomers(); }, []); - async function fetchGroups() { + async function fetchCustomers() { try { - let groups = await axios.get(`${BASE_URL}/customer/list`, { + let customers = await axios.get(`${BASE_URL}/customer/list`, { params: { limit: 10, page: 1, @@ -60,12 +64,22 @@ const Kyc = () => { } }); let temp = 1; - let resMembers = groups.data.data.list.map((el: any) => { + let resMembers = customers.data.data.list.map((el: any) => { el.no = temp++; el.name = el.fullname; return el; }); setMembers(resMembers); + let getProfession: any = await axios.get(`${BASE_URL_MASTER_DATA}/profession/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setProfession(getProfession.data.data.list) } catch (error: any) { toast.error(error.message); console.log(error); @@ -90,6 +104,7 @@ const Kyc = () => { } const handleYes = async () => { + setLoading(true) const userLogin: any = await getUser(); const updateData: any = member; const customerId = member.id; @@ -134,17 +149,24 @@ const Kyc = () => { description: description }); } - await fetchGroups(); + await fetchCustomers(); setDialogOpen(false); setIsDialogOpen(false); - toast.success('Success Update Kyc Member'); + toast.success(`Success Update & ${dialogType} Kyc Member`); } catch (error: any) { setDialogOpen(false); setIsDialogOpen(false); toast.error(error.message); + setLoading(false) } }; + function setShowAddDialog(el: any) { + setIsDialogOpen(el) + } + + if (loading) return ; + return ( <> @@ -161,12 +183,23 @@ const Kyc = () => { onNo={() => setDialogOpen(false)} /> { member.id ? ( - + ): ""} diff --git a/src/pages/members/manage-members/CustomerDetailModal.tsx b/src/pages/members/manage-members/CustomerDetailModal.tsx index 152e749..e7c2110 100644 --- a/src/pages/members/manage-members/CustomerDetailModal.tsx +++ b/src/pages/members/manage-members/CustomerDetailModal.tsx @@ -295,8 +295,7 @@ const CustomerDialog = ({ open, handleClose, handleSubmit, initialData, viewStat {/* {getAdmAccess(page, formData, handleClose, fetchCustomers)} */} - { - page === 'kyc' ? ( + { page === 'kyc' ? ( <> Approval @@ -589,23 +588,21 @@ function showCustomerWallet(customerid: any) { }} > - - Name - + Name {item.wallet.name} - - Description - + Description {item.wallet.description} - - Transaction Today - + Balance + {item.amount} + + + Transaction Today {item.transaction_number_today} diff --git a/src/pages/members/manage-members/ManageMembers.tsx b/src/pages/members/manage-members/ManageMembers.tsx index dc82642..b4ebdc7 100644 --- a/src/pages/members/manage-members/ManageMembers.tsx +++ b/src/pages/members/manage-members/ManageMembers.tsx @@ -3,20 +3,22 @@ import { apiConfig } from '@/config/api.config'; import { columns, Members, initialMember } from './Columns'; import { useState, useEffect } from 'react'; import axios from 'axios'; -import CustomerDialog from './CustomerDetailModal'; +// import CustomerDialog from './CustomerDetailModal'; +import DetailMember from './blocks/DetailMember'; import ConfirmDialog from '@/components/confirm'; import { useAuthContext } from '@/auth'; -import { ScreenLoader } from '@/components'; +import { LoaderTransparant } from '@/components'; import { toast } from 'sonner'; import { Breadcrumbs, Link } from '@mui/material'; +const BASE_URL_MASTER_DATA = apiConfig.service_master_data; const BASE_URL = apiConfig.service_customer; import { Helmet } from 'react-helmet'; - const ManageMembers = () => { const [loading, setLoading] = useState(false); const [members, setMembers] = useState([]); const [selectedMember, setSelectedMember] = useState(''); const [member, setMember] = useState(initialMember); + const [profession, setProfession] = useState([]); const [isDialogOpen, setIsDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [dialogType, setDialogType] = useState(''); @@ -34,7 +36,7 @@ const ManageMembers = () => { async function fetchCustomers() { try { - let groups = await axios.get(`${BASE_URL}/customer/list`, { + let customers = await axios.get(`${BASE_URL}/customer/list`, { params: { limit: 20, page: 1, @@ -44,12 +46,22 @@ const ManageMembers = () => { } }); let temp = 1; - let resMembers = groups.data.data.list.map((el: any) => { + let resMembers = customers.data.data.list.map((el: any) => { el.no = temp++; el.name = el.fullname; return el; }); setMembers(resMembers); + let getProfession: any = await axios.get(`${BASE_URL_MASTER_DATA}/profession/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setProfession(getProfession.data.data.list) } catch (error: any) { toast.error(error.message); console.log(error); @@ -75,6 +87,7 @@ const ManageMembers = () => { } const handleYes = async () => { + setLoading(true) const userLogin: any = await getUser(); const updateData: any = member; updateData.updated_by = userLogin.data ? userLogin.data.id : ''; @@ -113,18 +126,22 @@ const ManageMembers = () => { } }); // if (dialogType === 'create') await axios.post(`${BASE_URL}/customers/create`, member) - await fetchCustomers(); - setDialogOpen(false); - setIsDialogOpen(false); toast.success('Success Update Member'); } catch (error: any) { - setDialogOpen(false); - setIsDialogOpen(false); toast.error(error.message); + } finally { + setDialogOpen(false); + closeDialog(); + await fetchCustomers(); + setLoading(false) } }; - if (loading) return ; + function setShowAddDialog(el: any) { + setIsDialogOpen(el) + } + + if (loading) return ; return ( <> @@ -141,14 +158,23 @@ const ManageMembers = () => { onYes={handleYes} onNo={() => setDialogOpen(false)} /> - { member.id ? ( - + { member.id !== '' ? ( + // + ): ""}

Manage Members

diff --git a/src/pages/members/manage-members/blocks/AdmAccess.tsx b/src/pages/members/manage-members/blocks/AdmAccess.tsx new file mode 100644 index 0000000..735e832 --- /dev/null +++ b/src/pages/members/manage-members/blocks/AdmAccess.tsx @@ -0,0 +1,248 @@ +import { useState, useEffect } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue +} from '@/components/ui/select'; +import { apiConfig } from '@/config/api.config'; +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 +) { + const [dialogOpen, setDialogOpen] = useState(false); + const [dialogType, setDialogType] = useState(''); + const [changeGroup, setChangeGroup] = useState(''); + const [changeGroupD, setChangeGroupD] = useState(false); + const [groups, setGroups] = useState([]); + + useEffect(() => { + fetchGroups(); + }, []); + + const fetchGroups = async () => { + try { + let getGroups = await axios.get(`${BASE_URL_CUSTOMER}/groups/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC' + } + }); + setGroups(getGroups.data.data.list); + } catch (error: any) { + toast.error(error.message); + } + }; + + const handleYes = async () => { + try { + if (dialogType === 'update status') { + let statusNext = getPinStatus(data.status).res; + if (statusNext) + await axios.put(`${BASE_URL_CUSTOMER}/customer/statuspin`, { + customerid: data.id, + status: statusNext + }); + else toast.error('Handle Active/Suspend only'); + await fetchCustomers(); + toast.success('Success Update Status'); + } + if (dialogType === 'reset pin') { + if (data.id) + await axios.post(`${BASE_URL_CUSTOMER}/customer/resetpin`, { customerid: data.id }); + else throw { message: 'data.id not found' }; + await fetchCustomers(); + toast.success('Pin will send to customer MSISDN'); + } + } catch (error: any) { + toast.error(error.message); + } finally { + setDialogOpen(false); + handleClose(); + } + }; + + function buttonStatus(e: any) { + e.preventDefault(); + setDialogType('update status'); + setDialogOpen(true); + } + + function buttonResetPin(e: any) { + e.preventDefault(); + setDialogType('reset pin'); + setDialogOpen(true); + } + + async function buttonChangeGroup() { + try { + let dataObj = { + customerid: data.id, + destination_group: changeGroup + }; + if (data.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); + } + toast.success('Success Change group'); + await fetchCustomers(); + setChangeGroupD(false); + handleClose(); + } catch (error: any) { + console.log(error); + toast.error(error.message); + } + } + + function openChangeGroupDialog(e: any) { + e.preventDefault(); + setChangeGroup(data.group_id); + setChangeGroupD(true); + } + + function btnConfirmDialog(status: boolean) { + setDialogOpen(status); + } + + if (page !== 'kyc') { + return ( +
+

Access Administration

+ +
+ {/* Left Side */} +
+
+

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

+ +
+
+

Reset PIN

+ +
+
+ + {/* Right Side */} +
+
+

Change Group

+ +
+
+

Edit Member

+ +
+
+
+ + {/* Change Group Dialog */} + + + + + Are you sure to change customer Group? + + + +
+

Destination Group

+ +
+ + + + + +
+
+ btnConfirmDialog(false)} + onOpenChange={() => setDialogOpen(false)} + title="Confirm Action" + content={`Are you sure you want to ${dialogType}?`} + onYes={handleYes} + onNo={() => btnConfirmDialog(false)} + /> +
+ ); + } else { + return ''; + } +} + +function getPinStatus(status: string) { + if (status === 'Y') + return { + msg: 'Active', + btn: 'Block PIN', + res: 'Block' + }; + if (status === 'N') + return { + msg: 'Not Active', + btn: 'Activate PIN', + res: null + }; + if (status === 'P') + return { + msg: 'Suspend PIN', + btn: 'Unblock PIN', + res: 'UnBlock' + }; + if (status === 'O') + return { + msg: 'Suspend OTP', + btn: 'Unblock OTP', + res: null + }; + return { + msg: 'None', + btn: 'No status found', + res: null + }; +} diff --git a/src/pages/members/manage-members/blocks/CustomerWallet.tsx b/src/pages/members/manage-members/blocks/CustomerWallet.tsx new file mode 100644 index 0000000..11d4bbc --- /dev/null +++ b/src/pages/members/manage-members/blocks/CustomerWallet.tsx @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { Card } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; +import { apiConfig } from '@/config/api.config'; +import { toast } from 'sonner'; +const BASE_URL_CUSTOMER = apiConfig.service_customer; + +export default function CustomerWallet(customerid: any) { + const [customerWallet, setCustomerWallet] = useState([]); + if (!customerid) return ''; + + useEffect(() => { + fetchCustomerWallet(); + }, []); + + async function fetchCustomerWallet() { + try { + let getCustWallet = await axios.get(`${BASE_URL_CUSTOMER}/customer/wallet`, { + params: { customerid: customerid } + }); + setCustomerWallet(getCustWallet.data.data.data); + } catch (error: any) { + // toast.error(error.message); + toast.error(`Wallet Not Found`); + } + } + + return ( +
+

Wallet Member

+ + {customerWallet.length ? ( +
+ + + + + + + + + + + + {customerWallet.map((item:any, index) => ( + + + + + + + + ))} + +
NoNameBalanceMonth LimitCredit Limit
{index+1}{item.wallet}{item.amount}{item.monthly_limit}{item.credit_limit}
+
+ ) : ( +

No wallet

+ )} +
+
+ ); +} diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx new file mode 100644 index 0000000..2cb1950 --- /dev/null +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -0,0 +1,364 @@ +import { apiConfig } from '@/config/api.config'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { getAuth } from '@/auth'; +import axios from 'axios'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +const BASE_URL_MASTER_DATA = apiConfig.service_master_data; +const URL_NATIONALITY = apiConfig.nationality; +import { initialMember } from "../Columns"; +import AdmAccess from './AdmAccess'; +import CustomerWallet from './CustomerWallet'; + +const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialData, handleReject, page, fetchCustomers, + handleClose, profession + }: any) => { + const [formData, setFormData] = useState(initialData || initialMember); + const [viewOnly, setViewOnly] = useState(false); + const [nationality, setNationality] = useState([]); + const [municipios, setMunicipios] = useState([]); + const [aldeias, setAldeias] = useState([]); + const [postoAdm, setPostoAdm] = useState([]); + const [sucos, setSucos] = useState([]); + const [genders] = useState([ { name: 'Male',id: 'M' }, { name: 'Female',id: 'F' }]) + const [status] = useState([ + { name: 'Active',id: 'Y' }, { name: 'Inactive',id: 'N' }, { name: 'Suspend PIN',id: 'P' }, { name: 'Suspend OTP',id: 'O' } + ]) + const [banks] = useState([ + { name: 'BNCTL',id: 'BNCTL' }, { name: 'BRI',id: 'BRI' }, { name: 'BNU',id: 'BNU' }, { name: 'Mandiri',id: 'Mandiri' } + ]) + const parentRef = useRef(null); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + useEffect(() => { + setFormData(initialData || {}); + // fetchMasterData() + }, [initialData]); + + const handleChange = async (e: any) => { + const { name, value } = e.target; + if (name === "file_selfie" || name === "photouser" || name === 'file_document_id' || name === "file_document_id_selfie" || + name === "file_commercial_license") { // FOR FILE ONLY + setFormData({ ...formData, [name]: e.target.files[0] }); + } else if(name === "nationality") { + let getNationality = await axios.get(`${URL_NATIONALITY}/${value}`); + setNationality(getNationality.data.data) + setFormData({ ...formData, [name]: value }); + } else { + setFormData({ ...formData, [name]: value }); + if (name === 'municipio' || name === 'posto_adms' || name === 'suco') await getMasterAfter(name, value); + } + }; + + async function getMasterAfter(name: string, id: any) { + if (name === 'municipio') { + let getMunicipiosPosto = await axios.get(`${BASE_URL_MASTER_DATA}/municipios/postoadms/${id}`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setPostoAdm(getMunicipiosPosto.data.data) + } + if (name === 'posto_adms') { + let getPostoSuco = await axios.get(`${BASE_URL_MASTER_DATA}/postoadms/sucos/${id}`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setSucos(getPostoSuco.data.data) + } + if (name === 'suco') { + let getSucoAldeias = await axios.get(`${BASE_URL_MASTER_DATA}/sucos/aldeias/${id}`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setAldeias(getSucoAldeias.data.data) + } + } + + const handleAddDialog = (show:boolean) => { + if (show) { + setShowAddDialog(show) + } else { + handleClose() + setShowAddDialog(show) + } + } + + async function fetchMasterData() { + try { + let getMunicipios = await axios.get(`${BASE_URL_MASTER_DATA}/municipios/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setMunicipios(getMunicipios.data.data.list) + let getPostoAdms = await axios.get(`${BASE_URL_MASTER_DATA}/postoadms/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setPostoAdm(getPostoAdms.data.data.list) + let getSucos = await axios.get(`${BASE_URL_MASTER_DATA}/sucos/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setSucos(getSucos.data.data.list) + let getAldeias = await axios.get(`${BASE_URL_MASTER_DATA}/aldeias/list`, { + params: { + limit: 50, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC', + } + }); + setAldeias(getAldeias.data.data.list) + } catch (error) { + console.log(error); + } + } + + function buttonOnSubmit(e:any) { + e.preventDefault(); + if (page === 'kyc' && !formData.description) return toast.warning(`Description for approval needed!`) + handleSubmit(formData); + } + + function btnPrevDef(e:any) { + e.preventDefault(); + viewOnly ? setViewOnly(false) : setViewOnly(true) + } + + const onReject = () => { + handleReject(formData); + handleClose(); + } + + return ( + handleAddDialog(open)}> + + + Member - View/Edit + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + + {/*
*/} + {/* 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 ? generateInput(formData, handleChange, 'Approval Description', 'description', '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' ? ( + + ) : ('') + } + +
+
+ {/*
*/} +
+
+
+
+ ); +}; + +export default DetailMember; +{/* (agent) */} + +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 ( + <> +
+
+ + +
+
+ + ) +} + +// ON DEV (DI SELECT MASI HILANG) +function generateList(formData:any, handleChange: any, list:any, name:string, label: string, difId: any, required: boolean) { + return ( + <> +
+
+ + +
+
+ + ) +} + +// ON DEV (UPDATENYA) +function generateImage(formData:any, handleChange:any, label:string, name:string) { + const imagePreview = (file:any) => { + if (file && file.type && file.type.startsWith('image/')) { + const previewURL = URL.createObjectURL(file); + return previewURL + } + return file + }; + + return ( + <> +
+ +
+ { formData[name] ? ( +
+ {name} + {/* {name} */} +
+ ) : (No Data)} + +
+
+ + ) +} From b8be955ba360e39daa288c963f7aef05f8b1fde6 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 15:55:50 +0700 Subject: [PATCH 02/52] add column provider name on table Products --- .../master/products/hooks/ManageProductsContext.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx index 541c710..3ecd0aa 100644 --- a/src/pages/master/products/hooks/ManageProductsContext.tsx +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -95,6 +95,16 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode headerClassName: 'w-[250px]' } }, + { + accessorFn: (row) => row.provider?.name, + id: 'provider_name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, { accessorFn: (row) => row.price_point, id: 'price_point', @@ -160,7 +170,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', filter: JSON.stringify(filter) }); - // console.log(response?.data); + console.log(response?.data); setProducts(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { From c6d9fba3281a41bdcebd9638796f17c1cb4e4b87 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 15:57:15 +0700 Subject: [PATCH 03/52] remove console --- src/pages/master/products/hooks/ManageProductsContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx index 3ecd0aa..8699524 100644 --- a/src/pages/master/products/hooks/ManageProductsContext.tsx +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -170,7 +170,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', filter: JSON.stringify(filter) }); - console.log(response?.data); + // console.log(response?.data); setProducts(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { From 5a850d394eecdc09c0af892662fc4213167603bc Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 16:12:09 +0700 Subject: [PATCH 04/52] update column on table Products --- .../products/hooks/ManageProductsContext.tsx | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx index 8699524..cfe1ed4 100644 --- a/src/pages/master/products/hooks/ManageProductsContext.tsx +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -72,6 +72,19 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode const columns = useMemo[]>( () => [ + { + accessorFn: (row) => row.code, + id: 'code', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + }, + filterFn: (row, id, value) => { + return row.original.products_name.toLowerCase().includes(value.toLowerCase()); + } + }, { accessorFn: (row) => row.name, id: 'name', @@ -89,7 +102,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode accessorFn: (row) => row.description, id: 'description', header: ({ column }) => , - enableSorting: true, + enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' @@ -99,12 +112,22 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode accessorFn: (row) => row.provider?.name, id: 'provider_name', header: ({ column }) => , - enableSorting: true, + enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' } }, + { + accessorFn: (row) => row.type, + id: 'type', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[150px]' + } + }, { accessorFn: (row) => row.price_point, id: 'price_point', @@ -125,6 +148,43 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode headerClassName: 'w-[100px]' } }, + { + accessorFn: (row) => row.cashback_point, + id: 'cashback_point', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.cashback_cash, + id: 'cashback_cash', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.process_on_third_party, + id: 'process_on_third_party', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const isActive = row.original.process_on_third_party === 'Y'; + + return {isActive ? 'Yes' : 'No'}; + }, + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, { id: 'actions', header: ({ column }) => , From 475aaff1166ee049e189c9a8f16e58f82f844c0d Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 16:19:33 +0700 Subject: [PATCH 05/52] fix form update Products - remove validation input 0 --- src/pages/master/products/blocks/EditDialog.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/master/products/blocks/EditDialog.tsx b/src/pages/master/products/blocks/EditDialog.tsx index d16ae0e..f669b9e 100644 --- a/src/pages/master/products/blocks/EditDialog.tsx +++ b/src/pages/master/products/blocks/EditDialog.tsx @@ -139,10 +139,6 @@ const EditDialog = () => { formField.type.trim() === '' || formField.code.trim() === '' || formField.description.trim() === '' || - formField.price_point === 0 || - formField.price_cash === 0 || - formField.cashback_point === 0 || - formField.cashback_cash === 0 || formField.status === '' || formField.updated_by.trim() === '' || formField.updated_at.trim() === '' @@ -152,7 +148,7 @@ const EditDialog = () => { } doUpdateProduct(e); - console.log(formField); + // console.log(formField); setAlert({ show: false, message: '' }); }; From 6ff4b7d58af61025bc29b99dc92baa2bc95b2753 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 16:19:49 +0700 Subject: [PATCH 06/52] add column on table Products --- .../products/hooks/ManageProductsContext.tsx | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx index cfe1ed4..8c07643 100644 --- a/src/pages/master/products/hooks/ManageProductsContext.tsx +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -171,9 +171,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode { accessorFn: (row) => row.process_on_third_party, id: 'process_on_third_party', - header: ({ column }) => ( - - ), + header: ({ column }) => , cell: ({ row }) => { const isActive = row.original.process_on_third_party === 'Y'; @@ -185,6 +183,29 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode headerClassName: 'w-[100px]' } }, + { + accessorFn: (row) => row.status, + id: 'status', + header: ({ column }) => , + cell: ({ row }) => { + const isActive = row.original.status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, { id: 'actions', header: ({ column }) => , @@ -230,7 +251,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', filter: JSON.stringify(filter) }); - // console.log(response?.data); + console.log(response?.data); setProducts(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { From 1674aeed8559b44a08df58d146d52ccf612e0f9c Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 16:33:19 +0700 Subject: [PATCH 07/52] fix update form field Product - set default value number to null - use Numeric Format --- .../master/products/blocks/EditDialog.tsx | 109 ++++++++++++------ 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/src/pages/master/products/blocks/EditDialog.tsx b/src/pages/master/products/blocks/EditDialog.tsx index f669b9e..493489d 100644 --- a/src/pages/master/products/blocks/EditDialog.tsx +++ b/src/pages/master/products/blocks/EditDialog.tsx @@ -23,6 +23,7 @@ import { SelectValue } from '@/components/ui/select'; import { doSaveLogActivity } from '@/actions/GlobalActions'; +import { NumericFormat } from 'react-number-format'; interface ProviderProps { provider_id: string; @@ -41,15 +42,29 @@ const EditDialog = () => { show: false, message: '' }); - const initialState = { + const initialState: { + name: string; + code: string; + type: string; + description: string; + price_point: string | number | null; + price_cash: string | number | null; + cashback_point: string | number | null; + cashback_cash: string | number | null; + status: string; + provider: string; + process_on_third_party: string; + updated_by: string; + updated_at: string; + } = { name: '', code: '', type: '', description: '', - price_point: 0, - price_cash: 0, - cashback_point: 0, - cashback_cash: 0, + price_point: null, + price_cash: null, + cashback_point: null, + cashback_cash: null, status: '', provider: '', process_on_third_party: '', @@ -139,7 +154,13 @@ const EditDialog = () => { formField.type.trim() === '' || formField.code.trim() === '' || formField.description.trim() === '' || - formField.status === '' || + formField.price_point === null || + formField.price_cash === null || + formField.cashback_point === null || + formField.cashback_cash === null || + formField.status.trim() === '' || + formField.provider.trim() === '' || + formField.process_on_third_party.trim() === '' || formField.updated_by.trim() === '' || formField.updated_at.trim() === '' ) { @@ -256,16 +277,19 @@ const EditDialog = () => { - { - const value = parseFloat(e.target.value); - setFormField({ ...formField, price_point: isNaN(value) ? 0 : value }); + value={formField.price_point ?? ''} + thousandSeparator="." + decimalSeparator="," + allowNegative={false} + onValueChange={(values) => { + setFormField((prev) => ({ + ...prev, + price_point: values.floatValue !== undefined ? values.floatValue : '' + })); }} + placeholder="Enter Price Point" />
@@ -275,16 +299,19 @@ const EditDialog = () => { - { - const value = parseFloat(e.target.value); - setFormField({ ...formField, price_cash: isNaN(value) ? 0 : value }); + value={formField.price_cash ?? ''} + thousandSeparator="." + decimalSeparator="," + allowNegative={false} + onValueChange={(values) => { + setFormField((prev) => ({ + ...prev, + price_cash: values.floatValue !== undefined ? values.floatValue : '' + })); }} + placeholder="Enter Price Cash" /> @@ -294,16 +321,19 @@ const EditDialog = () => { - { - const value = parseFloat(e.target.value); - setFormField({ ...formField, cashback_point: isNaN(value) ? 0 : value }); + value={formField.cashback_point ?? ''} + thousandSeparator="." + decimalSeparator="," + allowNegative={false} + onValueChange={(values) => { + setFormField((prev) => ({ + ...prev, + cashback_point: values.floatValue !== undefined ? values.floatValue : '' + })); }} + placeholder="Enter Cashback Point" /> @@ -313,16 +343,19 @@ const EditDialog = () => { - { - const value = parseFloat(e.target.value); - setFormField({ ...formField, cashback_cash: isNaN(value) ? 0 : value }); + value={formField.cashback_cash ?? ''} + thousandSeparator="." + decimalSeparator="," + allowNegative={false} + onValueChange={(values) => { + setFormField((prev) => ({ + ...prev, + cashback_cash: values.floatValue !== undefined ? values.floatValue : '' + })); }} + placeholder="Enter Cashback Cash" /> From 0a15c3a1b4a3309ec8649ff74619deb14f3bfea9 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 17:30:48 +0700 Subject: [PATCH 08/52] fix detail member --- src/pages/members/manage-members/Columns.tsx | 4 +++- src/pages/members/manage-members/blocks/DetailMember.tsx | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/members/manage-members/Columns.tsx b/src/pages/members/manage-members/Columns.tsx index e657553..6c83c0b 100644 --- a/src/pages/members/manage-members/Columns.tsx +++ b/src/pages/members/manage-members/Columns.tsx @@ -178,5 +178,7 @@ export const initialMember = { posto_adms: '', suco: '', aldeia: '', - profession: '' + profession: '', + approval_description_premium: '', + approval_description_agent: '' }; diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 2cb1950..30e39cf 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -249,7 +249,8 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa {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 ? generateInput(formData, handleChange, 'Approval Description', 'description', 'text', false, viewOnly): ''} + {formData.id ? generateInput(formData, handleChange, 'Approval Premium Description', 'approval_description_premium', 'text', false, viewOnly): ''} + {formData.id ? 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) : ""} From def9ab12a6ca9cb814496e3079881b8de8b467c1 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 17:32:11 +0700 Subject: [PATCH 09/52] update sorting on table Products --- src/pages/master/products/hooks/ManageProductsContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx index 8c07643..856fd5c 100644 --- a/src/pages/master/products/hooks/ManageProductsContext.tsx +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -280,7 +280,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode pagination={{ size: 10 }} toolbar={} layout={{ card: true }} - sorting={[{ id: 'Product.id', desc: false }]} + sorting={[{ id: 'Product.created_at', desc: false }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => getProductsLists(pageIndex, pageSize, sorting, columnFilters) From df67b681bb57d2c48ae12f200cb8ed13c087775f Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 11 Apr 2025 17:33:27 +0700 Subject: [PATCH 10/52] update sorting desc on table Products --- src/pages/master/products/hooks/ManageProductsContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx index 856fd5c..236e306 100644 --- a/src/pages/master/products/hooks/ManageProductsContext.tsx +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -280,7 +280,7 @@ const ManageProductsContextProvider = ({ children }: { children: React.ReactNode pagination={{ size: 10 }} toolbar={} layout={{ card: true }} - sorting={[{ id: 'Product.created_at', desc: false }]} + sorting={[{ id: 'Product.created_at', desc: true }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => getProductsLists(pageIndex, pageSize, sorting, columnFilters) From 0de97696fd6cbb9de768a3b06e40a6a56cf1a9bb Mon Sep 17 00:00:00 2001 From: Narul Hidayah Date: Fri, 11 Apr 2025 18:19:27 +0700 Subject: [PATCH 11/52] disbursement list , detail, upload batch --- src/config/api.config.ts | 2 + src/hooks/useCallApi.ts | 20 +- .../HistoryTransaction.tsx | 39 +++ .../blocks/DetailTransaction.tsx | 137 +++++++++ .../blocks/ListToolbar.tsx | 70 +++++ .../blocks/UploadBatchDialog.tsx | 194 ++++++++++++ .../hooks/TransactionContext.tsx | 289 ++++++++++++++++++ .../history-transaction/hooks/index.tsx | 2 + .../hooks/useTransactionContext.tsx | 12 + src/routing/AppRoutingSetup.tsx | 9 + 10 files changed, 773 insertions(+), 1 deletion(-) create mode 100644 src/pages/disbursement/history-transaction/HistoryTransaction.tsx create mode 100644 src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx create mode 100644 src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx create mode 100644 src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx create mode 100644 src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx create mode 100644 src/pages/disbursement/history-transaction/hooks/index.tsx create mode 100644 src/pages/disbursement/history-transaction/hooks/useTransactionContext.tsx diff --git a/src/config/api.config.ts b/src/config/api.config.ts index 2c6cef5..81e8652 100644 --- a/src/config/api.config.ts +++ b/src/config/api.config.ts @@ -6,6 +6,7 @@ interface apiConfigProps { service_wallet: string; transaction: string; nationality: string; + service_disbursement: string; } const API_URL = import.meta.env.VITE_APP_API_URL; @@ -18,6 +19,7 @@ const apiConfig: apiConfigProps = { service_transaction: `${API_URL}/tt`, service_wallet: `${API_URL}/w`, transaction: `${API_URL}/x`, + service_disbursement: `${API_URL}/s`, nationality: `https://tpay.shiblysolution.id/cms/api/mobile/list-country/ ` }; diff --git a/src/hooks/useCallApi.ts b/src/hooks/useCallApi.ts index a359418..c610dd0 100644 --- a/src/hooks/useCallApi.ts +++ b/src/hooks/useCallApi.ts @@ -57,6 +57,24 @@ const useCallApi = () => { } }, []); + const PostDataFile = useCallback(async (url: string, field: any, config = {}) => { + try { + const response = await axios.post(url, field, config); + const result = response.data; + + if (result.status) { + return { status: true, message: result.data }; + } + return { status: false, message: result.message }; + } catch (error: any) { + const { response } = error; + return { + status: false, + message: response?.data?.error ?? response?.data?.message ?? 'Something went wrong' + }; + } + }, []); + const PutData = useCallback(async (url: string, field: any) => { try { const response = await axios.put(url, field); @@ -85,7 +103,7 @@ const useCallApi = () => { } }, []); - return { GetData, PostData, PutData, DeleteData, GetExportData }; + return { GetData, PostData, PutData, DeleteData, GetExportData, PostDataFile }; }; export { useCallApi }; diff --git a/src/pages/disbursement/history-transaction/HistoryTransaction.tsx b/src/pages/disbursement/history-transaction/HistoryTransaction.tsx new file mode 100644 index 0000000..dc6c51f --- /dev/null +++ b/src/pages/disbursement/history-transaction/HistoryTransaction.tsx @@ -0,0 +1,39 @@ +import { Container, DataGridInner } from '@/components'; +import { TransactionProvider } from './hooks/TransactionContext'; +import { Breadcrumbs, Link } from '@mui/material'; +import { Helmet } from 'react-helmet'; +import { UploadBatchDialog } from './blocks/UploadBatchDialog'; + +const HistoryTransactionDisbursement = () => { + return ( + <> + + TPAY | History Disbursement + + + +

DISBURSEMENT

+ + + Dashboard + + + + Disbursement + + + + History Transaction + + +
+ +
+ +
+
+ + ); +}; + +export default HistoryTransactionDisbursement; diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx new file mode 100644 index 0000000..28df134 --- /dev/null +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx @@ -0,0 +1,137 @@ +import { useTransactionContext } from '../hooks/useTransactionContext'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; +import { useEffect, useState } from 'react'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; + +const API_URL = apiConfig.service_disbursement; + +type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y'; + +interface StatusInfo { + label: string; + bg: string; + text: string; +} + +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' }, + 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) => { + const status = statusRaw as StatusCode; + const { label, bg, text } = statusMap[status] ?? { + label: 'Unknown', + bg: 'bg-gray-100', + text: 'text-gray-600', + }; + + return ( + + {label} + + ); + }; + +const DetailTransaction = () => { + const { GetData } = useCallApi(); + const { + showDetailDialog, + setShowDetailDialog, + selectedTransactionId + } = useTransactionContext(); + + const [transactionDetails, setTransactionDetails] = useState(null); + + useEffect(() => { + const fetchTransactionDetails = async () => { + console.log(selectedTransactionId) + 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); + } + } + }; + + if (showDetailDialog && selectedTransactionId) { + fetchTransactionDetails(); + } + }, [showDetailDialog, selectedTransactionId, GetData]); + + const [activeTab, setActiveTab] = useState('detail'); // 'detail', 'log', 'approve' + + return ( + + + + Transaction Details + + + {/* Tab Content */} +
+
+
+ + + + + + + + + + + + + + + {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) => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
UsernameNameTransfer AmountDescriptionInvoice NumberProcess DateResponseStatus
{log.customer.username ?? '-'}{log.customer.fullname ?? '-'}{log.amount ?? '-'}{log.remark ?? '-'}{log.reference ?? '-'}{log.response_date ?? '-'}{log.payment_response ?? '-'}{renderStatusBadge(log.status) ?? '-'}
+ 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 new file mode 100644 index 0000000..4b6c69f --- /dev/null +++ b/src/pages/disbursement/history-transaction/blocks/ListToolbar.tsx @@ -0,0 +1,70 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useTransactionContext } from '../hooks/useTransactionContext'; +import { Button } from '@/components/ui/button'; +import { useCallback, useState, useEffect } from 'react'; +import { toast } from 'sonner'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleUploadBatchDialog } = useTransactionContext(); + + // 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/disbursement/history-transaction/blocks/UploadBatchDialog.tsx b/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx new file mode 100644 index 0000000..237a72e --- /dev/null +++ b/src/pages/disbursement/history-transaction/blocks/UploadBatchDialog.tsx @@ -0,0 +1,194 @@ +import { MouseEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; + +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { useTransactionContext } from '../hooks'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { apiConfig } from '@/config/api.config'; +import { Alert, KeenIcon, useDataGrid } from '@/components'; +import { toast } from 'sonner'; +import { useCallApi } from '@/hooks'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; +import clsx from 'clsx'; + +const API_URL = apiConfig.service_disbursement; + +const UploadBatchDialog = () => { + const parentRef = useRef(null); + const { showUploadBatchDialog, handleUploadBatchDialog } = useTransactionContext(); + const { reload } = useDataGrid(); + const { PostData, PostDataFile, GetData } = useCallApi(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState: { + execution_date: string, + file: File | null; + } = { + execution_date: '', + file: null + } + const [formField, setFormField] = useState(initialState); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + /* actions */ + const doUploadBatch = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const formData = new FormData(); + formData.append('execution_date', formField.execution_date); + if (formField.file) { + formData.append('file', formField.file); + } + + try { + const response = await PostDataFile(`${API_URL}/upload-excel`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + if (response?.status) { + handleUploadBatchDialog(false); + resetForm(); + reload(); + + const createActivity = { + module: 'Disbursement', + description: `Create New Disbursement => ${formField.file?.name}`, + action: 'C' + }; + + doSaveLogActivity(createActivity); + toast.success('Success Create Disbursement'); + } else { + toast.error('Failed to create disbursement'); + setAlert({ show: true, message: response?.message ?? 'Failed to create disbursement.' }); + } + } catch (error) { + toast.error('Error uploading batch'); + setAlert({ show: true, message: 'Something went wrong. Please try again.' }); + } + }, + [formField] + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + console.log('Form data before submit:', formField); + + if ( + formField.execution_date.trim() === '' || + formField.file === null + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + doUploadBatch(e); + // console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showUploadBatchDialog === false) { + resetForm(); + } + }, [showUploadBatchDialog]); + + return ( + handleUploadBatchDialog(open)}> + + + + +
+
+

Upload Batch

+
+
+
{ + handleUploadBatchDialog(false); + resetForm(); + }} + > + +
+
+
+ +
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+
+
+
+ + + setFormField((prev) => ({ ...prev, execution_date: target.value })) + } + /> +
+
+
+
+ + + setFormField((prev) => ({ ...prev, file: target.files?.[0] ?? null })) + } + /> +
+
+
+ +
+
+
+
+
+
+
+ ); +}; + +export { UploadBatchDialog }; diff --git a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx new file mode 100644 index 0000000..19e2382 --- /dev/null +++ b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx @@ -0,0 +1,289 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { ColumnDef } from '@tanstack/react-table'; +import axios from 'axios'; +import React, { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallApi } from '@/hooks'; +import ListToolbar from '../blocks/ListToolbar'; +import { Button } from '@/components/ui/button'; +import { useNavigate } from 'react-router'; +import moment from 'moment'; +import DetailTransaction from '../blocks/DetailTransaction'; + +interface TransactionProps { + id: number; + name: string; +} + +interface ContextProps { + getTransactionLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any, + filter: any + ) => Promise<{ data: TransactionProps[]; totalCount: number } | undefined>; + showDetailDialog: boolean; + setShowDetailDialog: React.Dispatch>; + selectedTransactionId: number | null; + setSelectedTransactionId: React.Dispatch>; + showUploadBatchDialog: boolean; + handleUploadBatchDialog: (show: boolean) => void; +} + +const initialProps: ContextProps = { + getTransactionLists: async () => ({ data: [], totalCount: 0 }), + showDetailDialog: false, + setShowDetailDialog: () => { }, + selectedTransactionId: null, + setSelectedTransactionId: () => { }, + showUploadBatchDialog: false, + handleUploadBatchDialog: (show: boolean) => {}, +}; + +type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y'; + +interface StatusInfo { + label: string; + bg: string; + text: string; +} + +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' }, + 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' }, +}; + + +const ManageTransactionContext = createContext(initialProps); +const API_URL = apiConfig.service_disbursement; + +const TransactionProvider = ({ children }: { children: React.ReactNode }) => { + const [showDetailDialog, setShowDetailDialog] = useState(false); + const [selectedTransactionId, setSelectedTransactionId] = useState(null); + const [showUploadBatchDialog, setShowUploadBatchDialog] = useState(false); + const [transaction, setTransaction] = useState([]); + const { GetData } = useCallApi(); + const navigate = useNavigate(); + + const handleUploadBatchDialog = useCallback((show: boolean) => { + setShowUploadBatchDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + // { + // accessorKey: 'transaction_date', + // header: ({ column }) => , + // enableSorting: false, + // enableHiding: false, + // meta: { + // headerClassName: 'w-[250px]' + // } + // }, + { + accessorKey: 'file_name', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + }, + }, + { + accessorFn: (row) => { + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(row.amount); + }, + id: 'amount', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]', + }, + }, + { + accessorKey: 'total_record', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + }, + }, + { + accessorKey: 'total_success', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + }, + }, + { + accessorKey: 'total_fail', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + }, + }, + { + accessorKey: 'total_pending', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + }, + }, + { + accessorFn: (row) => row.status, + id: 'status', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: ({ row }) => { + const status = row.original.status as StatusCode; + const { label, bg, text } = statusMap[status] ?? { + label: 'Unknown', + bg: 'bg-gray-100', + text: 'text-gray-600', + }; + + return ( + + {label} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center', + }, + }, + { + accessorFn: (row) => row.execution_date, + id: 'execution_date', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + cell: ({ row }) => moment(row.original.execution_date).format('YYYY-MM-DD HH:mm:ss') + }, + { + accessorFn: (row) => row.done_date, + id: 'done_date', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + cell: ({ row }) => row.original.done_date ? moment(row.original.done_date).format('YYYY-MM-DD HH:mm:ss') : '' + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( +
+ +
+ ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + []); + + const getTransactionLists = async (page: number, limit: number, sorting: any, filter: any) => { + try { + let startdate; + let enddate; + let formattedFilter; + + if (filter == undefined || filter.length == 0) { + const today = new Date(); + const nextWeek = new Date(); + nextWeek.setDate(today.getDate() + 7); + + startdate = today.toISOString().split('T')[0]; + enddate = nextWeek.toISOString().split('T')[0]; + } else if (filter != undefined || filter.length != 0) { + startdate = filter[0].value.from; + enddate = filter[0].value.to; + } + + formattedFilter = { + + }; + + const response = await GetData(`${API_URL}/transaction/history`, { + limit, + page: page + 1, + with_deleted: false, + order_field: "id", + order_direction: 'DESC', + filter: JSON.stringify(formattedFilter) + }); + + setTransaction(response?.data.list); + return { data: response?.data.list, totalCount: response?.data.total_count }; + } catch (error) { + console.error('Error fetching transaction', error); + } + }; + + return ( + + + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getTransactionLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { TransactionProvider, ManageTransactionContext }; +export type { TransactionProps }; diff --git a/src/pages/disbursement/history-transaction/hooks/index.tsx b/src/pages/disbursement/history-transaction/hooks/index.tsx new file mode 100644 index 0000000..d25a2f6 --- /dev/null +++ b/src/pages/disbursement/history-transaction/hooks/index.tsx @@ -0,0 +1,2 @@ +export * from './TransactionContext'; +export * from './useTransactionContext'; diff --git a/src/pages/disbursement/history-transaction/hooks/useTransactionContext.tsx b/src/pages/disbursement/history-transaction/hooks/useTransactionContext.tsx new file mode 100644 index 0000000..4af7036 --- /dev/null +++ b/src/pages/disbursement/history-transaction/hooks/useTransactionContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageTransactionContext } from './TransactionContext'; + +const useTransactionContext = () => { + const context = useContext(ManageTransactionContext); + + if (!context) throw new Error('useTransactionContext must be used within AuthProvider'); + + return context; +}; + +export { useTransactionContext }; diff --git a/src/routing/AppRoutingSetup.tsx b/src/routing/AppRoutingSetup.tsx index 98f78b3..b7ce41f 100644 --- a/src/routing/AppRoutingSetup.tsx +++ b/src/routing/AppRoutingSetup.tsx @@ -40,6 +40,10 @@ import WalletHistory from '@/pages/wallet/wallet-history/WalletHistory'; import WalletMaster from '@/pages/master/wallet/WalletMaster'; import CurrencyMaster from '@/pages/master/currency/CurrencyMaster'; +// DISBURSEMENT +import HistoryTransactionDisbursement from '@/pages/disbursement/history-transaction/HistoryTransaction' +// DISBURSEMENT + const AppRoutingSetup = (): ReactElement => { return ( @@ -100,6 +104,11 @@ const AppRoutingSetup = (): ReactElement => { path="/settings/user-management/manage-position" element={} /> + + {/* DISBURSEMENT */} + } /> + {/* DISBURSEMENT */} + } /> From d115894a64ac9fcc4c5c8c2c9748fc57d4e814e7 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 18:34:52 +0700 Subject: [PATCH 12/52] approval --- src/pages/members/manage-members/ManageMembers.tsx | 7 ------- src/pages/members/manage-members/blocks/DetailMember.tsx | 2 ++ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pages/members/manage-members/ManageMembers.tsx b/src/pages/members/manage-members/ManageMembers.tsx index b4ebdc7..2cd8e96 100644 --- a/src/pages/members/manage-members/ManageMembers.tsx +++ b/src/pages/members/manage-members/ManageMembers.tsx @@ -159,13 +159,6 @@ const ManageMembers = () => { onNo={() => setDialogOpen(false)} /> { member.id !== '' ? ( - // Date: Fri, 11 Apr 2025 18:59:23 +0700 Subject: [PATCH 13/52] fix member and kyc --- src/pages/members/kyc/Kyc.tsx | 8 ++++++++ src/pages/members/manage-members/ManageMembers.tsx | 8 ++++++++ src/pages/members/manage-members/blocks/DetailMember.tsx | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx index 452b632..7270898 100644 --- a/src/pages/members/kyc/Kyc.tsx +++ b/src/pages/members/kyc/Kyc.tsx @@ -122,6 +122,14 @@ const Kyc = () => { delete updateData.destinationGroup; delete updateData.statusApproval; delete updateData.description; + delete updateData.municipio_id; + delete updateData.municipio_name; + delete updateData.posto_adms_id; + delete updateData.posto_adms_name; + delete updateData.suco_id; + delete updateData.suco_name; + delete updateData.aldeia_id; + delete updateData.aldeia_name; delete updateData.group_id; delete updateData.group_name; delete updateData.group_description; diff --git a/src/pages/members/manage-members/ManageMembers.tsx b/src/pages/members/manage-members/ManageMembers.tsx index 2cd8e96..048e28a 100644 --- a/src/pages/members/manage-members/ManageMembers.tsx +++ b/src/pages/members/manage-members/ManageMembers.tsx @@ -103,6 +103,14 @@ const ManageMembers = () => { delete updateData.destinationGroup; delete updateData.statusApproval; delete updateData.description; + delete updateData.municipio_id; + delete updateData.municipio_name; + delete updateData.posto_adms_id; + delete updateData.posto_adms_name; + delete updateData.suco_id; + delete updateData.suco_name; + delete updateData.aldeia_id; + delete updateData.aldeia_name; delete updateData.group_id; delete updateData.group_name; delete updateData.group_description; diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 0e2c669..1980510 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -251,8 +251,8 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa {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 ? generateInput(formData, handleChange, 'Approval Premium Description', 'approval_description_premium', 'text', false, viewOnly): ''} - {formData.id ? generateInput(formData, handleChange, 'Approval Agent Description', 'approval_description_agent', '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) : ""} From 31f58a38b32c1195d6cbab04742922e4cead7f29 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 19:01:29 +0700 Subject: [PATCH 14/52] fix member and kyc --- src/pages/members/kyc/Kyc.tsx | 4 ++++ src/pages/members/manage-members/ManageMembers.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx index 7270898..ff48332 100644 --- a/src/pages/members/kyc/Kyc.tsx +++ b/src/pages/members/kyc/Kyc.tsx @@ -112,6 +112,10 @@ const Kyc = () => { updateData.updated_by = userLogin.data ? userLogin.data.id : ''; let today = new Date(); updateData.updated_at = today.toString(); + updateData.municipio = updateData.municipio_id; + updateData.posto_adms = updateData.posto_adms_id; + updateData.suco = updateData.suco_id; + updateData.aldeia = updateData.aldeia_id; delete updateData.id; delete updateData.pin; delete updateData.name; diff --git a/src/pages/members/manage-members/ManageMembers.tsx b/src/pages/members/manage-members/ManageMembers.tsx index 048e28a..6be4b05 100644 --- a/src/pages/members/manage-members/ManageMembers.tsx +++ b/src/pages/members/manage-members/ManageMembers.tsx @@ -93,6 +93,10 @@ const ManageMembers = () => { updateData.updated_by = userLogin.data ? userLogin.data.id : ''; let today = new Date(); updateData.updated_at = today.toString(); + updateData.municipio = updateData.municipio_id; + updateData.posto_adms = updateData.posto_adms_id; + updateData.suco = updateData.suco_id; + updateData.aldeia = updateData.aldeia_id; delete updateData.id; delete updateData.pin; delete updateData.name; From f028885b3d213ec2f4dc1654c4f0fb2c07d6cf7f Mon Sep 17 00:00:00 2001 From: Narul Hidayah Date: Fri, 11 Apr 2025 19:04:35 +0700 Subject: [PATCH 15/52] update wording --- .../disbursement/history-transaction/HistoryTransaction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/disbursement/history-transaction/HistoryTransaction.tsx b/src/pages/disbursement/history-transaction/HistoryTransaction.tsx index dc6c51f..61b9d2d 100644 --- a/src/pages/disbursement/history-transaction/HistoryTransaction.tsx +++ b/src/pages/disbursement/history-transaction/HistoryTransaction.tsx @@ -23,7 +23,7 @@ const HistoryTransactionDisbursement = () => { - History Transaction + History Disbursement
From cdc86a90aacfb4fe8034e2d215e585399df0405a Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Fri, 11 Apr 2025 21:13:43 +0700 Subject: [PATCH 16/52] adding transactiontypeId on transfer type table --- .../transfertype/blocks/AddDialog.tsx | 4 +- .../transfertype/blocks/EditDialog.tsx | 401 ++++++++++-------- .../hooks/ManageTransferTypeContext.tsx | 12 +- 3 files changed, 235 insertions(+), 182 deletions(-) diff --git a/src/pages/transfer/transfertype/blocks/AddDialog.tsx b/src/pages/transfer/transfertype/blocks/AddDialog.tsx index 19760af..bff4222 100644 --- a/src/pages/transfer/transfertype/blocks/AddDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/AddDialog.tsx @@ -218,7 +218,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 +230,7 @@ const AddDialog = () => { if (!showAddDialog) return; fetchWallets(); }, [showAddDialog]); - // console.log(formField) + console.log(formField) return ( handleAddDialog(open)}> diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index 8f52633..db7e391 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -25,23 +25,16 @@ import { useCallApi } from '@/hooks'; import { getAuth } from '@/auth'; import { ManageTransferFeeContextProvider } from '../../transferfee/hooks/ManageTransferFeeContext'; import AddFeeDialog from '../../transferfee/blocks/AddDialog'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList -} from '@/components/ui/command'; +import { Checkbox } from '@/components/ui/checkbox'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; const API_URL = apiConfig.service_transaction; const API_URL_MASTERDATA = apiConfig.service_master_data; const API_URL_CUSTOMER = apiConfig.service_customer; interface WalletProps { - Wallet_id: string; - Wallet_name: string; + id: string; + name: string; } interface CustomerProps { @@ -50,19 +43,11 @@ interface CustomerProps { msisdn: string; } -interface TranssactionTypeProps { +interface GroupProps { + id: string; name: string; description: string; - minimum_amount: number; - maximum_amount: number; - max_transaction_per_day: number; - status_approval: string; status: string; - type: string; - wallet_origin: WalletProps; - wallet_destination: WalletProps; - wallet_fee_destination: WalletProps; - customer_fee_destination: CustomerProps; } const EditDialog = () => { @@ -71,29 +56,43 @@ const EditDialog = () => { useManageTransferTypeContext(); const { reload } = useDataGrid(); const [wallets, setWallets] = useState([]); + const [groups, setGroups] = useState([]); const { GetData, PutData } = useCallApi(); const [isSubmitting, setIsSubmitting] = useState(false); const parsedUser = getAuth()?.user; const [customers, setCustomers] = useState([]); - const [open, setOpen] = useState(false); + const [selectedGroups, setSelectedGroups] = useState([]); const [alert, setAlert] = useState({ show: false, message: '' }); - const initialState = { + const initialState: { + name: string; + description: string; + wallet_origin: string; + wallet_destination: string; + minimum_amount: number; + maximum_amount: number; + max_transaction_per_day: number; + status: string; + type: string; + status_approval: string; + updated_by: string; + updated_at: string; + permission: string[]; + } = { name: '', description: '', wallet_origin: '', wallet_destination: '', - wallet_fee_destination: '', - customer_fee_destination: '', minimum_amount: 0, maximum_amount: 0, max_transaction_per_day: 0, status_approval: '', type: '', status: '', + permission: [], updated_by: '', updated_at: '' }; @@ -102,27 +101,47 @@ const EditDialog = () => { const resetForm = () => { setFormField(initialState); + setSelectedGroups([]); + setAlert({ show: false, message: '' }); + }; + + const handleGroupChange = (groupId: string) => { + setFormField((prevState) => { + 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] + }; + } + }); }; - // Validation function to make certain fields required const validateForm = () => { const requiredFields = [ 'name', 'description', 'wallet_origin', 'wallet_destination', - 'wallet_fee_destination', - 'customer_fee_destination', 'status', 'status_approval', 'type' ]; 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 + (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) { @@ -133,6 +152,15 @@ const EditDialog = () => { return false; } + // Validate that at least one permission is selected + if (formField.permission.length === 0) { + setAlert({ + show: true, + message: 'Please select at least one group permission' + }); + return false; + } + setAlert({ show: false, message: '' }); return true; }; @@ -148,7 +176,12 @@ const EditDialog = () => { updated_at: formattedTime })); } - }, [showEditDialog]); + }, [showEditDialog, parsedUser]); + + const selectedPermissionNames = groups + .filter((g) => formField.permission.includes(g.id)) + .map((g) => g.name) + .join(', '); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -165,6 +198,14 @@ const EditDialog = () => { handleEditDialog(false, null); toast.success('Success Update Transfer Type'); reload(); + + const createActivity = { + module: 'Manage Transfer Type', + description: `Edit Transfer Type => ${selectedTransferType}`, + action: 'U' + }; + + doSaveLogActivity(createActivity); } }) .finally(() => { @@ -197,11 +238,13 @@ const EditDialog = () => { } }, [formField, selectedTransferType, PutData]); + // Fetch customers useEffect(() => { if (!showEditDialog) return; - const getCustomerList = async (sorting: any) => { + + const getCustomerList = async () => { try { - sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; + const sorting = [{ id: 'id', desc: false }]; const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { limit: 100, page: 1, @@ -209,74 +252,113 @@ const EditDialog = () => { order_field: sorting[0].id, order_direction: sorting[0].desc ? 'DESC' : 'ASC' }); - setCustomers(response?.data.list); + + if (response?.status && response?.data) { + setCustomers(response.data.list); + } } catch (error) { console.error('Error fetching customer', error); } }; - getCustomerList([{ id: 'id', desc: false }]); + getCustomerList(); }, [showEditDialog, GetData]); - - const fetchWallets = useCallback(async () => { - const params = { - limit: 100, - page: 1, - with_deleted: false, - order_field: 'name', - order_direction: 'ASC', - filter: JSON.stringify({ - status: 'Y' - }) - }; - const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, params); - if (response?.status && response?.data) { - setWallets(response.data.list); - } else { - setWallets([]); - } - }, [GetData]); + // Fetch groups useEffect(() => { if (!showEditDialog) return; - fetchWallets(); - }, [showEditDialog, fetchWallets]); - - const fetchTransactionType = useCallback(async (id: string) => { - try { - const response = await GetData(`${API_URL}/transactiontype/getdata/${id}`, { id }); - if (response?.status) { - setFormField((prev) => ({ - ...prev, - name: response.data.name, - description: response.data.description, - wallet_origin: response.data.wallet_origin.id, - wallet_destination: response.data.wallet_destination.id, - wallet_fee_destination: response.data.wallet_fee_destination.id, - customer_fee_destination: response.data.customer_fee_destination.id, - minimum_amount: response.data.minimum_amount, - maximum_amount: response.data.maximum_amount, - max_transaction_per_day: response.data.max_transaction_per_day, - status_approval: response.data.status_approval, - type: response.data.type || '', - status: response.data.status - })); + + const getGroupList = async () => { + try { + const response = await GetData(`${API_URL_MASTERDATA}/groups/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC' + }); + + if (response?.status && response?.data) { + setGroups(response.data.list); + } + } catch (error) { + console.error('Error fetching groups', error); } - // console.log(response); - } catch (error) { - console.error('Error fetching transaction type', error); - setAlert({ - show: true, - message: 'Failed to load transaction type data' - }); - } - }, [GetData]); + }; + + getGroupList(); + }, [showEditDialog, GetData]); + + // Fetch wallets + useEffect(() => { + if (!showEditDialog) return; + + const getWalletList = async () => { + try { + const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: 'wallets.name', + order_direction: 'ASC', + }); + + if (response?.status && response?.data) { + setWallets(response.data.list); + } + } catch (error) { + console.error('Error fetching wallets', error); + } + }; + + 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}`, {}); + + if (response?.status) { + setFormField((prev) => ({ + ...prev, + name: response.data.name, + description: response.data.description, + wallet_origin: response.data.wallet_origin?.id || '', + wallet_destination: response.data.wallet_destination?.id || '', + minimum_amount: response.data.minimum_amount, + maximum_amount: response.data.maximum_amount, + max_transaction_per_day: response.data.max_transaction_per_day, + status_approval: response.data.status_approval, + type: response.data.type || '', + status: response.data.status, + permission: response.data.permission || [] + })); + } + } catch (error) { + console.error('Error fetching transaction type', error); + setAlert({ + show: true, + message: 'Failed to load transaction type data' + }); + } + }; + + const timer = setTimeout(() => { + fetchTransactionType(); + }, 150); + + return () => clearTimeout(timer); + }, [showEditDialog, selectedTransferType, GetData]); useEffect(() => { - if (selectedTransferType) { - fetchTransactionType(selectedTransferType); + if (showEditDialog === false) { + resetForm(); } - }, [selectedTransferType, fetchTransactionType]); + }, [showEditDialog]); return ( handleEditDialog(open, null)}> @@ -305,9 +387,11 @@ const EditDialog = () => {
{alert.show && ( - -

{alert.message}

-
+
+ +

{alert.message}

+
+
)}
@@ -439,8 +523,8 @@ const EditDialog = () => { {wallets.map((wallet) => ( - - {wallet.Wallet_name} + + {wallet.name} ))} @@ -467,8 +551,8 @@ const EditDialog = () => { {wallets.map((wallet) => ( - - {wallet.Wallet_name} + + {wallet.name} ))} @@ -476,86 +560,6 @@ const EditDialog = () => {
-
-
- -
- -
-
-
-
-
- -
- - - - - - - - { - e.currentTarget.scrollTop += e.deltaY; - }} - > - No Customer found. - - {customers.map((customer) => ( - { - setFormField({ - ...formField, - customer_fee_destination: customer.id - }); - setOpen(false); - }} - > - {customer.username} - {customer.msisdn} - - ))} - - - - - -
-
-
@@ -637,7 +641,47 @@ const EditDialog = () => {
- + + {/* Group Permission Section - Read Only Display */} +
+
+ +
+ + + {/* Groups Selection Area */} +
+
+ {groups.map((group) => ( +
+ handleGroupChange(group.id)} + /> + +
+ ))} +
+
+
+
+
+
- {/* Transaction Fee Section */} diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index f5fcd14..2fde89a 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -76,6 +76,16 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React const columns = useMemo[]>( () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => ( + + ), + enableSorting: false, + enableHiding: false, + meta: { headerClassName: 'w-[250px]' } + }, { accessorFn: (row) => row.name, id: 'name', @@ -213,7 +223,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React order_direction: orderDirection, filter: JSON.stringify(filter) }); - + console.log(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; }; From e3771626039c6e09a150c06cf89f5e8d69c555f5 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 22:57:25 +0700 Subject: [PATCH 17/52] fix reject --- src/pages/members/kyc/Kyc.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx index ff48332..2b36fac 100644 --- a/src/pages/members/kyc/Kyc.tsx +++ b/src/pages/members/kyc/Kyc.tsx @@ -109,6 +109,7 @@ const Kyc = () => { const updateData: any = member; const customerId = member.id; const description = member.description; + const destinationGroup = updateData.destinationGroup; updateData.updated_by = userLogin.data ? userLogin.data.id : ''; let today = new Date(); updateData.updated_at = today.toString(); @@ -151,15 +152,12 @@ const Kyc = () => { if (updateData[property]) form.append(property, updateData[property]); } if (dialogType === 'reject') { - await axios.post(`${BASE_URL}/customer/reject`, { customerid: customerId }); + if (destinationGroup === "Premium") await axios.post(`${BASE_URL}/customer/reject`, { customerid: customerId, description: updateData.approval_description_premium }); + if (destinationGroup === "Agent") await axios.post(`${BASE_URL}/customer/reject`, { customerid: customerId, description: updateData.approval_description_agent }); } 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) await axios.post(`${BASE_URL}/customer/approve`, { customerid: customerId, description: description}); } await fetchCustomers(); setDialogOpen(false); From 62d5b0dc8081ee4a44facd9494604fa09b5fee65 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 23:03:32 +0700 Subject: [PATCH 18/52] fix reject --- src/pages/members/manage-members/blocks/DetailMember.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 1980510..5ba71b6 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -170,7 +170,7 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa 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 (page === 'kyc' && !formData.description) return toast.warning(`Description for approval needed!`) + // if (page === 'kyc' && !formData.description) return toast.warning(`Description for approval needed!`) handleSubmit(formData); } From e598d579a7f5a0921cd5d6b1e231504a387b7f16 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 23:22:53 +0700 Subject: [PATCH 19/52] fix reject --- src/pages/members/manage-members/blocks/DetailMember.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 5ba71b6..9853208 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -180,6 +180,8 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa } const onReject = () => { + if (formData.destinationGroup === "Premium" && !formData.approval_description_premium) return toast.warning(`Approval Premium Description needed!`) + if (formData.destinationGroup === "Agent" && !formData.approval_description_premium) return toast.warning(`Approval Agent Description needed!`) handleReject(formData); handleClose(); } From 672e551de84f0dfbc99c566d2c3a770e59ae9514 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 11 Apr 2025 23:26:15 +0700 Subject: [PATCH 20/52] fix reject --- src/pages/members/kyc/Kyc.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx index 2b36fac..65c7215 100644 --- a/src/pages/members/kyc/Kyc.tsx +++ b/src/pages/members/kyc/Kyc.tsx @@ -167,6 +167,7 @@ const Kyc = () => { setDialogOpen(false); setIsDialogOpen(false); toast.error(error.message); + } finally { setLoading(false) } }; From 115fb9c00132d81f577b3f3e1a4149a97c5fefeb Mon Sep 17 00:00:00 2001 From: Raja Oktafrianto Date: Sat, 12 Apr 2025 10:54:56 +0700 Subject: [PATCH 21/52] fixing manage user & add field customer --- src/pages/master/reward/blocks/AddDialog.tsx | 8 ++ .../master/reward/blocks/DeleteDialog.tsx | 8 ++ src/pages/master/reward/blocks/EditDialog.tsx | 8 ++ .../user/manage-user/blocks/AddDialog.tsx | 103 +++++++++++++++++- .../user/manage-user/blocks/EditDialog.tsx | 97 ++++++++++++++++- .../manage-user/hooks/ManageUserContext.tsx | 19 +++- 6 files changed, 231 insertions(+), 12 deletions(-) diff --git a/src/pages/master/reward/blocks/AddDialog.tsx b/src/pages/master/reward/blocks/AddDialog.tsx index 6e32603..de9756c 100644 --- a/src/pages/master/reward/blocks/AddDialog.tsx +++ b/src/pages/master/reward/blocks/AddDialog.tsx @@ -23,6 +23,7 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; const API_URL = apiConfig.service_master_data; @@ -75,6 +76,13 @@ const AddDialog = () => { resetForm(); reload(); toast.success('Reward Create successfully!'); + const createActivity = { + module: 'Manage Reward', + description: `Create New Reward => ${formField.name}`, + action: 'C' + }; + + doSaveLogActivity(createActivity); } else { toast.error('Failed to create reward.'); setAlert({ show: true, message: 'Failed to create reward. Please try again.' }); diff --git a/src/pages/master/reward/blocks/DeleteDialog.tsx b/src/pages/master/reward/blocks/DeleteDialog.tsx index 70aaf5e..8014795 100644 --- a/src/pages/master/reward/blocks/DeleteDialog.tsx +++ b/src/pages/master/reward/blocks/DeleteDialog.tsx @@ -13,6 +13,7 @@ import { import { Button } from '@/components/ui/button'; import { DialogDescription } from '@radix-ui/react-dialog'; import { useManageRewardContext } from '../hooks/useManageRewardContext'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; const API_URL = apiConfig.service_master_data; @@ -42,6 +43,13 @@ const DeleteDialog = () => { handleDeleteDialog(false, null); toast.success('Success Delete Reward'); reload(); + const deleteActivity = { + module: 'Manage Reward', + description: `Delete Reward => ${selectedReward}`, + action: 'D' + }; + + doSaveLogActivity(deleteActivity); } else { setAlert({ show: true, message: response?.message }); toast.error('Failed Delete Reward'); diff --git a/src/pages/master/reward/blocks/EditDialog.tsx b/src/pages/master/reward/blocks/EditDialog.tsx index ac71043..817dfb3 100644 --- a/src/pages/master/reward/blocks/EditDialog.tsx +++ b/src/pages/master/reward/blocks/EditDialog.tsx @@ -23,6 +23,7 @@ import { SelectValue } from '@/components/ui/select'; import { useManageRewardContext } from '../hooks/useManageRewardContext'; +import { doSaveLogActivity } from '@/actions/GlobalActions'; const API_URL = apiConfig.service_master_data; @@ -74,6 +75,13 @@ const EditDialog = () => { handleEditDialog(false, null); toast.success('Success Update Reward'); reload(); + const editActivity = { + module: 'Manage Reward', + description: `Edit Reward => ${formField.name}`, + action: 'U' + }; + + doSaveLogActivity(editActivity); } else { toast.error('Error Update Reward'); setAlert({ show: true, message: 'Failed to Update Reward. Please try again.' }); diff --git a/src/pages/settings/user/manage-user/blocks/AddDialog.tsx b/src/pages/settings/user/manage-user/blocks/AddDialog.tsx index 84298fa..333f84c 100644 --- a/src/pages/settings/user/manage-user/blocks/AddDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/AddDialog.tsx @@ -15,6 +15,16 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from '@/components/ui/command'; +import { ChevronDown } from 'lucide-react'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { useUserContext } from '../hooks'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; @@ -25,6 +35,14 @@ import { useCallApi } from '@/hooks'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import clsx from 'clsx'; +export interface CustomerProps { + id: string; + msisdn: string; + email: string; + fullname: string; + username: string; +} + interface RoleListProps { id: string; name: string; @@ -42,7 +60,9 @@ interface CreateUserParams { status: string; } +const API_URL_CUSTOMER = apiConfig.service_customer; const API_URL = apiConfig.service_dashboard; + type PasswordType = 'password' | 'retype_password'; const AddDialog = () => { @@ -62,7 +82,8 @@ const AddDialog = () => { retype_password: '', name: '', id_role: '', - status: '' + status: '', + customerid: '' }; const [formField, setFormField] = useState(initialState); const [showPassword, setShowPassword] = useState({ @@ -71,6 +92,8 @@ const AddDialog = () => { }); const [messagePassword, setMessagePassword] = useState(true); + const [open, setOpen] = useState(false); + const [customers, setCustomers] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [passwordErrors, setPasswordErrors] = useState([]); @@ -155,6 +178,23 @@ const AddDialog = () => { // console.log('ini data user_role:', response?.data); }, []); + const getCustomerList = async (sorting: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + + setCustomers(response?.data.list); + } catch (error) { + console.error('Error fetching customer', error); + } + }; + useEffect(() => { fetchRoles(); }, [fetchRoles]); @@ -162,7 +202,7 @@ const AddDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - console.log('Form data before submit:', formField); + // console.log('Form data before submit:', formField); if ( formField.email.trim() === '' || @@ -171,7 +211,8 @@ const AddDialog = () => { formField.retype_password.trim() === '' || formField.name.trim() === '' || formField.id_role.trim() === '' || - formField.status.trim() === '' + formField.status.trim() === '' || + formField.customerid.trim() === '' ) { setAlert({ show: true, message: 'Please fill name field.' }); return; @@ -196,6 +237,10 @@ const AddDialog = () => { } }, [showAddDialog]); + useEffect(() => { + getCustomerList([{ id: 'id', desc: false }]); + }, []); + const togglePassword = useCallback((event: MouseEvent, key: string) => { event.preventDefault(); setShowPassword((prev) => ({ ...prev, [key]: !prev[key as PasswordType] })); @@ -262,6 +307,58 @@ const AddDialog = () => { +
+
+ + + + + + + + + { + e.currentTarget.scrollTop += e.deltaY; + }} + > + No Customer found. + + {customers.map((customer) => ( + { + setFormField({ + ...formField, + customerid: customer.id + }); + setOpen(false); + }} + > + {customer.username} + + ))} + + + + + +
+
+
diff --git a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx index 0d32ca6..5fc88a1 100644 --- a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx @@ -6,7 +6,15 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select'; - +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from '@/components/ui/command'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Dialog, DialogBody, @@ -15,6 +23,7 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { CustomerProps } from './AddDialog'; import { useUserContext } from '../hooks'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; @@ -30,6 +39,7 @@ interface RoleListProps { status: string; } +const API_URL_CUSTOMER = apiConfig.service_customer; const API_URL = apiConfig.service_dashboard; const initialState = { @@ -37,7 +47,8 @@ const initialState = { username: '', email: '', id_role: '', - status: '' + status: '', + customerid: '' }; const EditDialog = () => { @@ -45,7 +56,9 @@ const EditDialog = () => { const { showEditDialog, selectedUser, handleEditDialog } = useUserContext(); const { reload } = useDataGrid(); const { GetData, PutData } = useCallApi(); + const [open, setOpen] = useState(false); const [roles, setRoles] = useState([]); + const [customers, setCustomers] = useState([]); const [alert, setAlert] = useState({ show: false, message: '' @@ -109,9 +122,27 @@ const EditDialog = () => { } }, []); + const getCustomerList = async (sorting: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + + // console.log('CUSTOMER: ', response?.data); + setCustomers(response?.data.list); + } catch (error) { + console.error('Error fetching customer', error); + } + }; + const doFetchUserData = useCallback(async (id: string) => { const response = await GetData(`${API_URL}/user/detail/${id}`, { id }); - // console.log('User detail response:', response); + console.log('User detail response:', response?.data); if (response?.status) { setFormField((prev) => ({ @@ -120,8 +151,10 @@ const EditDialog = () => { username: response.data.username, email: response.data.email, id_role: response.data.idRole, - status: response.data.status + status: response.data.status, + customerid: response.data.customerid })); + console.log('Customer ID dari user detail:', response.data.customerid); } else { setFormField((prev) => ({ ...prev, @@ -129,7 +162,8 @@ const EditDialog = () => { username: '', email: '', id_role: '0', - status: '' + status: '', + customerid: '' })); } // console.log('Fetched ID Role:', response?.data.id_role); @@ -141,6 +175,10 @@ const EditDialog = () => { } }, [selectedUser]); + useEffect(() => { + getCustomerList([{ id: 'id', desc: false }]); + }, []); + useEffect(() => { if (showEditDialog === false) { resetForm(); @@ -192,6 +230,55 @@ const EditDialog = () => { )}
+
+
+ + + + + + + + + { + e.currentTarget.scrollTop += e.deltaY; + }} + > + No Customer found. + + {customers.map((customer) => ( + { + setFormField({ + ...formField, + customerid: customer.id + }); + setOpen(false); + }} + > + {customer.username} + + ))} + + + + + +
+
+
diff --git a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx index 24cbc41..31c3c48 100644 --- a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx +++ b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx @@ -27,6 +27,7 @@ interface SelectedUser { role: string; new_password: string; check_new_password: string; + customer: string; } const initialProps: ContextProps = { @@ -83,6 +84,16 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) enableSorting: true, enableHiding: false }, + { + accessorFn: (row) => row.customer?.username, + id: 'customer', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[300px]' + } + }, { accessorFn: (row) => row.email, id: 'email', @@ -100,7 +111,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) enableSorting: false, enableHiding: false, meta: { - headerClassName: 'w-[350px]' + headerClassName: 'w-[250px]' } }, { @@ -179,7 +190,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) ); const doGetListData = async (page: number, limit: number, sorting: any, filter: any) => { - sorting = sorting.length == 0 ? [{ id: 'username', desc: false }] : sorting; + sorting = sorting.length == 0 ? [{ id: 'Users.username', desc: false }] : sorting; filter = filter.length == 0 ? {} : { any: filter[0].value.toLowerCase() }; const response = await GetData(`${API_URL}/user/list`, { limit: limit, @@ -189,7 +200,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', filter: JSON.stringify(filter) }); - console.log('response api:', response); + // console.log('response api:', response); return { data: response?.data.list, totalCount: response?.data.total_count }; }; @@ -215,7 +226,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) pagination={{ size: 10 }} toolbar={} layout={{ card: true }} - sorting={[{ id: 'username', desc: false }]} + sorting={[{ id: 'Users.username', desc: false }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => doGetListData(pageIndex, pageSize, sorting, columnFilters) From 2ebf7c9a0aafc286f68b1cf546a3403bb2b644af Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Sat, 12 Apr 2025 13:23:01 +0700 Subject: [PATCH 22/52] Adding permission on update transfertype --- .../transfer/transfertype/TransferType.tsx | 2 +- .../transfertype/blocks/EditDialog.tsx | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/pages/transfer/transfertype/TransferType.tsx b/src/pages/transfer/transfertype/TransferType.tsx index ab79eca..903669e 100644 --- a/src/pages/transfer/transfertype/TransferType.tsx +++ b/src/pages/transfer/transfertype/TransferType.tsx @@ -37,7 +37,7 @@ const TransferType = () => {
- {/* */} + diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index db7e391..82a757a 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -43,7 +43,7 @@ interface CustomerProps { msisdn: string; } -interface GroupProps { +interface PermissionObject { id: string; name: string; description: string; @@ -56,7 +56,7 @@ const EditDialog = () => { useManageTransferTypeContext(); const { reload } = useDataGrid(); const [wallets, setWallets] = useState([]); - const [groups, setGroups] = useState([]); + const [groups, setGroups] = useState([]); const { GetData, PutData } = useCallApi(); const [isSubmitting, setIsSubmitting] = useState(false); const parsedUser = getAuth()?.user; @@ -215,9 +215,13 @@ const EditDialog = () => { const doUpdateTransferType = useCallback(async () => { try { - const response = await PutData(`${API_URL}/transactiontype/update/${selectedTransferType}`, { - ...formField - }); + // Create a clean copy of the form data with properly formatted permissions + const formDataToSend = { + ...formField, + permission: formField.permission.filter(id => typeof id === 'string') + }; + + const response = await PutData(`${API_URL}/transactiontype/update/${selectedTransferType}`, formDataToSend); if (response?.status) { setAlert({ show: false, message: '' }); @@ -299,7 +303,7 @@ const EditDialog = () => { limit: 100, page: 1, with_deleted: false, - order_field: 'wallets.name', + order_field: 'Wallets.name', order_direction: 'ASC', }); @@ -321,8 +325,21 @@ const EditDialog = () => { const fetchTransactionType = async () => { try { 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, @@ -335,7 +352,7 @@ const EditDialog = () => { status_approval: response.data.status_approval, type: response.data.type || '', status: response.data.status, - permission: response.data.permission || [] + permission: permissionIds })); } } catch (error) { @@ -346,14 +363,13 @@ const EditDialog = () => { }); } }; - + const timer = setTimeout(() => { fetchTransactionType(); }, 150); return () => clearTimeout(timer); }, [showEditDialog, selectedTransferType, GetData]); - useEffect(() => { if (showEditDialog === false) { resetForm(); From a44bbcc77a247eff798bdb252e006f101da98de7 Mon Sep 17 00:00:00 2001 From: Raja Oktafrianto Date: Sat, 12 Apr 2025 13:27:16 +0700 Subject: [PATCH 23/52] update user & reward --- .../master/reward/blocks/DeleteDialog.tsx | 13 ++-- src/pages/master/reward/blocks/EditDialog.tsx | 8 ++- .../reward/hooks/ManageRewardContext.tsx | 39 ++++------- .../user/manage-user/blocks/DeleteDialog.tsx | 1 + .../user/manage-user/blocks/EditDialog.tsx | 70 ++++++++++--------- 5 files changed, 63 insertions(+), 68 deletions(-) diff --git a/src/pages/master/reward/blocks/DeleteDialog.tsx b/src/pages/master/reward/blocks/DeleteDialog.tsx index 8014795..4ece16d 100644 --- a/src/pages/master/reward/blocks/DeleteDialog.tsx +++ b/src/pages/master/reward/blocks/DeleteDialog.tsx @@ -34,24 +34,25 @@ const DeleteDialog = () => { return; } // console.log('Ini datanya:', selectedReward); - const response = await DeleteData(`${API_URL}/reward/delete/${selectedReward?.id}/true`, { - id: selectedReward.id + const response = await DeleteData(`${API_URL}/reward/delete/${selectedReward}/true`, { + id: selectedReward }); + // console.log('Response Delete:', response); if (response?.status) { - setAlert({ show: false, message: '' }); + setAlert((prev) => ({ ...prev, show: false, message: '' })); handleDeleteDialog(false, null); toast.success('Success Delete Reward'); reload(); - const deleteActivity = { + const createActivity = { module: 'Manage Reward', description: `Delete Reward => ${selectedReward}`, action: 'D' }; - doSaveLogActivity(deleteActivity); + doSaveLogActivity(createActivity); } else { - setAlert({ show: true, message: response?.message }); + setAlert((prev) => ({ ...prev, show: true, message: response?.message })); toast.error('Failed Delete Reward'); } }, [selectedReward, DeleteData, handleDeleteDialog, reload]); diff --git a/src/pages/master/reward/blocks/EditDialog.tsx b/src/pages/master/reward/blocks/EditDialog.tsx index 817dfb3..d31989d 100644 --- a/src/pages/master/reward/blocks/EditDialog.tsx +++ b/src/pages/master/reward/blocks/EditDialog.tsx @@ -58,7 +58,9 @@ const EditDialog = () => { const RewardType = { 'Daily Check in': 'D', - Referal: 'R' + Referal: 'R', + 'Level Pro': 'P', + 'Level Prioritas': 'L' } as const; type RewardTypeValue = (typeof RewardType)[keyof typeof RewardType]; @@ -68,7 +70,7 @@ const EditDialog = () => { e.preventDefault(); // console.log('Ini datanya:', selectedReward); - const response = await PutData(`${API_URL}/reward/update/${selectedReward?.id}`, formField); + const response = await PutData(`${API_URL}/reward/update/${selectedReward}`, formField); if (response?.status) { resetForm(); @@ -126,7 +128,7 @@ const EditDialog = () => { useEffect(() => { if (selectedReward) { - doFetchData(selectedReward.id.toString()); + doFetchData(selectedReward); } }, [selectedReward]); diff --git a/src/pages/master/reward/hooks/ManageRewardContext.tsx b/src/pages/master/reward/hooks/ManageRewardContext.tsx index 97c0130..32b8116 100644 --- a/src/pages/master/reward/hooks/ManageRewardContext.tsx +++ b/src/pages/master/reward/hooks/ManageRewardContext.tsx @@ -6,7 +6,7 @@ import React, { createContext, useCallback, useMemo, useState } from 'react'; import { Toaster } from 'sonner'; import ListToolbar from '../blocks/ListToolbar'; -interface Reward { +interface SelectedReward { id: number; name: string; type: string; @@ -14,23 +14,14 @@ interface Reward { status: string; } -const initialState = { - id: '', - name: '', - type: '', - amount: '', - status: '' -}; - interface ContextProps { showAddDialog: boolean; handleAddDialog: (show: boolean) => void; showEditDialog: boolean; - handleEditDialog: (show: boolean, selected_reward: Reward | null) => void; + handleEditDialog: (show: boolean, selected_reward: string | null) => void; showDeleteDialog: boolean; - handleDeleteDialog: (show: boolean, selected_rewad: Reward | null) => void; - selectedReward: Reward | null; - reward: string | null; + handleDeleteDialog: (show: boolean, selected_reward: string | null) => void; + selectedReward: string | null; } const initialProps: ContextProps = { @@ -40,31 +31,29 @@ const initialProps: ContextProps = { handleEditDialog: () => {}, showDeleteDialog: false, handleDeleteDialog: () => {}, - selectedReward: null, - reward: null + selectedReward: null }; const ManageRewardContext = createContext(initialProps); const API_URL_MASTER_DATA = apiConfig.service_master_data; const ManageRewardContextProvider = ({ children }: { children: React.ReactNode }) => { - const [reward, setRewards] = useState(null); const [showAddDialog, setShowAddDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); - const [selectedReward, setSelectedReward] = useState(null); + const [selectedReward, setSelectedReward] = useState(null); const { GetData } = useCallApi(); const handleAddDialog = useCallback((show: boolean) => { setShowAddDialog(show); }, []); - const handleEditDialog = useCallback((show: boolean, selected_reward: Reward | null) => { + const handleEditDialog = useCallback((show: boolean, selected_reward: string | null) => { setShowEditDialog(show); setSelectedReward(show ? selected_reward : null); }, []); - const handleDeleteDialog = useCallback((show: boolean, selected_reward: Reward | null) => { + const handleDeleteDialog = useCallback((show: boolean, selected_reward: string | null) => { setShowDeleteDialog(show); setSelectedReward(show ? selected_reward : null); }, []); @@ -135,13 +124,13 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } <> @@ -154,7 +143,7 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } } } ], - [] + [handleEditDialog, handleDeleteDialog] ); const getRewardList = async (page: number, limit: number, sorting: any, filter: any) => { @@ -162,7 +151,7 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; const response = await GetData(`${API_URL_MASTER_DATA}/reward/list`, { - limit, + limit: limit, page: page + 1, with_deleted: false, order_field: sorting[0].id, @@ -171,7 +160,6 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } }); // console.log('API Response:', response?.data.list); // console.log('reward list :', response); - setRewards(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { console.error('Error fethcing reward', error); @@ -181,7 +169,6 @@ const ManageRewardContextProvider = ({ children }: { children: React.ReactNode } return ( { const response = await DeleteData(`${API_URL}/user/delete/${selectedUser}/${enforce}`, { id: selectedUser }); + // console.log('Delete Response User:', response); if (response?.status) { setAlert((prev) => ({ ...prev, show: false, message: '' })); handleDeleteDialog(false, null); diff --git a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx index 5fc88a1..ecf99cd 100644 --- a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx @@ -25,6 +25,7 @@ import { } from '@/components/ui/dialog'; import { CustomerProps } from './AddDialog'; import { useUserContext } from '../hooks'; +import { ChevronDown } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { apiConfig } from '@/config/api.config'; @@ -142,7 +143,7 @@ const EditDialog = () => { const doFetchUserData = useCallback(async (id: string) => { const response = await GetData(`${API_URL}/user/detail/${id}`, { id }); - console.log('User detail response:', response?.data); + // console.log('User detail response:', response?.data); if (response?.status) { setFormField((prev) => ({ @@ -154,7 +155,7 @@ const EditDialog = () => { status: response.data.status, customerid: response.data.customerid })); - console.log('Customer ID dari user detail:', response.data.customerid); + // console.log('Customer ID dari user detail:', response.data.customerid); } else { setFormField((prev) => ({ ...prev, @@ -230,6 +231,34 @@ const EditDialog = () => { )}
+
+
+ + + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, username: target.value })) + } + /> +
+
+
@@ -237,11 +266,14 @@ const EditDialog = () => { @@ -279,34 +311,6 @@ const EditDialog = () => {
-
-
- - - setFormField((prev) => ({ ...prev, name: target.value })) - } - /> -
-
- -
-
- - - setFormField((prev) => ({ ...prev, username: target.value })) - } - /> -
-
-
From a773b7c558f3484721ce7fbc0d8d845093e021d4 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Sun, 13 Apr 2025 11:46:49 +0700 Subject: [PATCH 24/52] fixing Add+ delete+ managetransferfee --- .../transfer/transferfee/blocks/AddDialog.tsx | 293 ++++++- .../transferfee/blocks/DeleteDialog.tsx | 6 +- .../transferfee/blocks/EditDialog.tsx | 807 +++++++++++------- .../hooks/ManageTransferFeeContext.tsx | 109 ++- .../transfertype/blocks/EditDialog.tsx | 4 + 5 files changed, 838 insertions(+), 381 deletions(-) diff --git a/src/pages/transfer/transferfee/blocks/AddDialog.tsx b/src/pages/transfer/transferfee/blocks/AddDialog.tsx index c7c8d64..271f4fe 100644 --- a/src/pages/transfer/transferfee/blocks/AddDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/AddDialog.tsx @@ -50,9 +50,18 @@ interface TransactionTypeProps { id: string; name: string; } - +interface WalletProps { + id: string; + name: string; +} +interface CustomerProps { + id: string; + username: string; + msisdn: string; +} const API_URL = apiConfig.service_transaction; - +const API_URL_MASTER_DATA = apiConfig.service_master_data; +const API_URL_CUSTOMER = apiConfig.service_customer; const AddFeeDialog = () => { const parentRef = useRef(null); const { reload } = useDataGrid(); @@ -64,6 +73,9 @@ const AddFeeDialog = () => { message: '' }); const [transactionTypes, setTransactionTypes] = useState([]); + const [wallets, setWallets] = useState([]); + const [customers, setCustomers] = useState([]); + const [defaultCustomer, setDefaultCustomer] = useState(''); const initialState = { name: '', @@ -75,11 +87,17 @@ const AddFeeDialog = () => { period_end: '', deduct_amount: 0, deduct_percentage: 0, + fee_amount: 0, priority: '', status: '', status_include: '', created_by: '', - created_at: '' + created_at: '', + deduct_from: '', + deduct_from_account: '', + credit_to: '', + credit_destination: '00000000-0000-0000-0000-000000000000', + credit_destination_account: '' }; const [formField, setFormField] = useState(initialState); @@ -104,12 +122,19 @@ const AddFeeDialog = () => { maximum_amount: formField.maximum_amount, deduct_amount: formField.deduct_amount, deduct_percentage: formField.deduct_percentage, + fee_amount: formField.fee_amount, transaction_type: formField.transaction_type, status: formField.status, status_include: formField.status_include, - priority: formField.priority + priority: formField.priority, + deduct_from: formField.deduct_from, + deduct_from_account: formField.deduct_from_account, + credit_to: formField.credit_to, + credit_destination: formField.credit_destination, + credit_destination_account: formField.credit_destination_account }; }; + useEffect(() => { const created_time = new Date(); const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); @@ -138,38 +163,134 @@ const AddFeeDialog = () => { }); setTransactionTypes(response?.data.list || []); } catch (error) { - console.error('Error fetching customer', error); + console.error('Error fetching transaction types', error); } }; getTransactionTypeList([{ id: 'id', desc: false }]); }, [showAddFeeDialog]); + const fetchWallets = useCallback(async () => { + const params = { + limit: 100, + page: 1, + with_deleted: false, + order_field: 'Wallets.name', + order_direction: 'ASC', + }; + try { + const response = await GetData(`${API_URL_MASTER_DATA}/wallet/list`, params); + if (response?.status && response?.data) { + setWallets(response.data.list); + } else { + setWallets([]); + } + } catch (error) { + console.error('Error fetching wallets', error); + setWallets([]); + } + }, [GetData]); + + useEffect(() => { + if (!showAddFeeDialog) return; + fetchWallets(); + }, [showAddFeeDialog, fetchWallets]); + + useEffect(() => { + if (!showAddFeeDialog) return; + const getCustomerList = async (sorting: any) => { + try { + sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; + + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + }); + const customerList = response?.data.list || []; + setCustomers(customerList); + + if (customerList.length > 0) { + setDefaultCustomer(customerList[0].id); + } + } catch (error) { + console.error('Error fetching customers', error); + setCustomers([]); + } + }; + + getCustomerList([{ id: 'id', desc: false }]); + }, [showAddFeeDialog, GetData]); + const doCreateTransferType = useCallback( async (e: React.FormEvent) => { e.preventDefault(); - for (const key in formField) { - if ( - formField[key as keyof typeof formField] === '' ) { - setAlert({ show: true, message: 'All fields must be filled out' }); + setIsSubmitting(true); + + const requiredFields = [ + 'name', + 'description', + 'period_start', + 'period_end', + 'transaction_type', + 'status', + 'status_include', + 'priority', + 'deduct_from', + 'deduct_from_account', + 'credit_to', + 'credit_destination_account' + ]; + + for (const field of requiredFields) { + if (formField[field as keyof typeof formField] === '') { + setAlert({ + show: true, + message: `All required fields must be filled out. Missing: ${field.replace(/_/g, ' ')}` + }); + setIsSubmitting(false); return; } } + + if (formField.credit_to === 'I' && !formField.credit_destination) { + setAlert({ + show: true, + message: 'Credit destination is required when Input Customer is selected' + }); + setIsSubmitting(false); + return; + } setAlert({ show: false, message: '' }); - const response = await PostData(`${API_URL}/transactionfees/create`, { - ...formField - }); - if (response?.status) { - // toast.success('Success Create Transfer Fee'); - reload(); - resetForm(); - handleAddFeeDialog(false); - } else { - setAlert({ show: true, message: response?.message || 'Failed to create transfer fee' }); + + const payload = { ...formField }; + + if (formField.credit_to !== 'I') { + payload.credit_destination = '00000000-0000-0000-0000-000000000000'; + } + + try { + const response = await PostData(`${API_URL}/transactionfees/create`, payload); + + if (response?.status) { + toast.success('Successfully created transfer fee'); + reload(); + resetForm(); + handleAddFeeDialog(false); + } else { + setAlert({ show: true, message: response?.message || 'Failed to create transfer fee' }); + } + } catch (error) { + console.error('Error creating transfer fee', error); + setAlert({ show: true, message: 'An error occurred while creating the transfer fee' }); + } finally { + setIsSubmitting(false); } }, - [formField] + [formField, PostData, reload, handleAddFeeDialog] ); return ( @@ -194,7 +315,13 @@ const AddFeeDialog = () => {
- {alert.show && {alert.message}} + {alert.show && ( +
+ +

{alert.message}

+
+
+ )}
@@ -236,7 +363,7 @@ const AddFeeDialog = () => { minimum_amount: values.floatValue || 0 })); }} - placeholder="Enter Max Transaction Per Day" + placeholder="Enter Minimum Amount" />
@@ -253,7 +380,7 @@ const AddFeeDialog = () => { maximum_amount: values.floatValue || 0 })); }} - placeholder="Enter Max Transaction Per Day" + placeholder="Enter Maximum Amount" />
@@ -294,7 +421,7 @@ const AddFeeDialog = () => { deduct_amount: values.floatValue || 0 })); }} - placeholder="Enter Max Transaction Per Day" + placeholder="Enter Deduct Amount" />
@@ -311,11 +438,119 @@ const AddFeeDialog = () => { deduct_percentage: values.floatValue || 0 })); }} - placeholder="Enter Max Transaction Per Day" + placeholder="Enter Deduct Percentage" />
- + + { + setFormField((prev) => ({ + ...prev, + fee_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Fee Amount" + /> +
+
+ + +
+
+ + +
+
+ + +
+ {formField.credit_to === 'I' && ( +
+ + +
+ )} +
+ + +
+
+ + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+ +
+ + + setFormField((prev) => ({ ...prev, description: target.value })) + } + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + minimum_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Minimum Amount" + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + maximum_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Maximum Amount" + /> +
+ +
+ + + setFormField((prev) => ({ ...prev, period_start: target.value })) + } + /> +
+ +
+ + + setFormField((prev) => ({ ...prev, period_end: target.value })) + } + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + deduct_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Deduct Amount" + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + deduct_percentage: values.floatValue || 0 + })); + }} + placeholder="Enter Deduct Percentage" + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + fee_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Fee Amount" + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {formField.credit_to === 'I' && (
- - - setFormField((prev) => ({ ...prev, name: target.value })) - } - /> -
-
- - - setFormField((prev) => ({ ...prev, description: target.value })) - } - /> -
-
- - { - setFormField((prev) => ({ - ...prev, - minimum_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Minimum Amount" - /> -
-
- - { - setFormField((prev) => ({ - ...prev, - maximum_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Minimum Amount" - /> -
-
- - - setFormField((prev) => ({ ...prev, period_start: target.value })) - } - /> -
-
- - - setFormField((prev) => ({ ...prev, period_end: target.value })) - } - /> -
-
- - { - setFormField((prev) => ({ - ...prev, - deduct_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Minimum Amount" - /> -
-
- - { - setFormField((prev) => ({ - ...prev, - deduct_percentage: values.floatValue || 0 - })); - }} - placeholder="Enter Minimum Amount" - /> -
-
- +
-
- - -
-
- - -
-
- - -
- - {/*
- + )} + +
+ +
+ +
+ + +
+ +
+ + -
*/} - - {/*
- - -
*/} -
- - -
+
+ +
+ + +
+ +
+ + +
+ +
+ +
- -
+
+
); }; -export { EditFeeDialog }; +export { EditFeeDialog }; \ No newline at end of file diff --git a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx index 2510b2d..6f6bee5 100644 --- a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx +++ b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx @@ -7,6 +7,7 @@ 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; @@ -28,6 +29,21 @@ const initialProps: ContextProps = { 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 ManageTransferFeeContext = createContext(initialProps); const API_URL = apiConfig.service_transaction; @@ -35,6 +51,7 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN 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(); @@ -58,18 +75,28 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN sorting: any, filter: any ) => { - sorting = sorting.length == 0 ? [{ id: 'id', desc: false }] : sorting; - filter = filter.length == 0 ? {} : { any: filter[0].value.toLowerCase() }; - const response = await GetData(`${API_URL}/transactionfees/list`, { - limit: limit, - page: page+1, - with_deleted: false, - order_field: sorting[0].id, - order_direction: sorting[0].desc ? 'ASC' : 'DESC', - filter: JSON.stringify(filter) - }); - return { data: response?.data.list, totalCount: response?.data.total_count }; + 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[]>( () => [ { @@ -105,20 +132,12 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN meta: { headerClassName: 'w-[150px]' } }, { - accessorFn: (row) => row.period_start?.split('T')[0], - id: 'period_start', - header: ({ column }) => , + accessorFn: (row) => row.fee_amount, + id: 'fee_amount', + 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]' } + meta: { headerClassName: 'w-[150px]' } }, { accessorFn: (row) => row.deduct_amount, @@ -136,6 +155,23 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN 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', @@ -144,6 +180,30 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN 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', @@ -230,8 +290,7 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN } > {children} - - + diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index 82a757a..d646905 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -25,6 +25,8 @@ import { useCallApi } from '@/hooks'; import { getAuth } from '@/auth'; import { ManageTransferFeeContextProvider } from '../../transferfee/hooks/ManageTransferFeeContext'; import AddFeeDialog from '../../transferfee/blocks/AddDialog'; +import { EditFeeDialog } from '../../transferfee/blocks/EditDialog'; +import DeleteFeeDialog from '../../transferfee/blocks/DeleteDialog'; import { Checkbox } from '@/components/ui/checkbox'; import { doSaveLogActivity } from '@/actions/GlobalActions'; @@ -721,6 +723,8 @@ const EditDialog = () => { + + {/* */} From 0ffce7f22ca02343bc02df5ffa789cc158abbed8 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 19:15:07 +0700 Subject: [PATCH 25/52] validator --- src/pages/members/manage-members/blocks/DetailMember.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 9853208..c6c9247 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -170,6 +170,9 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa 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.updated_by||!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); } From a003a38cd1c207a797e3daea4a1927dcc6c68930 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 19:29:45 +0700 Subject: [PATCH 26/52] validator --- src/pages/members/manage-members/blocks/DetailMember.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index c6c9247..370f957 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -170,7 +170,7 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa 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.updated_by||!formData.gender) { + 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!`) From 65986ae3709560b201423cd9ec0de449606c3b55 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 14 Apr 2025 10:47:19 +0700 Subject: [PATCH 27/52] update id column to sync with db on Manage Wallet --- src/pages/master/wallet/hooks/ManageWalletContext.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/master/wallet/hooks/ManageWalletContext.tsx b/src/pages/master/wallet/hooks/ManageWalletContext.tsx index 28a5a96..823d6eb 100644 --- a/src/pages/master/wallet/hooks/ManageWalletContext.tsx +++ b/src/pages/master/wallet/hooks/ManageWalletContext.tsx @@ -74,7 +74,7 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } () => [ { accessorFn: (row) => row.name, - id: 'name', + id: 'wallets.name', header: ({ column }) => , enableSorting: true, enableHiding: false, @@ -86,7 +86,7 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } accessorFn: (row) => row.description, id: 'description', header: ({ column }) => , - enableSorting: true, + enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' @@ -94,7 +94,7 @@ const ManageWalletContextProvider = ({ children }: { children: React.ReactNode } }, { accessorFn: (row) => row.status, - id: 'status', + id: 'wallets.status', header: ({ column }) => , enableSorting: true, enableHiding: false, From 20ea6677ebc146912b41a448d5a824b09694bff6 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 14 Apr 2025 11:31:01 +0700 Subject: [PATCH 28/52] fix pagination Manage Menu - set params page to 1 --- .../menu/manage-menu/hooks/ManageMenusContext.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx b/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx index 3ff2f54..16f5b25 100644 --- a/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx +++ b/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx @@ -193,9 +193,8 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) if (parent.link === '/') { if (!parents.find((el: any) => el.id === parent.id)) - setParents((el: any) => [...el, { id: parent.id, name: parent.name }]); // GET PARENTS + setParents((el: any) => [...el, { id: parent.id, name: parent.name }]); - // Menambahkan parent ke dalam result meskipun tidak memiliki children result.push({ id: parent.id, module: parent.module, @@ -208,7 +207,6 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) }); } - // Jika parent tidak memiliki children, langsung return hasil yang sudah ada if (!parent.children || parent.children.length === 0) { return result; } @@ -230,7 +228,6 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) }; }); - // Gabungkan parent dengan children yang sudah diflatten return [...result, ...childrenFlattened]; }; @@ -241,7 +238,7 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) const response = await GetData(`${API_URL}/menus/list`, { limit, - page: page + 1, + page: 1, with_deleted: false, order_field: sorting[0].id, order_direction: sorting[0].desc ? 'DESC' : 'ASC', @@ -255,10 +252,11 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) ); const total_count = transformedData.length; - const paginatedData = transformedData.slice(page * limit, (page + 1) * limit); - console.log('data', paginatedData); - // setMenus(transformedData); + // **Pagination di frontend saja (tanpa hit API ulang)** + const paginatedData = transformedData.slice(page * limit, (page + 1) * limit); + const totalPages = Math.ceil(total_count / limit); + return { data: paginatedData, totalCount: total_count }; } catch (error) { console.error('Error fetching Menus', error); From 5a1a51bf5d6935e44e060cf00605425998a7e006 Mon Sep 17 00:00:00 2001 From: Raja Oktafrianto Date: Mon, 14 Apr 2025 11:57:29 +0700 Subject: [PATCH 29/52] fixing update customer manage user & sorting --- .../user/manage-user/blocks/EditDialog.tsx | 15 ++----- .../user/manage-user/blocks/ListToolBar.tsx | 2 +- .../manage-user/hooks/ManageUserContext.tsx | 42 ++++++++++++------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx index ecf99cd..a18ec29 100644 --- a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx @@ -153,9 +153,9 @@ const EditDialog = () => { email: response.data.email, id_role: response.data.idRole, status: response.data.status, - customerid: response.data.customerid + customerid: response.data.customer.id })); - // console.log('Customer ID dari user detail:', response.data.customerid); + // console.log('Customer ID from API:', response?.data.customerid); } else { setFormField((prev) => ({ ...prev, @@ -170,16 +170,6 @@ const EditDialog = () => { // console.log('Fetched ID Role:', response?.data.id_role); }, []); - useEffect(() => { - if (selectedUser) { - doFetchUserData(selectedUser); - } - }, [selectedUser]); - - useEffect(() => { - getCustomerList([{ id: 'id', desc: false }]); - }, []); - useEffect(() => { if (showEditDialog === false) { resetForm(); @@ -188,6 +178,7 @@ const EditDialog = () => { useEffect(() => { const fetchAllData = async () => { + await getCustomerList([{ id: 'id', desc: false }]); await doFetchUserRole([{ id: 'name', desc: false }]); if (selectedUser) { await doFetchUserData(selectedUser); diff --git a/src/pages/settings/user/manage-user/blocks/ListToolBar.tsx b/src/pages/settings/user/manage-user/blocks/ListToolBar.tsx index 70eb5be..2e76deb 100644 --- a/src/pages/settings/user/manage-user/blocks/ListToolBar.tsx +++ b/src/pages/settings/user/manage-user/blocks/ListToolBar.tsx @@ -15,7 +15,7 @@ const ListToolBar = () => { table.getColumn('username')?.setFilterValue(event.target.value) diff --git a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx index 31c3c48..2e2b2dd 100644 --- a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx +++ b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx @@ -78,7 +78,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) const columns = useMemo[]>( () => [ { - accessorFn: (row) => row.username, + accessorKey: 'username', id: 'username', header: ({ column }) => , enableSorting: true, @@ -88,27 +88,27 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) accessorFn: (row) => row.customer?.username, id: 'customer', header: ({ column }) => , - enableSorting: false, + enableSorting: true, enableHiding: false, meta: { headerClassName: 'w-[300px]' } }, { - accessorFn: (row) => row.email, + accessorKey: 'email', id: 'email', header: ({ column }) => , - enableSorting: false, + enableSorting: true, enableHiding: false, meta: { headerClassName: 'w-[350px]' } }, { - accessorFn: (row) => row.name, + accessorKey: 'name', id: 'name', header: ({ column }) => , - enableSorting: false, + enableSorting: true, enableHiding: false, meta: { headerClassName: 'w-[250px]' @@ -118,7 +118,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) accessorFn: (row) => row.role.name, id: 'role_name', header: ({ column }) => , - enableSorting: false, + enableSorting: true, enableHiding: false, cell: (data: any) => { const { role } = data.row.original; @@ -132,7 +132,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) } }, { - accessorFn: (row) => row.status, + accessorKey: 'status', id: 'status', header: ({ column }) => , enableSorting: false, @@ -190,18 +190,31 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) ); const doGetListData = async (page: number, limit: number, sorting: any, filter: any) => { + const mappedFilter = filter.reduce((acc: any, curr: any) => { + let key = curr.id; + + // mapping kolom ke nama kolom lengkap (agar tidak ambigu di backend) + if (key === 'username') key = 'Users.username'; + if (key === 'customer') key = 'Customers.username'; + if (key === 'role_name') key = 'Roles.name'; + if (key === 'email') key = 'Users.email'; + if (key === 'name') key = 'Users.name'; + + acc[key] = curr.value.toLowerCase(); + return acc; + }, {}); + sorting = sorting.length == 0 ? [{ id: 'Users.username', desc: false }] : sorting; - filter = filter.length == 0 ? {} : { any: filter[0].value.toLowerCase() }; + filter = Object.keys(mappedFilter).length === 0 ? {} : mappedFilter; const response = await GetData(`${API_URL}/user/list`, { limit: limit, page: page + 1, with_deleted: false, order_field: sorting[0].id, - order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', + order_direction: sorting[0].desc === false ? 'DESC' : 'ASC', filter: JSON.stringify(filter) }); // console.log('response api:', response); - return { data: response?.data.list, totalCount: response?.data.total_count }; }; @@ -228,9 +241,10 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) layout={{ card: true }} sorting={[{ id: 'Users.username', desc: false }]} serverSide={true} - onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => - doGetListData(pageIndex, pageSize, sorting, columnFilters) - } + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => { + // console.log('FILTER SENT:', columnFilters); + return doGetListData(pageIndex, pageSize, sorting, columnFilters); + }} > {children} From 3bbea4bff75830f3097ad6399357686fbc41d349 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Mon, 14 Apr 2025 12:07:54 +0700 Subject: [PATCH 30/52] fixing transactionfee --- .../transferfee/blocks/EditDialog.tsx | 50 ++++---- .../hooks/ManageTransferFeeContext.tsx | 116 +++++++++++++++--- .../transfertype/blocks/EditDialog.tsx | 11 +- 3 files changed, 125 insertions(+), 52 deletions(-) diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index 895a1dd..13ccfe2 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(); @@ -257,15 +257,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 +277,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 +300,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 +329,7 @@ const EditFeeDialog = () => {
handleEditFeeDialog(false, null)} + onClick={handleCloseDialog} >
@@ -654,13 +655,6 @@ const EditFeeDialog = () => {
- diff --git a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx index 6f6bee5..df14f82 100644 --- a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx +++ b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx @@ -181,7 +181,30 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN meta: { headerClassName: 'w-[250px]' } }, { - accessorFn: (row) => row.credit_destination?.fullname, + 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, @@ -196,37 +219,102 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN 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 }) => , + 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.priority === 'Y' ? 'Yes' : 'No'), - id: 'priority', - header: ({ column }) => , - enableSorting: true, - enableHiding: false, - meta: { headerClassName: 'w-[100px]' } - }, - { - accessorFn: (row) => (row.status === 'Y' ? 'Active' : 'Inactive'), + accessorFn: (row) => row.status, id: 'status', header: ({ column }) => , enableSorting: true, enableHiding: false, - meta: { headerClassName: 'w-[150px]' } + cell: ({ row }) => { + const isActive = row.original.status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } }, { - accessorFn: (row) => (row.status_include === 'Y' ? 'Yes' : 'No'), + accessorFn: (row) => row.status_include, id: 'status_include', header: ({ column }) => , enableSorting: true, enableHiding: false, - meta: { headerClassName: 'w-[150px]' } + cell: ({ row }) => { + const isActive = row.original.status_include === 'Y'; + + return ( + + {isActive ? 'Yes' : 'No'} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } }, { id: 'actions', diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index d646905..0039649 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -701,15 +701,6 @@ const EditDialog = () => {
- @@ -724,7 +715,7 @@ const EditDialog = () => {
- {/* */} + From 1a8849275993cc1cf8b4302dc84167fd46a577d0 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 14 Apr 2025 14:00:33 +0700 Subject: [PATCH 31/52] update column on table Manage Menus - fix filter searching menus - synx with response filter searching --- .../manage-menu/hooks/ManageMenusContext.tsx | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx b/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx index 16f5b25..7ced52f 100644 --- a/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx +++ b/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx @@ -109,7 +109,7 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) meta: { headerClassName: 'w-[200px]' } }, { - accessorFn: (row) => row.parentName, + accessorFn: (row) => row.parentName || row.module, id: 'menu', header: ({ column }) => , enableSorting: false, @@ -234,30 +234,38 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) const getMenusLists = async (page: number, limit: number, sorting: any, filter: any) => { try { sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; - filter = filter.length === 0 ? {} : { any: filter[0].value?.toLowerCase() }; + filter = filter.length === 0 ? {} : { name: { like: `%${filter[0].value?.toLowerCase()}%` } }; - const response = await GetData(`${API_URL}/menus/list`, { + const query: any = { limit, page: 1, with_deleted: false, order_field: sorting[0].id, - order_direction: sorting[0].desc ? 'DESC' : 'ASC', - filter: JSON.stringify(filter) - }); + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + }; - if (!response?.data.list) return { data: [], totalCount: 0 }; + if (filter && Object.keys(filter).length > 0) { + query.filter = JSON.stringify(filter); + query.page = page + 1; + } - const transformedData = response.data.list.flatMap((row: any, parentIdx: number) => - flattenChildren(row, parentIdx) - ); + const response = await GetData(`${API_URL}/menus/list`, query); - const total_count = transformedData.length; + if (query.filter && query.filter.length > 0) { + return { data: response?.data.list, totalCount: response?.data.total_count }; + } else { + const transformedData = response?.data.list.flatMap((row: any, parentIdx: number) => + flattenChildren(row, parentIdx) + ); - // **Pagination di frontend saja (tanpa hit API ulang)** - const paginatedData = transformedData.slice(page * limit, (page + 1) * limit); - const totalPages = Math.ceil(total_count / limit); + const total_count = transformedData.length; - return { data: paginatedData, totalCount: total_count }; + // **Pagination di frontend saja (tanpa hit API ulang)** + const paginatedData = transformedData.slice(page * limit, (page + 1) * limit); + const totalPages = Math.ceil(total_count / limit); + + return { data: paginatedData, totalCount: total_count }; + } } catch (error) { console.error('Error fetching Menus', error); return { data: [], totalCount: 0 }; @@ -287,7 +295,7 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) pagination={{ size: 25 }} toolbar={} layout={{ card: true }} - sorting={[{ id: 'id', desc: false }]} + sorting={[{ id: 'created_at', desc: true }]} serverSide={true} onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => getMenusLists(pageIndex, pageSize, sorting, columnFilters) From e17e99a6de5a33cf26157045f3c6cc816416d1b2 Mon Sep 17 00:00:00 2001 From: Narul Hidayah Date: Mon, 14 Apr 2025 14:52:32 +0700 Subject: [PATCH 32/52] detail transaction disbursement --- .../blocks/DetailTransaction.tsx | 49 ++++--- .../blocks/DetailTransactionLog.tsx | 131 ++++++++++++++++++ .../hooks/TransactionContext.tsx | 24 +++- .../hooks/TransactionDialogContext.tsx | 42 ++++++ 4 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx create mode 100644 src/pages/disbursement/history-transaction/hooks/TransactionDialogContext.tsx 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 = () => { Username - Name - Transfer Amount - Description - Invoice Number - Process Date - Response + Fullname + Amount Status + Process Date + Invoice Number + Actions + {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) => ( {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 ?? '-'} + +
+ +
+ + )) ) : ( 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 ? ( +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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; +}; From 9762402be9e256df6f306396e1b7a1b4d7eeb5e1 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 14 Apr 2025 15:04:29 +0700 Subject: [PATCH 33/52] update ListToolbar on Manage Wallet --- src/pages/master/wallet/blocks/ListToolbar.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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) + } /> {/* From a15aa14eb329b105d196bebdf6ea267e8ee7dbf3 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 14 Apr 2025 15:08:06 +0700 Subject: [PATCH 34/52] fix table and list toolbar on Manage Menu - add button search for not debounce searching - set if condition parent to `id_parent` === null --- .../menu/manage-menu/blocks/ListToolbar.tsx | 22 +++++++++++++++++-- .../manage-menu/hooks/ManageMenusContext.tsx | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) 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} /> + + + {/* */} + {createData?() : ""}
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); } @@ -137,8 +142,27 @@ const ManageMembers = () => { 'Content-Type': 'multipart/form-data' } }); - // if (dialogType === 'create') await axios.post(`${BASE_URL}/customers/create`, member) - toast.success('Success Update 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 Membee. PIN sent to email'); } catch (error: any) { toast.error(error.message); } finally { @@ -170,7 +194,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..59914ac 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,6 @@ export default function CustomerWallet(customerid: any) { }); setCustomerWallet(getCustWallet.data.data.data); } catch (error: any) { - // toast.error(error.message); toast.error(`Wallet Not Found`); } } @@ -30,7 +29,7 @@ export default function CustomerWallet(customerid: any) {

Wallet Member

- {customerWallet.length ? ( + {customerWallet&&customerWallet.length ? (
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 ( <> From 1f962e445197db3f2634f79248f3abe15b2c57c2 Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Mon, 14 Apr 2025 16:47:59 +0700 Subject: [PATCH 36/52] fixing transaction type + transaction fee --- .../transfer/transferfee/blocks/AddDialog.tsx | 262 +++--- .../transferfee/blocks/EditDialog.tsx | 5 + .../hooks/ManageTransferFeeContext.tsx | 755 +++++++++--------- .../transfertype/blocks/AddDialog.tsx | 24 +- .../transfertype/blocks/DeleteDialog.tsx | 7 + .../transfertype/blocks/EditDialog.tsx | 119 ++- .../hooks/ManageTransferTypeContext.tsx | 26 +- 7 files changed, 642 insertions(+), 556 deletions(-) diff --git a/src/pages/transfer/transferfee/blocks/AddDialog.tsx b/src/pages/transfer/transferfee/blocks/AddDialog.tsx index 271f4fe..45df2a9 100644 --- a/src/pages/transfer/transferfee/blocks/AddDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/AddDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useManageTransferFeeContext } from '../hooks/useManageTransferFeeContext'; import { NumericFormat } from 'react-number-format'; import { @@ -40,11 +40,7 @@ import { TableHeader, TableRow } from '@/components/ui/table'; -import { ManageTransferFeeContext } from '../hooks/ManageTransferFeeContext'; -import { ColumnDef } from '@tanstack/react-table'; -import { ManageTransferFeeContextProvider } from '../../transferfee/hooks/ManageTransferFeeContext'; import { apiConfig } from '@/config/api.config'; -import { get } from 'http'; interface TransactionTypeProps { id: string; @@ -62,12 +58,19 @@ interface CustomerProps { const API_URL = apiConfig.service_transaction; const API_URL_MASTER_DATA = apiConfig.service_master_data; const API_URL_CUSTOMER = apiConfig.service_customer; + const AddFeeDialog = () => { const parentRef = useRef(null); const { reload } = useDataGrid(); const { PostData, PutData, GetData } = useCallApi(); - const { showAddFeeDialog, handleAddFeeDialog, handleEditFeeDialog, selectedTransferFee } = - useManageTransferFeeContext(); + const { + showAddFeeDialog, + handleAddFeeDialog, + handleEditFeeDialog, + selectedTransferFee, + transactionTypeId + } = useManageTransferFeeContext(); + const [alert, setAlert] = useState({ show: false, message: '' @@ -75,8 +78,16 @@ const AddFeeDialog = () => { const [transactionTypes, setTransactionTypes] = useState([]); const [wallets, setWallets] = useState([]); const [customers, setCustomers] = useState([]); - const [defaultCustomer, setDefaultCustomer] = useState(''); - + const [defaultCustomer, setDefaultCustomer] = useState(''); + const [transactionTypeName, setTransactionTypeName] = useState(''); + + const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false); + const [isLoadingWallets, setIsLoadingWallets] = useState(false); + const [isLoadingCustomers, setIsLoadingCustomers] = useState(false); + const customersWithNames = customers.map((customer) => ({ + id: customer.id, + name: customer.username, + })); const initialState = { name: '', description: '', @@ -104,54 +115,57 @@ const AddFeeDialog = () => { const resetForm = () => { setFormField(initialState); + setTransactionTypeName(''); }; const [isSubmitting, setIsSubmitting] = useState(false); const [showTransactionFeeDialog, setShowTransactionFeeDialog] = useState(false); const parsedUser = getAuth()?.user; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - // setIsSubmitting(true); - const payload = { - name: formField.name, - description: formField.description, - period_start: formField.period_start, - period_end: formField.period_end, - minimum_amount: formField.minimum_amount, - maximum_amount: formField.maximum_amount, - deduct_amount: formField.deduct_amount, - deduct_percentage: formField.deduct_percentage, - fee_amount: formField.fee_amount, - transaction_type: formField.transaction_type, - status: formField.status, - status_include: formField.status_include, - priority: formField.priority, - deduct_from: formField.deduct_from, - deduct_from_account: formField.deduct_from_account, - credit_to: formField.credit_to, - credit_destination: formField.credit_destination, - credit_destination_account: formField.credit_destination_account - }; - }; + useEffect(() => { + if (showAddFeeDialog && transactionTypeId) { + setIsLoadingTransactionType(true); + + setFormField(prev => ({ + ...prev, + transaction_type: transactionTypeId + })); + + const getTransactionTypeDetails = async () => { + try { + const response = await GetData(`${API_URL}/transactiontype/getdata/${transactionTypeId}`, {}); + if (response?.status && response?.data) { + setTransactionTypeName(response.data.name); + } + } catch (error) { + console.error('Error fetching transaction type details', error); + } finally { + setIsLoadingTransactionType(false); + } + }; + + getTransactionTypeDetails(); + } + }, [showAddFeeDialog, transactionTypeId, GetData]); useEffect(() => { const created_time = new Date(); const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); if (showAddFeeDialog) { - setFormField({ - ...formField, + setFormField(prev => ({ + ...prev, created_by: parsedUser?.username, created_at: formattedTime - }); + })); } - }, [showAddFeeDialog]); + }, [showAddFeeDialog, parsedUser?.username]); useEffect(() => { if (!showAddFeeDialog) return; const getTransactionTypeList = async (sorting: any) => { + setIsLoadingTransactionType(true); try { sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; const response = await GetData(`${API_URL}/transactiontype/list`, { @@ -164,13 +178,16 @@ const AddFeeDialog = () => { setTransactionTypes(response?.data.list || []); } catch (error) { console.error('Error fetching transaction types', error); + } finally { + setIsLoadingTransactionType(false); } }; getTransactionTypeList([{ id: 'id', desc: false }]); - }, [showAddFeeDialog]); + }, [showAddFeeDialog, GetData]); const fetchWallets = useCallback(async () => { + setIsLoadingWallets(true); const params = { limit: 100, page: 1, @@ -188,6 +205,8 @@ const AddFeeDialog = () => { } catch (error) { console.error('Error fetching wallets', error); setWallets([]); + } finally { + setIsLoadingWallets(false); } }, [GetData]); @@ -199,6 +218,7 @@ const AddFeeDialog = () => { useEffect(() => { if (!showAddFeeDialog) return; const getCustomerList = async (sorting: any) => { + setIsLoadingCustomers(true); try { sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; @@ -218,6 +238,8 @@ const AddFeeDialog = () => { } catch (error) { console.error('Error fetching customers', error); setCustomers([]); + } finally { + setIsLoadingCustomers(false); } }; @@ -254,7 +276,7 @@ const AddFeeDialog = () => { return; } } - + if (formField.credit_to === 'I' && !formField.credit_destination) { setAlert({ show: true, @@ -280,6 +302,13 @@ const AddFeeDialog = () => { reload(); resetForm(); handleAddFeeDialog(false); + + const createActivity = { + module: 'Manage Transfer Fee', + description: `Create Transfer Fee => ${formField.name}`, + action: 'C' + }; + doSaveLogActivity(createActivity); } else { setAlert({ show: true, message: response?.message || 'Failed to create transfer fee' }); } @@ -293,6 +322,40 @@ const AddFeeDialog = () => { [formField, PostData, reload, handleAddFeeDialog] ); + const renderSelectWithLoading = ( + value: string, + onChangeHandler: (value: string) => void, + options: {id: string, name: string}[] | null, + placeholder: string, + isLoading: boolean + ) => { + return ( + + ); + }; + return ( handleAddFeeDialog(open)}> @@ -325,6 +388,35 @@ const AddFeeDialog = () => {
+
+ + {transactionTypeId ? ( +
+ + {isLoadingTransactionType && ( +
+
+
+ Loading transaction type... +
+
+ )} +
+ ) : ( + renderSelectWithLoading( + formField.transaction_type, + (transaction_type) => setFormField((prev) => ({ ...prev, transaction_type })), + transactionTypes, + "Select Transaction Type", + isLoadingTransactionType + ) + )} +
{
- + {renderSelectWithLoading( + formField.deduct_from_account, + (value) => setFormField({ ...formField, deduct_from_account: value }), + wallets, + "Select Wallet", + isLoadingWallets + )}
@@ -514,60 +598,24 @@ const AddFeeDialog = () => { {formField.credit_to === 'I' && (
- + {renderSelectWithLoading( + formField.credit_destination, + (value) => setFormField({ ...formField, credit_destination: value }), + customersWithNames, + "Select Customer", + isLoadingCustomers +)}
)}
- -
-
- - + {renderSelectWithLoading( + formField.credit_destination_account, + (value) => setFormField({ ...formField, credit_destination_account: value }), + wallets, + "Select Wallet", + isLoadingWallets + )}
@@ -624,7 +672,11 @@ const AddFeeDialog = () => { > Reset -
diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index 13ccfe2..6123f3f 100644 --- a/src/pages/transfer/transferfee/blocks/EditDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/EditDialog.tsx @@ -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' }); } diff --git a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx index df14f82..15b650f 100644 --- a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx +++ b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx @@ -1,388 +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: { 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'; + 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; + } - 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' - } + 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 0039649..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 */} - +
- - + +
@@ -725,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..84a1287 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 }; }; From 90ea7caaa74fc7ed72619bd2317208d77b779575 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 Apr 2025 17:00:55 +0700 Subject: [PATCH 37/52] member --- src/pages/members/manage-members/ManageMembers.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/members/manage-members/ManageMembers.tsx b/src/pages/members/manage-members/ManageMembers.tsx index a7b6295..9710ce1 100644 --- a/src/pages/members/manage-members/ManageMembers.tsx +++ b/src/pages/members/manage-members/ManageMembers.tsx @@ -137,11 +137,14 @@ 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' } }); + toast.success('Success Edit Member'); + } if (dialogType === 'create') { const createMember:any = member createMember.pin = "admin" @@ -161,8 +164,8 @@ const ManageMembers = () => { 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'); } - toast.success('Success Create Membee. PIN sent to email'); } catch (error: any) { toast.error(error.message); } finally { From 94dfb993c0edf903865375b97229bf4a6bf38ce3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 Apr 2025 17:10:31 +0700 Subject: [PATCH 38/52] member --- src/pages/members/manage-members/blocks/CustomerWallet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/members/manage-members/blocks/CustomerWallet.tsx b/src/pages/members/manage-members/blocks/CustomerWallet.tsx index 59914ac..dcf7590 100644 --- a/src/pages/members/manage-members/blocks/CustomerWallet.tsx +++ b/src/pages/members/manage-members/blocks/CustomerWallet.tsx @@ -29,7 +29,7 @@ export default function CustomerWallet({customerid}: any) {

Wallet Member

- {customerWallet&&customerWallet.length ? ( + {customerWallet ? (
From b2b312ced474f374d8c6f598ac611eec27ff6ac0 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 Apr 2025 17:19:46 +0700 Subject: [PATCH 39/52] add member --- src/pages/members/manage-members/blocks/CustomerWallet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/members/manage-members/blocks/CustomerWallet.tsx b/src/pages/members/manage-members/blocks/CustomerWallet.tsx index dcf7590..172591a 100644 --- a/src/pages/members/manage-members/blocks/CustomerWallet.tsx +++ b/src/pages/members/manage-members/blocks/CustomerWallet.tsx @@ -29,7 +29,7 @@ export default function CustomerWallet({customerid}: any) {

Wallet Member

- {customerWallet ? ( + {customerWallet.length > 0 ? (
From 0f61f3ac356fb4e71df28c53c015ab6c68f3a546 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 Apr 2025 17:36:13 +0700 Subject: [PATCH 40/52] recommit --- src/pages/members/manage-members/blocks/CustomerWallet.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/members/manage-members/blocks/CustomerWallet.tsx b/src/pages/members/manage-members/blocks/CustomerWallet.tsx index 172591a..3522cb7 100644 --- a/src/pages/members/manage-members/blocks/CustomerWallet.tsx +++ b/src/pages/members/manage-members/blocks/CustomerWallet.tsx @@ -21,6 +21,7 @@ export default function CustomerWallet({customerid}: any) { }); setCustomerWallet(getCustWallet.data.data.data); } catch (error: any) { + setCustomerWallet([]) toast.error(`Wallet Not Found`); } } @@ -29,7 +30,7 @@ export default function CustomerWallet({customerid}: any) {

Wallet Member

- {customerWallet.length > 0 ? ( + {customerWallet ? (
From a7223379d83c32016336ec614d916b70c2028685 Mon Sep 17 00:00:00 2001 From: Raja Oktafrianto Date: Mon, 14 Apr 2025 17:38:43 +0700 Subject: [PATCH 41/52] fixing search & sorting manage user --- .../manage-user/hooks/ManageUserContext.tsx | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx index 2e2b2dd..8b33e36 100644 --- a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx +++ b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx @@ -95,7 +95,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) } }, { - accessorKey: 'email', + accessorFn: (row) => row.email, id: 'email', header: ({ column }) => , enableSorting: true, @@ -105,7 +105,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) } }, { - accessorKey: 'name', + accessorFn: (row) => row.name, id: 'name', header: ({ column }) => , enableSorting: true, @@ -132,7 +132,7 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) } }, { - accessorKey: 'status', + accessorFn: (row) => row.status, id: 'status', header: ({ column }) => , enableSorting: false, @@ -190,31 +190,31 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) ); const doGetListData = async (page: number, limit: number, sorting: any, filter: any) => { - const mappedFilter = filter.reduce((acc: any, curr: any) => { - let key = curr.id; + const USER_TABLE_COLUMNS = ['username', 'email', 'name', 'status']; - // mapping kolom ke nama kolom lengkap (agar tidak ambigu di backend) - if (key === 'username') key = 'Users.username'; - if (key === 'customer') key = 'Customers.username'; - if (key === 'role_name') key = 'Roles.name'; - if (key === 'email') key = 'Users.email'; - if (key === 'name') key = 'Users.name'; + // âž• Tambahkan prefix ke field dari tabel Users + const mappedSorting = sorting.map((sort: any) => ({ + ...sort, + id: USER_TABLE_COLUMNS.includes(sort.id) ? `Users.${sort.id}` : sort.id + })); - acc[key] = curr.value.toLowerCase(); - return acc; - }, {}); + const orderField = mappedSorting[0]?.id ?? 'Users.username'; + const orderDirection = mappedSorting[0]?.desc === false ? 'DESC' : 'ASC'; - sorting = sorting.length == 0 ? [{ id: 'Users.username', desc: false }] : sorting; - filter = Object.keys(mappedFilter).length === 0 ? {} : mappedFilter; + filter = + filter.length == 0 + ? {} + : { 'Users.username': { like: `%${filter[0].value?.toLowerCase()}%` } }; const response = await GetData(`${API_URL}/user/list`, { limit: limit, page: page + 1, with_deleted: false, - order_field: sorting[0].id, - order_direction: sorting[0].desc === false ? 'DESC' : 'ASC', + order_field: orderField, + order_direction: orderDirection, filter: JSON.stringify(filter) }); // console.log('response api:', response); + return { data: response?.data.list, totalCount: response?.data.total_count }; }; @@ -241,10 +241,9 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) layout={{ card: true }} sorting={[{ id: 'Users.username', desc: false }]} serverSide={true} - onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => { - // console.log('FILTER SENT:', columnFilters); - return doGetListData(pageIndex, pageSize, sorting, columnFilters); - }} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + doGetListData(pageIndex, pageSize, sorting, columnFilters) + } > {children} From ea31b37f7738f0cd3855e92c1d7aabae99ecb8ff Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Mon, 14 Apr 2025 17:38:48 +0700 Subject: [PATCH 42/52] remove command on managetransfertype --- .../transfer/transfertype/hooks/ManageTransferTypeContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index 84a1287..efa6279 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -266,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) From 59a8f364d767da0549b670ea0268f58b78c94adb Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 Apr 2025 18:37:31 +0700 Subject: [PATCH 43/52] fix group --- src/pages/groups/ManageGroups.tsx | 6 +++++- src/pages/members/manage-members/blocks/DetailMember.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/groups/ManageGroups.tsx b/src/pages/groups/ManageGroups.tsx index 7704ad1..55ae1eb 100644 --- a/src/pages/groups/ManageGroups.tsx +++ b/src/pages/groups/ManageGroups.tsx @@ -89,6 +89,10 @@ const ManageGroups = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (!formData.groupName) return toast.warning(`Group name can not be empty!`) + if (!formData.status) return toast.warning(`Status can not be empty!`) + if (!formData.description) return toast.warning(`Description can not be empty!`) + setIsDialogOpen(false); setDialogOpen(true); }; @@ -198,7 +202,7 @@ const ManageGroups = () => {
- Create New Group + {dialogType==='create'?"Create New Group":"Update Group"} - - - ); - }, - meta: { - headerClassName: 'w-[100px]', - cellClassName: 'text-center' - } - } - ], - [handleEditFeeDialog, handleDeleteFeeDialog] - ); + try { + const response = await GetData( + `${API_URL}/transactionfees/getdatabytransactiontype/${selectedTransferType}`, + {} + ); - return ( -
-
-

Manage Transaction Fee

-
- - - } - sorting={[{ id: 'id', desc: true }]} - serverSide={true} - onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => - doGetTransferFeeListData(pageIndex, pageSize, sorting, columnFilters) - } - > - {children} + // 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 }; + } }; - export { ManageTransferFeeContext, ManageTransferFeeContextProvider }; + 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

+
+ + + } + sorting={[{ id: 'id', desc: true }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + doGetTransferFeeListData(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + +
+ ); +}; + +export { ManageTransferFeeContext, ManageTransferFeeContextProvider }; diff --git a/src/pages/transfer/transfertype/blocks/AddDialog.tsx b/src/pages/transfer/transfertype/blocks/AddDialog.tsx index 6d35adc..6e5f51f 100644 --- a/src/pages/transfer/transfertype/blocks/AddDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/AddDialog.tsx @@ -76,7 +76,6 @@ const AddDialog = () => { wallet_origin: '', wallet_destination: '', - minimum_amount: 0, maximum_amount: 0, max_transaction_per_day: 0, @@ -141,13 +140,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' - }; + const createActivity = { + module: 'Manage Transfer Type', + description: `Create Transfer Type => ${formField.name}`, + action: 'C' + }; - doSaveLogActivity(createActivity); + doSaveLogActivity(createActivity); } }) .finally(() => { @@ -222,7 +221,7 @@ const AddDialog = () => { page: 1, with_deleted: false, order_field: 'wallets.name', - order_direction: 'ASC', + order_direction: 'ASC' }; const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, params); // console.log(response) @@ -265,11 +264,11 @@ const AddDialog = () => {
{alert.show && ( -
- -

{alert.message}

-
-
+
+ +

{alert.message}

+
+
)}
@@ -439,11 +438,10 @@ const AddDialog = () => {
-
@@ -458,7 +456,7 @@ const AddDialog = () => { - Disbursement + Disbursement Other Change Group Emoney Customer to Agent Change Group Emoney Agent to Customer diff --git a/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx b/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx index e4c51ad..ec35bf1 100644 --- a/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/DeleteDialog.tsx @@ -1,4 +1,10 @@ -import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Alert, useDataGrid } from '@/components'; import { useCallback, useState } from 'react'; @@ -12,7 +18,8 @@ import { doSaveLogActivity } from '@/actions/GlobalActions'; const API_URL = apiConfig.service_transaction; const DeleteDialog = () => { - const { showDeleteDialog, handleDeleteDialog, selectedTransferType } = useManageTransferTypeContext(); + const { showDeleteDialog, handleDeleteDialog, selectedTransferType } = + useManageTransferTypeContext(); const { reload } = useDataGrid(); const { DeleteData } = useCallApi(); const [alert, setAlert] = useState({ @@ -26,9 +33,12 @@ const DeleteDialog = () => { return; } - const response = await DeleteData(`${API_URL}/transactiontype/delete/${selectedTransferType}/false`, { - id: selectedTransferType - }); + const response = await DeleteData( + `${API_URL}/transactiontype/delete/${selectedTransferType}/false`, + { + id: selectedTransferType + } + ); if (response?.status) { setAlert({ show: false, message: '' }); @@ -37,7 +47,7 @@ const DeleteDialog = () => { const createActivity = { module: 'Manage Transfer Type', description: `Delete Transfer Type => ${selectedTransferType}`, - action: 'D' + action: 'D' }; doSaveLogActivity(createActivity); // setTimeout(() => toast.success('Success Delete Transaction Type'), 0); @@ -51,7 +61,9 @@ const DeleteDialog = () => { Delete Transfer Type - Are you sure you want to delete this data? + + Are you sure you want to delete this data? +

Are you sure?

You will delete this data! diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index c4b9e8f..1e3ca41 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -589,7 +589,7 @@ const EditDialog = () => { - Disbursement + Disbursement Other Change Group Emoney Customer to Agent Change Group Emoney Agent to Customer diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index efa6279..942ee8f 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -147,21 +147,21 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React meta: { headerClassName: 'w-[250px]' } }, { - accessorFn: (row: {type: string }) => { + 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', + 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', + CE: 'Return Customer Emoney', + AD: 'Return Agent Deposit', AM: 'Return Agent Merchant', AE: 'Return Agent Emoney', - R: 'Reward Point', + R: 'Reward Point' }; - + return mapping[row.type] || 'Unknown'; }, id: 'type', @@ -171,7 +171,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React enableSorting: true, enableHiding: false, meta: { headerClassName: 'w-[250px]' } - }, + }, { accessorFn: (row) => (row.status === 'Y' ? 'Active' : 'Inactive'), id: 'status', @@ -217,20 +217,19 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React ], [handleEditDialog, handleDeleteDialog] ); - + const doGetTransferTypeListData = async ( page: number, limit: number, sorting: any, filter: any ) => { - const orderField = 'created_at'; const orderDirection = sorting.length > 0 ? (sorting[0].desc ? 'DESC' : 'ASC') : 'DESC'; - + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; - + const response = await GetData(`${API_URL}/transactiontype/list`, { limit: limit, page: page + 1, @@ -281,4 +280,4 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React }; export { ManageTransferTypeContext, ManageTransferTypeContextProvider }; -export type { TransferType }; \ No newline at end of file +export type { TransferType }; From 9808b88eec49bdea6b9068adb25140deb968e550 Mon Sep 17 00:00:00 2001 From: Narul Hidayah Date: Mon, 14 Apr 2025 20:43:42 +0700 Subject: [PATCH 45/52] fixing manage account, kyc delete member --- .../account/manage-account/ManageAccount.tsx | 1 + .../blocks/DetailTransaction.tsx | 28 +- .../blocks/DetailTransactionLog.tsx | 4 +- .../hooks/TransactionContext.tsx | 13 +- .../kyc-delete-member/ManageKycDeletion.tsx | 37 +++ .../kyc-delete-member/blocks/ListToolbar.tsx | 61 +++++ .../hooks/ManageKycDeletionContext.tsx | 252 ++++++++++++++++++ .../members/kyc-delete-member/hooks/index.tsx | 2 + .../hooks/useManageKycDeletionContext.tsx | 12 + src/routing/AppRoutingSetup.tsx | 2 + 10 files changed, 385 insertions(+), 27 deletions(-) create mode 100644 src/pages/members/kyc-delete-member/ManageKycDeletion.tsx create mode 100644 src/pages/members/kyc-delete-member/blocks/ListToolbar.tsx create mode 100644 src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx create mode 100644 src/pages/members/kyc-delete-member/hooks/index.tsx create mode 100644 src/pages/members/kyc-delete-member/hooks/useManageKycDeletionContext.tsx diff --git a/src/pages/account/manage-account/ManageAccount.tsx b/src/pages/account/manage-account/ManageAccount.tsx index f116edd..2b6b33b 100644 --- a/src/pages/account/manage-account/ManageAccount.tsx +++ b/src/pages/account/manage-account/ManageAccount.tsx @@ -93,6 +93,7 @@ const ManageAccount = () => { {}} onUpdate={() => console.log('Callback update')} onDelete={() => console.log('Callback delete')} /> diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx index dfa33ec..c3ded68 100644 --- a/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransaction.tsx @@ -33,19 +33,19 @@ const statusMap: Record = { }; 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 status = statusRaw as StatusCode; + const { label, bg, text } = statusMap[status] ?? { + label: 'Unknown', + bg: 'bg-gray-100', + text: 'text-gray-600', + }; + + return ( + + {label} + + ); +}; const DetailTransaction = () => { const { GetData } = useCallApi(); @@ -114,7 +114,7 @@ const DetailTransaction = () => {
- + - + - + diff --git a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx index b1807a1..8c4862b 100644 --- a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx @@ -93,15 +93,6 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const columns = useMemo[]>( () => [ - // { - // accessorKey: 'transaction_date', - // header: ({ column }) => , - // enableSorting: false, - // enableHiding: false, - // meta: { - // headerClassName: 'w-[250px]' - // } - // }, { accessorKey: 'file_name', header: ({ column }) => , @@ -192,7 +183,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { header: ({ column }) => , enableSorting: true, enableHiding: false, - cell: ({ row }) => moment(row.original.execution_date).format('YYYY-MM-DD HH:mm:ss') + cell: ({ row }) => moment(row.original.execution_date).format('DD/MM/YYYY HH:mm:ss') }, { accessorFn: (row) => row.done_date, @@ -200,7 +191,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { header: ({ column }) => , enableSorting: true, enableHiding: false, - cell: ({ row }) => row.original.done_date ? moment(row.original.done_date).format('YYYY-MM-DD HH:mm:ss') : '' + cell: ({ row }) => row.original.done_date ? moment(row.original.done_date).format('DD/MM/YYYY HH:mm:ss') : '' }, { id: 'actions', diff --git a/src/pages/members/kyc-delete-member/ManageKycDeletion.tsx b/src/pages/members/kyc-delete-member/ManageKycDeletion.tsx new file mode 100644 index 0000000..0d5962e --- /dev/null +++ b/src/pages/members/kyc-delete-member/ManageKycDeletion.tsx @@ -0,0 +1,37 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageKycDeletionContextProvider } from './hooks/ManageKycDeletionContext'; +import { Breadcrumbs, Link } from '@mui/material'; +import { Helmet } from 'react-helmet'; + +const ManageKycDeletion = () => { + return ( + <> + + TPAY | List Deletion + + + +

MANAGE KYC DELETION

+ + + Dashboard + + + + Member + + + + History Disbursement + + +
+ +
+
+
+ + ); +}; + +export default ManageKycDeletion; diff --git a/src/pages/members/kyc-delete-member/blocks/ListToolbar.tsx b/src/pages/members/kyc-delete-member/blocks/ListToolbar.tsx new file mode 100644 index 0000000..013a15c --- /dev/null +++ b/src/pages/members/kyc-delete-member/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/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx new file mode 100644 index 0000000..5fec6e7 --- /dev/null +++ b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx @@ -0,0 +1,252 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { Toaster } from '@/components/ui/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 ManageKycDeletionProps { + id: string; + customers_id: string; + group_id: string; + username: string; + fullname: string; + email: string; + status: string; + created_at: Date; +} + +interface ContextProps { + getKycDeletionList: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any, + filter: any + ) => Promise<{ data: ManageKycDeletionProps[]; totalCount: number } | undefined>; + showDetailDialog: boolean; + handleDetailDialog: (show: boolean, selected_user: ManageKycDeletionProps | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + selectedUser: ManageKycDeletionProps | null; +} + +const initialProps: ContextProps = { + getKycDeletionList: async () => ({ data: [], totalCount: 0 }), + showDetailDialog: false, + handleDetailDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + selectedUser: null +}; + +const ManageKycDeletionContext = 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 ManageKycDeletionContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showDetailDialog, setShowDetailDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [manageKyc, setManageKyc] = useState([]); + const { GetData } = useCallApi(); + + const getKycDeletionList = async (page: number, limit: number, sorting: any, filter: any) => { + try { + let startdate; + let enddate; + let formattedFilter; + + if (filter == undefined || filter.length == 0) { + const today = new Date(); + const nextWeek = new Date(); + nextWeek.setDate(today.getDate() + 7); + + startdate = today.toISOString().split('T')[0]; + enddate = nextWeek.toISOString().split('T')[0]; + } else if (filter != undefined || filter.length != 0) { + startdate = filter[0].value.from; + enddate = filter[0].value.to; + } + + formattedFilter = { + + }; + + const response = await GetData(`${API_URL}/customer_deletion/list`, { + limit, + page: page + 1, + with_deleted: false, + order_field: "id", + order_direction: 'DESC', + filter: JSON.stringify(formattedFilter) + }); + + setManageKyc(response?.data.list); + return { data: response?.data.list, totalCount: response?.data.total_count }; + } catch (error) { + console.error('Error fetching transaction', error); + } + }; + + const handleDetailDialog = useCallback((show: boolean, selected_user: ManageKycDeletionProps | null) => { + setSelectedUser(show ? selected_user : null); + setShowDetailDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + }, + }, + { + accessorFn: (row) => row.created_at, + id: 'created_at', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + }, + cell: ({ row }) => moment(row.original.created_at).format('DD/MM/YYYY HH:mm:ss') + }, + { + accessorFn: (row) => row.username, + id: 'username', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[350px]' + } + }, + { + accessorFn: (row) => row.fullname, + id: 'fullname', + header: ({ column }) => , + enableSorting: true, + meta: { + headerClassName: 'w-[350px]' + } + }, + { + accessorFn: (row) => row.registered_email, + id: 'email', + header: ({ column }) => , + enableSorting: true, + meta: { + headerClassName: 'w-[350px]' + } + }, + { + accessorFn: (row) => row.status_approve, + id: 'status_approve', + header: ({ column }) => , + enableSorting: true, + meta: { + headerClassName: 'w-[350px]' + }, + cell: ({row}) => renderStatusBadge(row.original.status_approve) + }, + { + id: 'actions', + enableSorting: false, + header: ({ column }) => , + cell: (data: any) => { + const row = data.row.original; + + return ( + <> + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleDetailDialog] + ); + + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'name', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getKycDeletionList(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManageKycDeletionContextProvider, ManageKycDeletionContext }; +export type { ManageKycDeletionProps }; diff --git a/src/pages/members/kyc-delete-member/hooks/index.tsx b/src/pages/members/kyc-delete-member/hooks/index.tsx new file mode 100644 index 0000000..0d10e8a --- /dev/null +++ b/src/pages/members/kyc-delete-member/hooks/index.tsx @@ -0,0 +1,2 @@ +export * from './ManageKycDeletionContext'; +export * from './useManageKycDeletionContext'; diff --git a/src/pages/members/kyc-delete-member/hooks/useManageKycDeletionContext.tsx b/src/pages/members/kyc-delete-member/hooks/useManageKycDeletionContext.tsx new file mode 100644 index 0000000..f3a20c9 --- /dev/null +++ b/src/pages/members/kyc-delete-member/hooks/useManageKycDeletionContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageKycDeletionContext } from './ManageKycDeletionContext'; + +const useManageKycDeletionContext = () => { + const context = useContext(ManageKycDeletionContext); + + if (!context) throw new Error('useManageKycDeletionContext must be used within AuthProvider'); + + return context; +}; + +export { useManageKycDeletionContext }; diff --git a/src/routing/AppRoutingSetup.tsx b/src/routing/AppRoutingSetup.tsx index b7ce41f..865effb 100644 --- a/src/routing/AppRoutingSetup.tsx +++ b/src/routing/AppRoutingSetup.tsx @@ -15,6 +15,7 @@ import ManagePositionPage from '@/pages/settings/user/manage-position/ManagePosi import ManageAccount from '@/pages/account/manage-account/ManageAccount'; import ManageGroups from '@/pages/groups/ManageGroups'; import ManageMembers from '@/pages/members/manage-members/ManageMembers'; +import ManageKycDeletion from '@/pages/members/kyc-delete-member/ManageKycDeletion'; import Kyc from '@/pages/members/kyc/Kyc'; import AccessType from '@/pages/access/access-type/AccessType'; import MemberCredential from '@/pages/access/member-credentials/MemberCredentials'; @@ -79,6 +80,7 @@ const AppRoutingSetup = (): ReactElement => { } /> } /> + } /> } /> } /> From 4aae4a3b412f94006807c97bafd27044fc0901e1 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 Apr 2025 20:44:36 +0700 Subject: [PATCH 46/52] fix err --- src/pages/account/manage-account/ManageAccount.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/account/manage-account/ManageAccount.tsx b/src/pages/account/manage-account/ManageAccount.tsx index f116edd..402befa 100644 --- a/src/pages/account/manage-account/ManageAccount.tsx +++ b/src/pages/account/manage-account/ManageAccount.tsx @@ -93,6 +93,7 @@ const ManageAccount = () => { console.log('Callback update')} onDelete={() => console.log('Callback delete')} /> From 49b19629bdaaa8f2c8a99053be59bda8670de441 Mon Sep 17 00:00:00 2001 From: Narul Hidayah Date: Mon, 14 Apr 2025 22:27:20 +0700 Subject: [PATCH 47/52] detail deletion --- .../kyc-delete-member/blocks/DetailDialog.tsx | 98 +++++++++++++++++++ .../hooks/ManageKycDeletionContext.tsx | 31 ++++-- 2 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 src/pages/members/kyc-delete-member/blocks/DetailDialog.tsx diff --git a/src/pages/members/kyc-delete-member/blocks/DetailDialog.tsx b/src/pages/members/kyc-delete-member/blocks/DetailDialog.tsx new file mode 100644 index 0000000..7875949 --- /dev/null +++ b/src/pages/members/kyc-delete-member/blocks/DetailDialog.tsx @@ -0,0 +1,98 @@ +import { + Dialog, + DialogBody, + DialogContent, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { useManageKycDeletionContext } from '../hooks'; +import { apiConfig } from '@/config/api.config'; +import axios from 'axios'; + +const API_URL = apiConfig.service_customer; +const DetailDialog = () => { + const { showDetailDialog, setShowDetailDialog, detailKyc } = useManageKycDeletionContext(); + + return ( + + + + Customer Deletion Details + + + {/* Tab Content */} + {detailKyc && detailKyc != null ? ( +
+ {generateInput(detailKyc, null, 'Group', 'group_name', 'text', false, true)} + {generateInput(detailKyc, null, 'Username', 'username', 'text', false, true)} + {generateInput(detailKyc, null, 'Full Name', 'fullname', 'text', false, true)} + {generateInput(detailKyc, null, 'Gender', 'customers_gender', 'text', false, true)} + {generateInput(detailKyc, null, 'Date of Birth', 'birthdate', 'date', false, true)} + {generateInput(detailKyc, null, 'Mother Name', 'registered_mother_fullname', 'text', false, true)} + {generateInput(detailKyc, null, 'MSISDN', 'registered_msisdn', 'text', false, true)} + {generateInput(detailKyc, null, 'Reason Deletion', 'reason_deletion', 'text', false, true)} + {generateInput(detailKyc, null, 'Reason Note', 'reason_note', 'text', false, true)} + {generateInput(detailKyc, null, 'Status Approval', 'status_approve', 'text', false, true)} + +
+ + + +
+
+ ) : (
)} +
+
+
+ ); +}; + +export default DetailDialog; + +function generateInput(formData:any, handleChange:any, label:string, name:string, type: string, required: boolean, disabled: boolean) { + function generateDate(isoString: string) { + return isoString.slice(0, 10); // "2000-01-18" + } + + type Code = 'W' | 'Y' | 'N' | 'T' | 'P' | 'D' | 'L'; + interface Reason { + label: string; + } + + const statusMap: Record = { + W: {label: 'Waiting Approval'}, + Y: {label: 'Approve'}, + N: {label: 'Reject'}, + T: {label: 'Tidak lagi menggunakan layanan'}, + P: {label: 'Privasi dan keamanan'}, + D: {label: 'Akun ganda'}, + L: {label: 'Lainnya'} + }; + if (name == 'reason_deletion' || name == 'status_approve') { + const status = formData[name] as Code + const fixStatus = statusMap[status] ?? {label: formData[name]} + formData[name] = fixStatus.label + } + return ( + <> +
+
+ + +
+
+ + ) +} \ No newline at end of file diff --git a/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx index 5fec6e7..bf5e46f 100644 --- a/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx +++ b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx @@ -6,6 +6,7 @@ import { createContext, useCallback, useMemo, useState } from 'react'; import ListToolbar from '../blocks/ListToolbar'; import { useCallApi } from '@/hooks'; import moment from 'moment'; +import DetailDialog from '../blocks/DetailDialog'; interface ManageKycDeletionProps { id: string; @@ -28,10 +29,13 @@ interface ContextProps { filter: any ) => Promise<{ data: ManageKycDeletionProps[]; totalCount: number } | undefined>; showDetailDialog: boolean; - handleDetailDialog: (show: boolean, selected_user: ManageKycDeletionProps | null) => void; + setShowDetailDialog: React.Dispatch>; + handleDetailDialog: (show: boolean, selected_user: string | null) => void; showAddDialog: boolean; handleAddDialog: (show: boolean) => void; - selectedUser: ManageKycDeletionProps | null; + selectedIdCustomer: string | null; + detailKyc: any | null; + setDetailKyc: React.Dispatch>; } const initialProps: ContextProps = { @@ -40,7 +44,10 @@ const initialProps: ContextProps = { handleDetailDialog: () => {}, showAddDialog: false, handleAddDialog: () => {}, - selectedUser: null + selectedIdCustomer: null, + setShowDetailDialog: () => { }, + detailKyc: async () => {}, + setDetailKyc: () => { }, }; const ManageKycDeletionContext = createContext(initialProps); @@ -79,7 +86,8 @@ export const renderStatusBadge = (statusRaw: string | null | undefined) => { const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactNode }) => { const [showDetailDialog, setShowDetailDialog] = useState(false); const [showAddDialog, setShowAddDialog] = useState(false); - const [selectedUser, setSelectedUser] = useState(null); + const [selectedIdCustomer, setSelectedIdCustomer] = useState(null); + const [detailKyc, setDetailKyc] = useState(); const [manageKyc, setManageKyc] = useState([]); const { GetData } = useCallApi(); @@ -121,8 +129,13 @@ const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactN } }; - const handleDetailDialog = useCallback((show: boolean, selected_user: ManageKycDeletionProps | null) => { - setSelectedUser(show ? selected_user : null); + const handleDetailDialog = useCallback(async (show: boolean, selected_id_customer: string | null) => { + setSelectedIdCustomer(show ? selected_id_customer : null); + let detailCustomer = await GetData(`${API_URL}/customer_deletion/detail/${selected_id_customer}`, {}) + setDetailKyc(detailCustomer?.data) + console.log('detailCustomer: ',detailCustomer) + console.log('detailCustomer2: ',detailCustomer?.data) + console.log('detailKyc: ', detailKyc) setShowDetailDialog(show); }, []); @@ -226,10 +239,14 @@ const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactN handleDetailDialog, showAddDialog, handleAddDialog, - selectedUser + selectedIdCustomer, + setShowDetailDialog, + setDetailKyc, + detailKyc }} > + Date: Tue, 15 Apr 2025 00:31:55 +0700 Subject: [PATCH 48/52] fixing transaction_type field on update transactionfee --- .../transferfee/blocks/EditDialog.tsx | 808 ++++++++++-------- 1 file changed, 451 insertions(+), 357 deletions(-) diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index 9fd8e8f..c42823f 100644 --- a/src/pages/transfer/transferfee/blocks/EditDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/EditDialog.tsx @@ -7,15 +7,6 @@ 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, @@ -33,6 +24,8 @@ import { toast } from 'sonner'; import { useCallApi } from '@/hooks'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import { getAuth } from '@/auth'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; const API_URL = apiConfig.service_transaction; const API_URL_MASTER_DATA = apiConfig.service_master_data; @@ -41,14 +34,12 @@ const API_URL_CUSTOMER = apiConfig.service_customer; interface WalletProps { id: string; name: string; - description?: string; } interface CustomerProps { id: string; username: string; msisdn: string; - fullname?: string; } interface TransactionTypeProps { @@ -61,18 +52,32 @@ const EditFeeDialog = () => { const { showEditFeeDialog, handleEditFeeDialog, selectedTransferFee } = useManageTransferFeeContext(); const { reload } = useDataGrid(); - const [wallets, setWallets] = useState([]); const { GetData, PutData } = useCallApi(); - const [isSubmitting, setIsSubmitting] = useState(false); - const [transactionTypes, setTransactionTypes] = useState([]); const parsedUser = getAuth()?.user; + + const [wallets, setWallets] = useState([]); const [customers, setCustomers] = useState([]); + const [transactionTypes, setTransactionTypes] = useState([]); + const [transactionTypeName, setTransactionTypeName] = useState(''); + const customersWithNames = customers.map((customer) => ({ + id: customer.id, + name: customer.username + })); + const [customerSearchTerm, setCustomerSearchTerm] = useState(''); + const [showCustomerSearch, setShowCustomerSearch] = useState(false); const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const [alert, setAlert] = useState({ show: false, message: '' }); + // Loading states + const [isLoadingTransferFee, setIsLoadingTransferFee] = useState(false); + const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false); + const [isLoadingWallets, setIsLoadingWallets] = useState(false); + const [isLoadingCustomers, setIsLoadingCustomers] = useState(false); + const initialState = { name: '', description: '', @@ -109,7 +114,7 @@ const EditFeeDialog = () => { updated_at: formattedTime })); } - }, [showEditFeeDialog]); + }, [showEditFeeDialog, parsedUser?.username]); const resetForm = () => { if (selectedTransferFee) { @@ -178,10 +183,11 @@ const EditFeeDialog = () => { reload(); handleEditFeeDialog(false, null); const createActivity = { - module: 'Manage Transfer Type', - description: `Edit Transfer Type => ${selectedTransferFee}`, + module: 'Manage Transfer Fee', + description: `Edit Transfer Fee => ${formField.name}`, action: 'U' }; + doSaveLogActivity(createActivity); } else { setAlert({ show: true, message: response?.message || 'Failed to update transfer fee' }); } @@ -196,6 +202,7 @@ const EditFeeDialog = () => { ); const fetchWallets = useCallback(async () => { + setIsLoadingWallets(true); const params = { limit: 100, page: 1, @@ -213,6 +220,8 @@ const EditFeeDialog = () => { } catch (error) { console.error('Error fetching wallets', error); setWallets([]); + } finally { + setIsLoadingWallets(false); } }, [GetData]); @@ -225,6 +234,7 @@ const EditFeeDialog = () => { if (!showEditFeeDialog) return; const getCustomerList = async (sorting: any) => { + setIsLoadingCustomers(true); try { sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { @@ -237,16 +247,19 @@ const EditFeeDialog = () => { setCustomers(response?.data.list || []); } catch (error) { console.error('Error fetching customers', error); + } finally { + setIsLoadingCustomers(false); } }; - getCustomerList([{ id: 'msisdn', desc: false }]); + getCustomerList([{ id: 'id', desc: false }]); }, [showEditFeeDialog, GetData]); useEffect(() => { if (!showEditFeeDialog) return; const getTransactionTypeList = async (sorting: any) => { + setIsLoadingTransactionType(true); try { sorting = sorting.length === 0 ? [{ id: 'name', desc: false }] : sorting; const response = await GetData(`${API_URL}/transactiontype/list`, { @@ -259,6 +272,8 @@ const EditFeeDialog = () => { setTransactionTypes(response?.data.list || []); } catch (error) { console.error('Error fetching transaction types', error); + } finally { + setIsLoadingTransactionType(false); } }; @@ -272,6 +287,7 @@ const EditFeeDialog = () => { const fetchTransactionFee = useCallback( async (id: string) => { + setIsLoadingTransferFee(true); try { const response = await GetData(`${API_URL}/transactionfees/getdata/${id}`, {}); @@ -300,10 +316,16 @@ const EditFeeDialog = () => { updated_by: parsedUser?.username, updated_at: new Date().toISOString().slice(0, 19).replace('T', ' ') }); + + if (response.data.transaction_type?.name) { + setTransactionTypeName(response.data.transaction_type.name); + } } } catch (error) { console.error('Error fetching transaction fee details', error); setAlert({ show: true, message: 'Failed to fetch transaction fee details' }); + } finally { + setIsLoadingTransferFee(false); } }, [GetData, parsedUser?.username] @@ -328,6 +350,37 @@ const EditFeeDialog = () => { handleEditFeeDialog(false, null); }; + const renderSelectWithLoading = ( + value: string, + onChangeHandler: (value: string) => void, + options: { id: string; name: string }[] | null, + placeholder: string, + isLoading: boolean + ) => { + return ( + + ); + }; + return ( { )} - - -
-
-
- - - setFormField((prev) => ({ ...prev, name: target.value })) - } - /> + + {isLoadingTransferFee ? ( +
+
+
+
+
+
+
+
- -
- - - setFormField((prev) => ({ ...prev, description: target.value })) - } - /> -
- -
- - { - setFormField((prev) => ({ - ...prev, - minimum_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Minimum Amount" - /> -
- -
- - { - setFormField((prev) => ({ - ...prev, - maximum_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Maximum Amount" - /> -
- -
- - - setFormField((prev) => ({ ...prev, period_start: target.value })) - } - /> -
- -
- - - setFormField((prev) => ({ ...prev, period_end: target.value })) - } - /> -
- -
- - { - setFormField((prev) => ({ - ...prev, - deduct_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Deduct Amount" - /> -
- -
- - { - setFormField((prev) => ({ - ...prev, - deduct_percentage: values.floatValue || 0 - })); - }} - placeholder="Enter Deduct Percentage" - /> -
- -
- - { - setFormField((prev) => ({ - ...prev, - fee_amount: values.floatValue || 0 - })); - }} - placeholder="Enter Fee Amount" - /> -
- -
- - -
- -
- - -
- -
- - -
- - {formField.credit_to === 'I' && ( +
+

Loading transfer fee details...

+
+ ) : ( + +
+
+
+ + {isLoadingTransactionType && ( +
+
+
+ Loading transaction type... +
+
+ )} + +
+
+ +
+ + + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+ +
+ + + setFormField((prev) => ({ ...prev, description: target.value })) + } + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + minimum_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Minimum Amount" + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + maximum_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Maximum Amount" + /> +
+ +
+ + + setFormField((prev) => ({ ...prev, period_start: target.value })) + } + /> +
+ +
+ + + setFormField((prev) => ({ ...prev, period_end: target.value })) + } + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + deduct_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Deduct Amount" + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + deduct_percentage: values.floatValue || 0 + })); + }} + placeholder="Enter Deduct Percentage" + /> +
+ +
+ + { + setFormField((prev) => ({ + ...prev, + fee_amount: values.floatValue || 0 + })); + }} + placeholder="Enter Fee Amount" + /> +
+ +
+
- )} + +
+ + {renderSelectWithLoading( + formField.deduct_from_account, + (value) => setFormField({ ...formField, deduct_from_account: value }), + wallets, + 'Select Wallet', + isLoadingWallets + )} +
+ +
+ + +
+ +{formField.credit_to === 'I' && ( +
+ + + + + + + + + + No Customer found. + + {customers.map((customer) => ( + { + setFormField({ + ...formField, + credit_destination: customer.id + }); + setOpen(false); + }} + > + {customer.username} + + ))} + + + + + +
+)} -
- - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- + +
+ + {renderSelectWithLoading( + formField.credit_destination_account, + (value) => setFormField({ ...formField, credit_destination_account: value }), + wallets, + 'Select Wallet', + isLoadingWallets + )} +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
-
- + + )}
); }; -export { EditFeeDialog }; +export {EditFeeDialog}; \ No newline at end of file From 83593db7ac76bce18c52e01f99842238b143bddf Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Tue, 15 Apr 2025 01:04:53 +0700 Subject: [PATCH 49/52] adding search in credit_destination on update transaction fee --- .../transferfee/blocks/EditDialog.tsx | 95 ++++++++++++------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index c42823f..b8077b5 100644 --- a/src/pages/transfer/transferfee/blocks/EditDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/EditDialog.tsx @@ -343,13 +343,19 @@ const EditFeeDialog = () => { hasFetchedRef.current = false; } }, [selectedTransferFee, showEditFeeDialog, fetchTransactionFee]); - const handleCloseDialog = () => { setFormField(initialState); setAlert({ show: false, message: '' }); + setCustomerSearchTerm(''); + setOpen(false); handleEditFeeDialog(false, null); }; - + useEffect(() => { + if (!showEditFeeDialog) { + setCustomerSearchTerm(''); + setOpen(false); + } + }, [showEditFeeDialog]); const renderSelectWithLoading = ( value: string, onChangeHandler: (value: string) => void, @@ -668,36 +674,51 @@ const EditFeeDialog = () => { -{formField.credit_to === 'I' && ( + {formField.credit_to === 'I' && (
- - - - - - - - - No Customer found. - - {customers.map((customer) => ( - { +
+
setOpen(!open)} + > + + {customers.find(customer => customer.id === formField.credit_destination)?.username || 'Search customer...'} + + + + +
+ + {open && ( +
+
+ setCustomerSearchTerm(e.target.value)} + autoComplete="off" + // Prevent clicks from closing the dropdown + onClick={(e) => e.stopPropagation()} + // Auto focus the input when opened + autoFocus + /> +
+
+ {customers + .filter(customer => + customer.username.toLowerCase().includes(customerSearchTerm.toLowerCase()) || + customer.msisdn.includes(customerSearchTerm) + ) + .map(customer => ( +
{ setFormField({ ...formField, credit_destination: customer.id @@ -706,17 +727,23 @@ const EditFeeDialog = () => { }} > {customer.username} - +
))} - - - - - + {customers.filter(customer => + customer.username.toLowerCase().includes(customerSearchTerm.toLowerCase()) || + customer.msisdn.includes(customerSearchTerm) + ).length === 0 && ( +
No customer found
+ )} +
+
+ )} +
)} +
) : (
)} diff --git a/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx index bf5e46f..c0742fd 100644 --- a/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx +++ b/src/pages/members/kyc-delete-member/hooks/ManageKycDeletionContext.tsx @@ -1,5 +1,6 @@ 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'; @@ -36,6 +37,7 @@ interface ContextProps { selectedIdCustomer: string | null; detailKyc: any | null; setDetailKyc: React.Dispatch>; + handleApproveReject: (customerDeletionId: string, status_approve: string) => {}; } const initialProps: ContextProps = { @@ -48,6 +50,7 @@ const initialProps: ContextProps = { setShowDetailDialog: () => { }, detailKyc: async () => {}, setDetailKyc: () => { }, + handleApproveReject: () => ({customerDeletionId: '0', status_approve: 'Y'}), }; const ManageKycDeletionContext = createContext(initialProps); @@ -83,13 +86,15 @@ export const renderStatusBadge = (statusRaw: string | null | undefined) => { ); }; +// const { reload } = useDataGrid(); + const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactNode }) => { const [showDetailDialog, setShowDetailDialog] = useState(false); const [showAddDialog, setShowAddDialog] = useState(false); const [selectedIdCustomer, setSelectedIdCustomer] = useState(null); const [detailKyc, setDetailKyc] = useState(); const [manageKyc, setManageKyc] = useState([]); - const { GetData } = useCallApi(); + const { GetData, PostData } = useCallApi(); const getKycDeletionList = async (page: number, limit: number, sorting: any, filter: any) => { try { @@ -130,12 +135,11 @@ const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactN }; const handleDetailDialog = useCallback(async (show: boolean, selected_id_customer: string | null) => { - setSelectedIdCustomer(show ? selected_id_customer : null); - let detailCustomer = await GetData(`${API_URL}/customer_deletion/detail/${selected_id_customer}`, {}) - setDetailKyc(detailCustomer?.data) - console.log('detailCustomer: ',detailCustomer) - console.log('detailCustomer2: ',detailCustomer?.data) - console.log('detailKyc: ', detailKyc) + if (show == true) { + setSelectedIdCustomer(show ? selected_id_customer : null); + let detailCustomer = await GetData(`${API_URL}/customer_deletion/detail/${selected_id_customer}`, {}) + setDetailKyc(detailCustomer?.data) + } setShowDetailDialog(show); }, []); @@ -143,6 +147,26 @@ const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactN setShowAddDialog(show); }, []); + const handleApproveReject = useCallback(async (customerDeletionId: string, status_approve: string) => { + console.log(customerDeletionId, status_approve) + try { + let approveReject = await PostData(`${API_URL}/customer_deletion/update_status/${customerDeletionId}`, { + status_approve + }) + + if (approveReject?.status == true) { + toast.success('Success update status approval') + handleDetailDialog(false, null) + } else { + toast.warning(`${approveReject?.message}`) + handleDetailDialog(false, null) + } + } catch (error) { + toast.warning('Failed to approve/reject') + handleDetailDialog(false, null) + } + }, []) + const columns = useMemo[]>( () => [ { @@ -242,7 +266,8 @@ const ManageKycDeletionContextProvider = ({ children }: { children: React.ReactN selectedIdCustomer, setShowDetailDialog, setDetailKyc, - detailKyc + detailKyc, + handleApproveReject, }} > From 172e8a0815f04b8b4d2abd37ba2df61aca8ed23e Mon Sep 17 00:00:00 2001 From: bagusajisaputroo Date: Tue, 15 Apr 2025 10:44:43 +0700 Subject: [PATCH 52/52] added search on credit_destination field in transaction fee add +edit --- .../transfer/transferfee/blocks/AddDialog.tsx | 83 +++++++++++++++++-- .../transferfee/blocks/EditDialog.tsx | 5 -- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/pages/transfer/transferfee/blocks/AddDialog.tsx b/src/pages/transfer/transferfee/blocks/AddDialog.tsx index 0860010..6b73eed 100644 --- a/src/pages/transfer/transferfee/blocks/AddDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/AddDialog.tsx @@ -70,6 +70,8 @@ const AddFeeDialog = () => { selectedTransferFee, transactionTypeId } = useManageTransferFeeContext(); + const [customerSearchTerm, setCustomerSearchTerm] = useState(''); + const [open, setOpen] = useState(false); const [alert, setAlert] = useState({ show: false, @@ -111,6 +113,7 @@ const AddFeeDialog = () => { credit_destination_account: '' }; + const [formField, setFormField] = useState(initialState); const resetForm = () => { @@ -151,6 +154,13 @@ const AddFeeDialog = () => { } }, [showAddFeeDialog, transactionTypeId, GetData]); + useEffect(() => { + if (!showAddFeeDialog) { + setCustomerSearchTerm(''); + setOpen(false); + } + }, [showAddFeeDialog]); + useEffect(() => { const created_time = new Date(); const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); @@ -619,13 +629,72 @@ const AddFeeDialog = () => { - {renderSelectWithLoading( - formField.credit_destination, - (value) => setFormField({ ...formField, credit_destination: value }), - customersWithNames, - 'Select Customer', - isLoadingCustomers - )} +
+
setOpen(!open)} + > + + {customers.find( + (customer) => customer.id === formField.credit_destination + )?.username || 'Search customer...'} + + +
+ + {open && ( +
+
+ setCustomerSearchTerm(e.target.value)} + autoComplete="off" + onClick={(e) => e.stopPropagation()} + autoFocus + /> +
+
+ {customers + .filter( + (customer) => + customer.username + .toLowerCase() + .includes(customerSearchTerm.toLowerCase()) || + customer.msisdn.includes(customerSearchTerm) + ) + .map((customer) => ( +
{ + setFormField({ + ...formField, + credit_destination: customer.id + }); + setOpen(false); + }} + > + {customer.username} +
+ ))} + {customers.filter( + (customer) => + customer.username + .toLowerCase() + .includes(customerSearchTerm.toLowerCase()) || + customer.msisdn.includes(customerSearchTerm) + ).length === 0 && ( +
+ No customer found +
+ )} +
+
+ )} +
)}
diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index b8077b5..3d6b0ce 100644 --- a/src/pages/transfer/transferfee/blocks/EditDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/EditDialog.tsx @@ -72,7 +72,6 @@ const EditFeeDialog = () => { message: '' }); - // Loading states const [isLoadingTransferFee, setIsLoadingTransferFee] = useState(false); const [isLoadingTransactionType, setIsLoadingTransactionType] = useState(false); const [isLoadingWallets, setIsLoadingWallets] = useState(false); @@ -687,9 +686,7 @@ const EditFeeDialog = () => { {customers.find(customer => customer.id === formField.credit_destination)?.username || 'Search customer...'} - -
{open && ( @@ -702,9 +699,7 @@ const EditFeeDialog = () => { value={customerSearchTerm} onChange={(e) => setCustomerSearchTerm(e.target.value)} autoComplete="off" - // Prevent clicks from closing the dropdown onClick={(e) => e.stopPropagation()} - // Auto focus the input when opened autoFocus />
{log.customer.fullname ?? '-'} {log.amount ?? '-'} {renderStatusBadge(log.status) ?? '-'}{log.request_date && moment(log.request_date).isValid() ? moment(log.request_date).format('YYYY-MM-DD HH:mm:ss') : '-'}{log.request_date && moment(log.request_date).isValid() ? moment(log.request_date).format('DD/MM/YYYY HH:mm:ss') : '-'} {log.reference ?? '-'}
diff --git a/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx b/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx index 7b26b05..b2a668f 100644 --- a/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx +++ b/src/pages/disbursement/history-transaction/blocks/DetailTransactionLog.tsx @@ -106,11 +106,11 @@ const TransactionLogViewer = () => {
Prosess Date{detailLogData.request_date && moment(detailLogData.request_date).isValid() ? moment(detailLogData.request_date).format('YYYY-MM-DD HH:mm:ss') : '-'}{detailLogData.request_date && moment(detailLogData.request_date).isValid() ? moment(detailLogData.request_date).format('DD/MM/YYYY HH:mm:ss') : '-'}
Response Date{detailLogData.response_date && moment(detailLogData.response_date).isValid() ? moment(detailLogData.response_date).format('YYYY-MM-DD HH:mm:ss') : '-'}{detailLogData.response_date && moment(detailLogData.response_date).isValid() ? moment(detailLogData.response_date).format('DD/MM/YYYY HH:mm:ss') : '-'}
Reference Number