This commit is contained in:
unknown
2025-04-11 22:57:30 +07:00
4 changed files with 236 additions and 183 deletions

View File

@ -23,7 +23,7 @@ const HistoryTransactionDisbursement = () => {
</Link> </Link>
<Link underline="none" color="inherit"> <Link underline="none" color="inherit">
<span className="text-sm">History Transaction</span> <span className="text-sm">History Disbursement</span>
</Link> </Link>
</Breadcrumbs> </Breadcrumbs>
<div className="grid gap-5 lg:gap-7.5"> <div className="grid gap-5 lg:gap-7.5">

View File

@ -218,7 +218,7 @@ const AddDialog = () => {
order_direction: 'ASC', order_direction: 'ASC',
}; };
const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, params); const response = await GetData(`${API_URL_MASTERDATA}/wallet/list`, params);
// console.log(response) console.log(response)
if (response?.status && response?.data) { if (response?.status && response?.data) {
setWallets(response.data.list); setWallets(response.data.list);
} else { } else {
@ -230,7 +230,7 @@ const AddDialog = () => {
if (!showAddDialog) return; if (!showAddDialog) return;
fetchWallets(); fetchWallets();
}, [showAddDialog]); }, [showAddDialog]);
// console.log(formField) console.log(formField)
return ( return (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}> <Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[1080px] flex flex-col p-5 overflow-hidden [&>button]:hidden"> <DialogContent className="container-fixed max-w-[1080px] flex flex-col p-5 overflow-hidden [&>button]:hidden">

View File

@ -25,23 +25,16 @@ import { useCallApi } from '@/hooks';
import { getAuth } from '@/auth'; import { getAuth } from '@/auth';
import { ManageTransferFeeContextProvider } from '../../transferfee/hooks/ManageTransferFeeContext'; import { ManageTransferFeeContextProvider } from '../../transferfee/hooks/ManageTransferFeeContext';
import AddFeeDialog from '../../transferfee/blocks/AddDialog'; import AddFeeDialog from '../../transferfee/blocks/AddDialog';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Checkbox } from '@/components/ui/checkbox';
import { import { doSaveLogActivity } from '@/actions/GlobalActions';
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from '@/components/ui/command';
const API_URL = apiConfig.service_transaction; const API_URL = apiConfig.service_transaction;
const API_URL_MASTERDATA = apiConfig.service_master_data; const API_URL_MASTERDATA = apiConfig.service_master_data;
const API_URL_CUSTOMER = apiConfig.service_customer; const API_URL_CUSTOMER = apiConfig.service_customer;
interface WalletProps { interface WalletProps {
Wallet_id: string; id: string;
Wallet_name: string; name: string;
} }
interface CustomerProps { interface CustomerProps {
@ -50,19 +43,11 @@ interface CustomerProps {
msisdn: string; msisdn: string;
} }
interface TranssactionTypeProps { interface GroupProps {
id: string;
name: string; name: string;
description: string; description: string;
minimum_amount: number;
maximum_amount: number;
max_transaction_per_day: number;
status_approval: string;
status: string; status: string;
type: string;
wallet_origin: WalletProps;
wallet_destination: WalletProps;
wallet_fee_destination: WalletProps;
customer_fee_destination: CustomerProps;
} }
const EditDialog = () => { const EditDialog = () => {
@ -71,29 +56,43 @@ const EditDialog = () => {
useManageTransferTypeContext(); useManageTransferTypeContext();
const { reload } = useDataGrid(); const { reload } = useDataGrid();
const [wallets, setWallets] = useState<WalletProps[]>([]); const [wallets, setWallets] = useState<WalletProps[]>([]);
const [groups, setGroups] = useState<GroupProps[]>([]);
const { GetData, PutData } = useCallApi(); const { GetData, PutData } = useCallApi();
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const parsedUser = getAuth()?.user; const parsedUser = getAuth()?.user;
const [customers, setCustomers] = useState<CustomerProps[]>([]); const [customers, setCustomers] = useState<CustomerProps[]>([]);
const [open, setOpen] = useState(false); const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
const [alert, setAlert] = useState({ const [alert, setAlert] = useState({
show: false, show: false,
message: '' 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: '', name: '',
description: '', description: '',
wallet_origin: '', wallet_origin: '',
wallet_destination: '', wallet_destination: '',
wallet_fee_destination: '',
customer_fee_destination: '',
minimum_amount: 0, minimum_amount: 0,
maximum_amount: 0, maximum_amount: 0,
max_transaction_per_day: 0, max_transaction_per_day: 0,
status_approval: '', status_approval: '',
type: '', type: '',
status: '', status: '',
permission: [],
updated_by: '', updated_by: '',
updated_at: '' updated_at: ''
}; };
@ -102,27 +101,47 @@ const EditDialog = () => {
const resetForm = () => { const resetForm = () => {
setFormField(initialState); 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 validateForm = () => {
const requiredFields = [ const requiredFields = [
'name', 'name',
'description', 'description',
'wallet_origin', 'wallet_origin',
'wallet_destination', 'wallet_destination',
'wallet_fee_destination',
'customer_fee_destination',
'status', 'status',
'status_approval', 'status_approval',
'type' 'type'
]; ];
const missingFields = requiredFields.filter( const missingFields = requiredFields.filter(
(field) => (field) => {
formField[field as keyof typeof formField] === '' || return formField[field as keyof typeof formField] === '' ||
formField[field as keyof typeof formField] === null || formField[field as keyof typeof formField] === null ||
formField[field as keyof typeof formField] === undefined formField[field as keyof typeof formField] === undefined;
}
); );
if (missingFields.length > 0) { if (missingFields.length > 0) {
@ -133,6 +152,15 @@ const EditDialog = () => {
return false; 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: '' }); setAlert({ show: false, message: '' });
return true; return true;
}; };
@ -148,7 +176,12 @@ const EditDialog = () => {
updated_at: formattedTime 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<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -165,6 +198,14 @@ const EditDialog = () => {
handleEditDialog(false, null); handleEditDialog(false, null);
toast.success('Success Update Transfer Type'); toast.success('Success Update Transfer Type');
reload(); reload();
const createActivity = {
module: 'Manage Transfer Type',
description: `Edit Transfer Type => ${selectedTransferType}`,
action: 'U'
};
doSaveLogActivity(createActivity);
} }
}) })
.finally(() => { .finally(() => {
@ -197,11 +238,13 @@ const EditDialog = () => {
} }
}, [formField, selectedTransferType, PutData]); }, [formField, selectedTransferType, PutData]);
// Fetch customers
useEffect(() => { useEffect(() => {
if (!showEditDialog) return; if (!showEditDialog) return;
const getCustomerList = async (sorting: any) => {
const getCustomerList = async () => {
try { 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`, { const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
limit: 100, limit: 100,
page: 1, page: 1,
@ -209,74 +252,113 @@ const EditDialog = () => {
order_field: sorting[0].id, order_field: sorting[0].id,
order_direction: sorting[0].desc ? 'DESC' : 'ASC' order_direction: sorting[0].desc ? 'DESC' : 'ASC'
}); });
setCustomers(response?.data.list);
if (response?.status && response?.data) {
setCustomers(response.data.list);
}
} catch (error) { } catch (error) {
console.error('Error fetching customer', error); console.error('Error fetching customer', error);
} }
}; };
getCustomerList([{ id: 'id', desc: false }]); getCustomerList();
}, [showEditDialog, GetData]); }, [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(() => { useEffect(() => {
if (!showEditDialog) return; if (!showEditDialog) return;
fetchWallets();
}, [showEditDialog, fetchWallets]); const getGroupList = async () => {
try {
const fetchTransactionType = useCallback(async (id: string) => { const response = await GetData(`${API_URL_MASTERDATA}/groups/list`, {
try { limit: 100,
const response = await GetData(`${API_URL}/transactiontype/getdata/${id}`, { id }); page: 1,
if (response?.status) { with_deleted: false,
setFormField((prev) => ({ order_field: 'name',
...prev, order_direction: 'ASC'
name: response.data.name, });
description: response.data.description,
wallet_origin: response.data.wallet_origin.id, if (response?.status && response?.data) {
wallet_destination: response.data.wallet_destination.id, setGroups(response.data.list);
wallet_fee_destination: response.data.wallet_fee_destination.id, }
customer_fee_destination: response.data.customer_fee_destination.id, } catch (error) {
minimum_amount: response.data.minimum_amount, console.error('Error fetching groups', error);
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
}));
} }
// console.log(response); };
} catch (error) {
console.error('Error fetching transaction type', error); getGroupList();
setAlert({ }, [showEditDialog, GetData]);
show: true,
message: 'Failed to load transaction type data' // Fetch wallets
}); useEffect(() => {
} if (!showEditDialog) return;
}, [GetData]);
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(() => { useEffect(() => {
if (selectedTransferType) { if (showEditDialog === false) {
fetchTransactionType(selectedTransferType); resetForm();
} }
}, [selectedTransferType, fetchTransactionType]); }, [showEditDialog]);
return ( return (
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}> <Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
@ -305,9 +387,11 @@ const EditDialog = () => {
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}> <DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0"> <div className="flex flex-col px-0">
{alert.show && ( {alert.show && (
<Alert variant="danger" className="mb-3"> <div className="sticky top-0 z-10 bg-white p-3">
<h3>{alert.message}</h3> <Alert variant="danger" className="mb-3">
</Alert> <h3>{alert.message}</h3>
</Alert>
</div>
)} )}
<form action="" onSubmit={handleSubmit}> <form action="" onSubmit={handleSubmit}>
<div className="card-body grid gap-5 p-0"> <div className="card-body grid gap-5 p-0">
@ -439,8 +523,8 @@ const EditDialog = () => {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{wallets.map((wallet) => ( {wallets.map((wallet) => (
<SelectItem value={wallet.Wallet_id} key={wallet.Wallet_id}> <SelectItem value={wallet.id} key={wallet.id}>
{wallet.Wallet_name} {wallet.name}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
@ -467,8 +551,8 @@ const EditDialog = () => {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{wallets.map((wallet) => ( {wallets.map((wallet) => (
<SelectItem value={wallet.Wallet_id} key={wallet.Wallet_id}> <SelectItem value={wallet.id} key={wallet.id}>
{wallet.Wallet_name} {wallet.name}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
@ -476,86 +560,6 @@ const EditDialog = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Wallet Destination Fee
<span className="text-red-500">*</span>
</label>
<div className="grow">
<Select
value={formField.wallet_fee_destination}
onValueChange={(wallet_fee_destination) =>
setFormField((prev) => ({ ...prev, wallet_fee_destination }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select Wallet" />
</SelectTrigger>
<SelectContent>
{wallets.map((wallet) => (
<SelectItem value={wallet.Wallet_id} key={wallet.Wallet_id}>
{wallet.Wallet_name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Customer Fee Destination<span className="text-red-500">*</span>
</label>
<div className="grow">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
className="input col-span-5 text-left"
style={{ color: 'inherit' }}
>
{customers.find(
(customer) => customer.id === formField.customer_fee_destination
)?.username || 'Select Customer'}
</button>
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0">
<Command>
<CommandInput placeholder="Search Customer..." />
<CommandList
className="max-h-[300px] overflow-y-auto"
style={{ touchAction: 'pan-y' }}
onWheel={(e) => {
e.currentTarget.scrollTop += e.deltaY;
}}
>
<CommandEmpty>No Customer found.</CommandEmpty>
<CommandGroup>
{customers.map((customer) => (
<CommandItem
key={customer.id}
value={customer.username}
onSelect={() => {
setFormField({
...formField,
customer_fee_destination: customer.id
});
setOpen(false);
}}
>
{customer.username} - {customer.msisdn}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
</div>
<div className="w-full"> <div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
@ -637,7 +641,47 @@ const EditDialog = () => {
</div> </div>
</div> </div>
</div> </div>
{/* Group Permission Section - Read Only Display */}
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Groups
<span className="text-red-500">*</span>
</label>
<div className="relative w-full">
<Input
type="text"
placeholder="No groups selected"
value={selectedPermissionNames || ""}
readOnly
className="bg-gray-100 mb-2"
/>
{/* Groups Selection Area */}
<div className="border rounded-md p-3 max-h-48 overflow-y-auto">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{groups.map((group) => (
<div key={group.id} className="flex items-center space-x-2">
<Checkbox
id={`group-${group.id}`}
checked={formField.permission.includes(group.id)}
onCheckedChange={() => handleGroupChange(group.id)}
/>
<label
htmlFor={`group-${group.id}`}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{group.name}
</label>
</div>
))}
</div>
</div>
</div>
</div>
</div>
<div className="flex justify-end pt-2.5 gap-5"> <div className="flex justify-end pt-2.5 gap-5">
<Button <Button
variant={'outline'} variant={'outline'}
@ -654,7 +698,6 @@ const EditDialog = () => {
</div> </div>
</div> </div>
</form> </form>
{/* Transaction Fee Section */} {/* Transaction Fee Section */}
<ManageTransferFeeContextProvider> <ManageTransferFeeContextProvider>
<Container> <Container>

View File

@ -76,6 +76,16 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
const columns = useMemo<ColumnDef<any>[]>( const columns = useMemo<ColumnDef<any>[]>(
() => [ () => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => (
<DataGridColumnHeader title="Transaction Type ID" column={column} />
),
enableSorting: false,
enableHiding: false,
meta: { headerClassName: 'w-[250px]' }
},
{ {
accessorFn: (row) => row.name, accessorFn: (row) => row.name,
id: 'name', id: 'name',
@ -213,7 +223,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
order_direction: orderDirection, order_direction: orderDirection,
filter: JSON.stringify(filter) filter: JSON.stringify(filter)
}); });
console.log(response?.data.list);
return { data: response?.data.list, totalCount: response?.data.total_count }; return { data: response?.data.list, totalCount: response?.data.total_count };
}; };