diff --git a/public/media/avatars/profile.png b/public/media/avatars/profile.png new file mode 100644 index 0000000..fc1169b Binary files /dev/null and b/public/media/avatars/profile.png differ diff --git a/src/layouts/demo2/header/HeaderTopbar.tsx b/src/layouts/demo2/header/HeaderTopbar.tsx index 44a6057..6cfac6a 100644 --- a/src/layouts/demo2/header/HeaderTopbar.tsx +++ b/src/layouts/demo2/header/HeaderTopbar.tsx @@ -1,9 +1,10 @@ -import { useRef } from 'react'; +import { useRef, useEffect, useState } from 'react'; import { KeenIcon } from '@/components/keenicons'; import { toAbsoluteUrl } from '@/utils'; import { Menu, MenuItem, MenuToggle } from '@/components'; import { DropdownUser } from '@/partials/dropdowns/user'; import { useLanguage } from '@/i18n'; +import { getAuth } from '@/auth'; const HeaderTopbar = () => { const itemChatRef = useRef(null); @@ -14,38 +15,44 @@ const HeaderTopbar = () => { const handleDropdownChatShow = () => { window.dispatchEvent(new Event('resize')); }; - + console.log(getAuth()) return ( -
- - - - - - {DropdownUser({ menuItemRef: itemUserRef })} - - -
+
+
+ {getAuth()?.user.username} + {getAuth()?.role_name} +
+ + + + + User avatar + + {DropdownUser({ menuItemRef: itemUserRef })} + + +
+ ); }; -export { HeaderTopbar }; +export { HeaderTopbar }; \ No newline at end of file diff --git a/src/pages/dashboards/home/DashboardHomePage.tsx b/src/pages/dashboards/home/DashboardHomePage.tsx index 4884ebb..22d69b9 100644 --- a/src/pages/dashboards/home/DashboardHomePage.tsx +++ b/src/pages/dashboards/home/DashboardHomePage.tsx @@ -16,6 +16,10 @@ import { useFetchYear } from './hooks/useFetchYear'; import { get5LastYear } from '@/utils/Date'; import { staticChartData } from './staticChart'; import { Helmet } from 'react-helmet'; +import BalanceCard from './blocks/BalanceCard'; +import { getAuth } from '@/auth'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; // sum -> nominal, count-> total type CountType = 'sum' | 'count'; @@ -33,6 +37,9 @@ const DashboardHomePage = () => { from: new Date(), to: new Date() }); + const currentRole = getAuth()?.role_name; + const idCustomer = getAuth()?.user.customer?.id; + // console.log(currentRole); // Menyusun tanggal awal dan akhir berdasarkan selectedYear useEffect(() => { @@ -144,6 +151,13 @@ const DashboardHomePage = () => { TPAY | Dashboard + {/* Account Balance Cards */} + {currentRole === 'Escrow' || currentRole === 'Master Agent' ? ( +
+ +
+ ) : null} +
diff --git a/src/pages/dashboards/home/blocks/BalanceCard.tsx b/src/pages/dashboards/home/blocks/BalanceCard.tsx new file mode 100644 index 0000000..e349fbd --- /dev/null +++ b/src/pages/dashboards/home/blocks/BalanceCard.tsx @@ -0,0 +1,100 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { CreditCard, Wallet } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +type WalletResponse = { + amount: number; + id_wallet: string; + wallet: string; + credit_limit: number; + monthly_limit: number; +}; + +interface BalanceCardProps { + id: string; +} + +const API_URL_WALLET = apiConfig.service_wallet; + +const BalanceCard = ({ id }: BalanceCardProps) => { + const { GetData } = useCallApi(); + const [wallets, setWallets] = useState([]); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(value); + }; + + const formatNumberWithDots = (value: number) => { + return value.toLocaleString('id-ID'); + }; + + const getColorClass = (amount: number, walletType: string) => { + if (walletType.toLowerCase().includes('point account')) return 'bg-amber-300'; + // if (amount > 3125) return 'bg-amber-300'; + return 'bg-red-500'; + }; + + useEffect(() => { + async function fetchAccountBalances() { + try { + const response = await GetData(`${API_URL_WALLET}/dashboard/balance/account/${id}`, { + id: id + }); + if (response?.status === true) { + setWallets(response.data); + } + } catch (error) { + console.error('Error fetching account balances', error); + } + } + + fetchAccountBalances(); + }, [id]); + + return ( +
+ {wallets.map((wallet) => { + const colorClass = getColorClass(wallet.amount, wallet.wallet); + const isNegative = wallet.amount < 0; + + return ( +
+
+
+
+

{wallet.wallet}

+ {wallet.wallet.includes('Credit') ? ( + + ) : ( + + )} +
+

+ {formatCurrency(wallet.amount)} +

+ +
+
Credit Limit:
+
{formatNumberWithDots(wallet.credit_limit)}
+
+
+
Monthly Limit:
+
{formatNumberWithDots(wallet.monthly_limit)}
+
+
+
+ ); + })} +
+ ); +}; + +export default BalanceCard; diff --git a/src/pages/groups/Column.tsx b/src/pages/groups/Column.tsx index 2f6fe7f..3d1a1e0 100644 --- a/src/pages/groups/Column.tsx +++ b/src/pages/groups/Column.tsx @@ -1,5 +1,8 @@ import { KeenIcon } from '@/components'; +import { Button } from '@/components/ui/button'; import { ColumnDef } from '@tanstack/react-table'; +import { ArrowUpDown } from 'lucide-react'; +import moment from 'moment'; export type Group = { no: number; @@ -12,27 +15,81 @@ export type Group = { export const getColumns = (handleUpdate: (data: any) => void): ColumnDef[] => [ { accessorKey: 'no', - header: 'ID' + header: ({ column }) => { + return ( + + ); + } }, { accessorKey: 'created_at', - header: 'Created Date', - cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString() + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => moment(row.original.created_at).format('YYYY-MM-DD HH:mm:ss') }, { accessorKey: 'name', - header: 'Name' + header: ({ column }) => { + return ( + + ); + } }, { accessorKey: 'status', - header: 'Status' + header: 'Status', + cell: ({ row }) => { + const isActive = row.original.status === 'Y'; + + return ( + + {isActive ? 'Active' : 'inactive'} + + ); + } }, { accessorKey: 'description', - header: 'Description' + header: ({ column }) => { + return ( + + ); + } }, { id: 'actions', + header: 'Actions', cell: ({ row }) => { const dataMembers = row.original; diff --git a/src/pages/groups/ListToolbar.tsx b/src/pages/groups/ListToolbar.tsx index 793f4c9..9d3dd1a 100644 --- a/src/pages/groups/ListToolbar.tsx +++ b/src/pages/groups/ListToolbar.tsx @@ -1,8 +1,20 @@ import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; import { Button } from '@/components/ui/button'; +import React, { useState } from 'react'; -const ListToolBar = ({ createGroup }: { createGroup: () => void }) => { - const { table, reload } = useDataGrid(); +interface ListToolBarProps { + createGroup: () => void; + onReload: () => void; + isReloading: boolean; +} + +const ListToolBar = ({ createGroup, onReload, isReloading }: ListToolBarProps) => { + const { table } = useDataGrid(); + const [usernameFilter, setUsernameFilter] = useState(''); + const handleUsernameChange = (e: React.ChangeEvent) => { + setUsernameFilter(e.target.value); + table.getColumn('name')?.setFilterValue(e.target.value); + }; return (
@@ -14,29 +26,25 @@ const ListToolBar = ({ createGroup }: { createGroup: () => void }) => { table.getColumn('name')?.setFilterValue(event.target.value)} + value={usernameFilter} + onChange={handleUsernameChange} + className="input input-sm w-40" /> - {/* - - */}
- -
diff --git a/src/pages/groups/ManageGroups.tsx b/src/pages/groups/ManageGroups.tsx index d03966f..32841c2 100644 --- a/src/pages/groups/ManageGroups.tsx +++ b/src/pages/groups/ManageGroups.tsx @@ -24,8 +24,10 @@ import CloseIcon from '@mui/icons-material/Close'; import Divider from '@mui/material/Divider'; import ConfirmDialog from '@/components/confirm'; import { toast } from 'sonner'; -import { Container, DataGridProvider } from '@/components'; +import { Container, DataGridProvider, LoaderTransparant } from '@/components'; import { ListToolBar } from './ListToolbar'; +import { RefreshCw } from 'lucide-react'; +import { setgroups } from 'process'; // import { DialogHeader } from '@/components/ui/dialog'; // import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; const BASE_URL = apiConfig.service_customer; @@ -42,37 +44,52 @@ let initGroup = { const ManageGroups = () => { const [isDialogOpen, setIsDialogOpen] = useState(false); + const [loading, setLoading] = useState(false); const [dataGroup, setDataGroup] = useState([]); const [formData, setFormData] = useState(initGroup); const [pageIndex, setPageIndex] = useState(1); const [pageSize, setPageSize] = useState(10); const [dialogType, setDialogType] = useState(''); const [dialogOpen, setDialogOpen] = useState(false); + const [isReloading, setIsReloading] = useState(false); + // const closeDialog = () => { + // setIsDialogOpen(false); + // setgroups(initGroup); + // }; useEffect(() => { + setLoading(true); fetchGroups(); + setLoading(false); }, []); - async function fetchGroups() { + async function fetchGroups(): Promise { try { let groups = await axios.get(`${BASE_URL}/groups/list`, { params: { - limit: pageSize, - page: pageIndex, + limit: 20, + page: 1, with_deleted: false, - order_field: 'name', - order_direction: 'ASC' + order_field: 'created_at', + order_direction: 'DESC' } }); let temp = 1; let resGroups = groups.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'); + } return el; - }) + }); setDataGroup(resGroups); } catch (error: any) { alert(error.message); console.log(error); + } finally { + setLoading(false); + setIsReloading(false); } } @@ -127,6 +144,11 @@ const ManageGroups = () => { setDialogOpen(true); }; + const handleReload = () => { + setIsReloading(true); + fetchGroups(); + }; + const handleYes = async () => { try { if (dialogType === 'create') { @@ -153,12 +175,19 @@ const ManageGroups = () => { console.log(error); toast.error(error.message); } finally { - await fetchGroups(); setDialogOpen(false); closeDialog(); + await fetchGroups(); + setLoading(false); } }; + function setShowAddDialog(el: any) { + setIsDialogOpen(el); + } + + if (loading) return ; + return ( <> @@ -191,21 +220,28 @@ const ManageGroups = () => { {/*
*/} -
- {/* */} + +
+ {isReloading && ( +
+
+ + Refreshing data... +
+
+ )} } + toolbar={ + + } layout={{ card: true }} - sorting={[{ id: 'created_at', desc: true }]} serverSide={false} onRowSelectionChange={(selected, table: any) => { const selectedRow = table.getSelectedRowModel().rows[0]; diff --git a/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx b/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx new file mode 100644 index 0000000..648ac9e --- /dev/null +++ b/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx @@ -0,0 +1,199 @@ +import React, { useEffect, useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; +import { format } from 'date-fns'; + +interface FeedbackDetailProps { + showDialog: boolean; + handleDialog: (show: boolean) => void; + feedbackId: string | null; +} + +interface FeedbackDetailData { + Feedback_id: string; + Feedback_feedback_notes: string; + Feedback_feedback_screenshoot: string; + Feedback_review_notes: string; + Feedback_emotion: string; + Feedback_review_screenshoot: string | null; + Feedback_category: string; + Feedback_status: string; + Feedback_created_at: string; + Feedback_review_by: string | null; + Feedback_review_at: string; + Feedback_deleted_by: string | null; + Feedback_deleted_at: string | null; + Feedback_createdById: string; +} + +const API_URL = apiConfig.service_feedback; + +const FeedbackDetail: React.FC = ({ showDialog, handleDialog, feedbackId }) => { + const [feedbackDetail, setFeedbackDetail] = useState(null); + const [loading, setLoading] = useState(false); + const { GetData } = useCallApi(); + + useEffect(() => { + const fetchFeedbackDetail = async () => { + if (!feedbackId) return; + + setLoading(true); + try { + const response = await GetData(`${API_URL}/feedback/${feedbackId}`,{}); + if (response?.data) { + setFeedbackDetail(response.data); + } + } catch (error) { + console.error('Error fetching feedback detail:', error); + } finally { + setLoading(false); + } + }; + + if (showDialog && feedbackId) { + fetchFeedbackDetail(); + } else { + setFeedbackDetail(null); + } + }, [showDialog, feedbackId, GetData]); + + const formatDate = (dateString: string | null) => { + if (!dateString) return '-'; + try { + return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); + } catch (e) { + return dateString; + } + }; + + const getEmotionText = (emotion: string) => { + const emotions: Record = { + '1': 'Very Disappointed', + '2': 'Disappointed', + '3': 'Happy', + '4': 'Very Happy', + '5': 'Extremely Happy' + }; + return emotions[emotion] || emotion; + }; + + const getStatusText = (status: string) => { + const statuses: Record = { + 'W': 'Waiting', + 'P': 'Processed', + 'D': 'Done' + }; + return statuses[status] || status; + }; + + return ( + + + + Feedback Detail + + + {loading ? ( +
+
+ Loading... +
+
+ ) : feedbackDetail ? ( +
+
+
+ +
{feedbackDetail.Feedback_id}
+
+ +
+ +
{feedbackDetail.Feedback_category}
+
+ +
+ +
{getEmotionText(feedbackDetail.Feedback_emotion)}
+
+ +
+ +
{getStatusText(feedbackDetail.Feedback_status)}
+
+ +
+ +
{formatDate(feedbackDetail.Feedback_created_at)}
+
+ +
+ +
{feedbackDetail.Feedback_createdById}
+
+
+ +
+ +
{feedbackDetail.Feedback_feedback_notes}
+
+ + {feedbackDetail.Feedback_feedback_screenshoot && ( +
+ +
+ Feedback Screenshot { + (e.target as HTMLImageElement).style.display = 'none'; + (e.target as HTMLImageElement).parentElement?.appendChild( + Object.assign(document.createElement('div'), { + className: 'text-sm text-gray-500', + textContent: 'Image not available or invalid URL' + }) + ); + }} + /> +
+
+ )} + +
+ +
{feedbackDetail.Feedback_review_notes || '-'}
+
+ + {feedbackDetail.Feedback_review_screenshoot && ( +
+ +
+ Review Screenshot { + (e.target as HTMLImageElement).style.display = 'none'; + (e.target as HTMLImageElement).parentElement?.appendChild( + Object.assign(document.createElement('div'), { + className: 'text-sm text-gray-500', + textContent: 'Image not available or invalid URL' + }) + ); + }} + /> +
+
+ )} +
+ ) : ( +
No feedback details found
+ )} +
+
+ ); +}; + +export default FeedbackDetail; \ No newline at end of file diff --git a/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx b/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx index 41f986e..d971d08 100644 --- a/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx +++ b/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx @@ -4,7 +4,8 @@ import { useCallApi } from '@/hooks'; import { ColumnDef } from '@tanstack/react-table'; import React, { createContext, useCallback, useMemo, useState } from 'react'; import { Toaster } from 'sonner'; -// import ListToolbar from '../blocks/ListToolbar'; +// import FeedbackDetail from './blocks/FeedbackDetail'; +import FeedbackDetail from '../blocks/FeedbackDetail'; interface feedbackProps { id: string; @@ -24,6 +25,8 @@ interface ContextProps { handleAddDialog: (show: boolean) => void; showDeleteDialog: boolean; handleDeleteDialog: (show: boolean, selected_sucos: string | null) => void; + showDetailDialog: boolean; + handleDetailDialog: (show: boolean, selected_sucos: string | null) => void; selectedfeedback: string | null; getfeedbackLists: ( limit: number, @@ -42,6 +45,8 @@ const initialProps: ContextProps = { handleAddDialog: () => {}, showDeleteDialog: false, handleDeleteDialog: () => {}, + showDetailDialog: false, + handleDetailDialog: () => {}, selectedfeedback: null, getfeedbackLists: async () => undefined }; @@ -54,6 +59,7 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode const [showAddDialog, setShowAddDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [showDetailDialog, setShowDetailDialog] = useState(false); const [selectedfeedback, setSelectedfeedback] = useState(null); const { GetData } = useCallApi(); @@ -71,10 +77,35 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode setSelectedfeedback(show ? selected_feedback : null); }, []); + const handleDetailDialog = useCallback((show: boolean, selected_feedback: string | null) => { + setShowDetailDialog(show); + setSelectedfeedback(show ? selected_feedback : null); + }, []); + const columns = useMemo[]>( () => [ { - accessorFn: (row) => row.feedback_notes, + accessorFn: (row) => row.customer, + id: 'customer_id', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.Feedback_emotion, + id: 'feedback_emotion', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.Feedback_feedback_notes, id: 'feedback_notes', header: ({ column }) => , enableSorting: false, @@ -84,67 +115,45 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode } }, { - accessorFn: (row) => row.feedback_screenshot, - id: 'feedback_screenshot', - header: ({ column }) => , + id: 'actions', + header: ({ column }) => , enableSorting: false, enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( + <> + + {/* + */} + + ); + }, meta: { - headerClassName: 'w-[250px]' - } - }, - { - accessorFn: (row) => row.review_notes, - id: 'review_notes', - header: ({ column }) => , - enableSorting: false, - enableHiding: false, - meta: { - headerClassName: 'w-[250px]' - } - }, - { - accessorFn: (row) => row.review_screenshot, - id: 'review_screenshot', - header: ({ column }) => , - enableSorting: false, - enableHiding: false, - meta: { - headerClassName: 'w-[250px]' + headerClassName: 'w-[150px] text-center', + cellClassName: 'text-center' } } - // , - // { - // id: 'actions', - // header: ({ column }) => , - // enableSorting: false, - // enableHiding: false, - // cell: (data) => { - // const row = data.row.original; - // return ( - // <> - // - // - // - // ); - // }, - // meta: { - // headerClassName: 'w-[100px] text-center', - // cellClassName: 'text-center' - // } - // } ], - [handleEditDialog, handleDeleteDialog] + [handleEditDialog, handleDeleteDialog, handleDetailDialog] ); const getfeedbackLists = async (page: number, limit: number, sorting: any, filter: any) => { @@ -167,7 +176,6 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode } }; - return ( + + {/* Feedback Detail Modal Component */} + handleDetailDialog(show, show ? selectedfeedback : null)} + feedbackId={selectedfeedback} + /> { createMember.pin = 'admin'; delete createMember.password; delete createMember.try_pin; - delete createMember.license_number; delete createMember.isneedapproval; delete createMember.isapproved; delete createMember.approveddate; diff --git a/src/pages/members/manage-members/blocks/DetailMember.tsx b/src/pages/members/manage-members/blocks/DetailMember.tsx index 1ba2005..aa0a951 100644 --- a/src/pages/members/manage-members/blocks/DetailMember.tsx +++ b/src/pages/members/manage-members/blocks/DetailMember.tsx @@ -70,7 +70,8 @@ const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialDa setFormData({ ...formData, [name]: value }); } else { if (name === 'municipio_id' || name === 'posto_adms_id' || name === 'suco_id') await getMasterAfter(name, value); - setFormData({ ...formData, [name]: value }); + if (name==='msisdn') setFormData({ ...formData, [name]: value.replace(/\D/g, '') }) + else setFormData({ ...formData, [name]: value }); } }; diff --git a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx index 8968cd2..2371696 100644 --- a/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ApprovalDialog.tsx @@ -20,11 +20,13 @@ import { import { doSaveLogActivity } from '@/actions/GlobalActions'; import { toast } from 'sonner'; import { Input } from '@/components/ui/input'; +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; const API_URL = apiConfig.transaction; const ApprovalDialog = () => { const { GetData, PostData } = useCallApi(); + // const { table, reload } = useDataGrid(); const { showApprovalDialog, @@ -83,6 +85,7 @@ const ApprovalDialog = () => { }; doSaveLogActivity(createActivity); setShowApprovalDialog(false); + // reload(); } else { setAlert({ show: true, message: response?.message }); } diff --git a/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx b/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx index d5b36ea..2bf79e0 100644 --- a/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx +++ b/src/pages/transaction/approval-transaction/blocks/ListToolbar.tsx @@ -3,12 +3,30 @@ import { useTransactionContext } from '../hooks/useApprovalTransactionContext'; import { Button } from '@/components/ui/button'; import { useCallback, useState, useEffect } from 'react'; import { toast } from 'sonner'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; const ListToolbar = () => { const { table, reload } = useDataGrid(); // Set the initial state for trxDate const [trxDate, settrxDate] = useState({ from: '', to: '' }); + const [statusApproval, setStatusApproval] = useState( + (table.getColumn('status_approve')?.getFilterValue() as string) ?? '' + ); + + useEffect(() => { + const timer = setTimeout(() => { + table.getColumn('status_approve')?.setFilterValue(statusApproval); + table.setPageIndex(0); + }, 200); + return () => clearTimeout(timer); + }, [statusApproval, table]); // Function to format date to YYYY-MM-DD const formatDate = (date: Date): string => { @@ -83,6 +101,22 @@ const ListToolbar = () => { /> + +
); - }; export default ListToolbar; diff --git a/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx b/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx index 6ac3b2e..ba57c71 100644 --- a/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx +++ b/src/pages/transaction/approval-transaction/hooks/ApprovalTransactionContext.tsx @@ -250,7 +250,7 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } let startdate = firstDayOfMonth.toISOString().split('T')[0]; let enddate = today.toISOString().split('T')[0]; let transactioncode = ''; - // let transactionstatus = ''; + let approvalstatus = ''; if (Array.isArray(filter)) { filter.forEach((f: any) => { @@ -264,35 +264,37 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } transactioncode = f.value; } - // if (f.id === 'status' && f.value) { - // transactionstatus = f.value; - // } + if (f.id === 'status_approve' && f.value) { + approvalstatus = f.value; + } }); } - const formattedFilter = { + // Constructing filter object with dynamic properties + const formattedFilter: any = { "Transactions.transaction_date": { from: `${startdate} 00:00:00`, - to: `${enddate} 23:59:59` + to: `${enddate} 23:59:59`, }, - "Transactions.code": { - like: `%${transactioncode}%` - }, - - "Transactions.status_approve": { in: ["W", "Y", "N"] }, - // "Transactions.status": { - // like: `%${transactionstatus}%` - // } }; + if (transactioncode) { + formattedFilter["Transactions.code"] = { like: `%${transactioncode}%` }; + } + // Handle approval status filter + formattedFilter["Transactions.status_approve"] = approvalstatus + ? approvalstatus + : { in: ["W", "Y", "N"] }; + + // Fetch data from API const response = await GetData(`${API_URL}/transaction/history`, { limit, page: page + 1, with_deleted: false, order_field: "Transactions.created_at", order_direction: 'DESC', - filter: JSON.stringify(formattedFilter) + filter: JSON.stringify(formattedFilter), }); if (!response || !response.data) { @@ -300,12 +302,12 @@ const ApprovalTransactionProvider = ({ children }: { children: React.ReactNode } return { data: [], totalCount: 0 }; } - setTransaction(response.data.list); + setTransaction(response.data.list); // Assuming this is a state setter return { data: response.data.list, totalCount: response.data.total_count }; } catch (error) { console.error('Error fetching transaction', error); - return { data: [], totalCount: 0 }; // optional: fail-safe fallback + return { data: [], totalCount: 0 }; // Fail-safe fallback } }; diff --git a/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx b/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx index 9138911..49bdb4a 100644 --- a/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx +++ b/src/pages/transaction/history-transaction/blocks/ListToolbar.tsx @@ -3,30 +3,30 @@ import { useTransactionContext } from '../hooks/useTransactionContext'; import { Button } from '@/components/ui/button'; import { useCallback, useState, useEffect } from 'react'; import { toast } from 'sonner'; -// import { -// Select, -// SelectContent, -// SelectItem, -// SelectTrigger, -// SelectValue, -// } from '@/components/ui/select'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; const ListToolbar = () => { const { table, reload } = useDataGrid(); - // Set the initial state for trxDate const [trxDate, settrxDate] = useState({ from: '', to: '' }); - const [searchValue, setSearchValue] = useState( (table.getColumn('code')?.getFilterValue() as string) ?? '' ); - // const [statusValue, setStatusValue] = useState( // (table.getColumn('status')?.getFilterValue() as string) ?? '' // ); - // Function to format date to YYYY-MM-DD + const [typeValue, setTypeValue] = useState( + (table.getState().columnFilters.find(f => f.id === 'kind')?.value as string) ?? '' + ); + const formatDate = (date: Date): string => { return date.toISOString().split('T')[0]; }; @@ -48,9 +48,17 @@ const ListToolbar = () => { // return () => clearTimeout(timer); // }, [statusValue, table]); + useEffect(() => { + const timer = setTimeout(() => { + table.setColumnFilters((prev) => [ + ...prev.filter((f) => f.id !== 'kind'), + { id: 'kind', value: typeValue }, + ]); + table.setPageIndex(0); + }, 200); + return () => clearTimeout(timer); + }, [typeValue, table]); - - // useEffect to set the default date values useEffect(() => { const today = new Date(); const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); @@ -111,7 +119,7 @@ const ListToolbar = () => { setStatusValue(value); }} > - + @@ -121,6 +129,24 @@ const ListToolbar = () => { */} + + -
@@ -139,20 +164,27 @@ const ListToolbar = () => { variant="outline" className="h-7.5" onClick={() => { + // Preserve trxDate values (from, to) and reset others const today = new Date(); const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + // Only reset filters excluding from and to setSearchValue(''); // setStatusValue(''); + setTypeValue(''); settrxDate({ from: formatDate(firstDayOfMonth), to: formatDate(today), }); - table.getColumn('code')?.setFilterValue(''); - // table.getColumn('status')?.setFilterValue(''); - table.setPageIndex(0); + // Reset other filters except for date range + table.setColumnFilters([ + { id: 'code', value: '' }, + // { id: 'status', value: '' }, + { id: 'kind', value: '' }, + ]); + table.setPageIndex(0); reload(); }} > diff --git a/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx b/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx index 52409ad..569e266 100644 --- a/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/transaction/history-transaction/hooks/TransactionContext.tsx @@ -61,6 +61,25 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { headerClassName: 'w-[250px]' } }, + { + accessorFn: (row) => { + switch (row.kind) { + case 'T': return 'TRANSFER'; + case 'P': return 'PURCHASE'; + case 'W': return 'WITHDRAW'; + case 'U': return 'TOP UP'; + case 'R': return 'RETURN'; + default: return '_'; + } + }, + accessorKey: 'kind', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, { accessorKey: 'transaction_date', header: ({ column }) => , @@ -220,10 +239,11 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { let enddate = today.toISOString().split('T')[0]; let transactioncode = ''; // let transactionstatus = ''; + let type = '' if (Array.isArray(filter)) { + // console.log(filter); filter.forEach((f: any) => { - // console.log(f.id); if (f.id === 'transaction_date' && f.value?.from && f.value?.to) { startdate = f.value.from; enddate = f.value.to; @@ -236,22 +256,34 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { // if (f.id === 'status' && f.value) { // transactionstatus = f.value; // } + + if (f.id === 'kind' && f.value) { + type = f.value; + } }); } - const formattedFilter = { + // Build filter object dynamically + const formattedFilter: any = { "Transactions.transaction_date": { from: `${startdate} 00:00:00`, to: `${enddate} 23:59:59` - }, - "Transactions.code": { - like: `%${transactioncode}%` - }, - // "Transactions.status": { - // like: `%${transactionstatus}%` - // } + } }; + if (transactioncode) { + formattedFilter["Transactions.code"] = { + like: `%${transactioncode}%` + }; + } + + // if (transactionstatus) { + // formattedFilter["Transactions.status"] = transactionstatus; + // } + + if (type) { + formattedFilter["Transactions.kind"] = type; + } const response = await GetData(`${API_URL}/transaction/history`, { limit, @@ -272,7 +304,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { } catch (error) { console.error('Error fetching transaction', error); - return { data: [], totalCount: 0 }; // optional: fail-safe fallback + return { data: [], totalCount: 0 }; } }; diff --git a/src/pages/transfer/transfertype/blocks/AddDialog.tsx b/src/pages/transfer/transfertype/blocks/AddDialog.tsx index 581ba03..799437e 100644 --- a/src/pages/transfer/transfertype/blocks/AddDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/AddDialog.tsx @@ -471,7 +471,9 @@ const AddDialog = () => { Top Up Master Agent Top Up Agent Purchase Loja - + Disbursment Escrow + Disbursment Master Agent + Disbursment Agent
diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index 28e2296..a6070bf 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -605,6 +605,9 @@ const EditDialog = () => { Top Up Master Agent Top Up Agent Purchase Loja + Disbursment Escrow + Disbursment Master Agent + Disbursment Agent
diff --git a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx index a21febf..cae8c34 100644 --- a/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx +++ b/src/pages/transfer/transfertype/hooks/ManageTransferTypeContext.tsx @@ -80,7 +80,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React const [selectedTransferType, setSelectedTransferType] = useState(null); const [transferType, setTransferType] = useState(null); const [searchTerm, setSearchTerm] = useState(''); - + const debouncedSearchTerm = useDebounce(searchTerm, 200); const handleEditDialog = useCallback((show: boolean, selected_transfertype: string | null) => { @@ -183,10 +183,13 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React AM: 'Return Agent Merchant', AE: 'Return Agent Emoney', R: 'Reward Point', - TE:'Top Up Escrow', - TM:'Top Up Master Agent', - TA:'Top Up Agent', - PL:'Purchase Loja' + TE: 'Top Up Escrow', + TM: 'Top Up Master Agent', + TA: 'Top Up Agent', + PL: 'Purchase Loja', + DE: 'Disbursment Escrow', + DM: 'Disbursment Master Agent', + DA: 'Disbursment Agent' }; return mapping[row.type] || 'Unknown'; @@ -256,7 +259,7 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React const orderDirection = sorting.length > 0 ? (sorting[0].desc ? 'DESC' : 'ASC') : 'DESC'; const searchFilter = debouncedSearchTerm ? { any: debouncedSearchTerm.toLowerCase() } : {}; - + filter = filter.length == 0 ? searchFilter : { any: filter[0].value?.toLowerCase() }; const response = await GetData(`${API_URL}/transactiontype/list`, { @@ -310,4 +313,4 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React }; export { ManageTransferTypeContext, ManageTransferTypeContextProvider }; -export type { TransferType }; \ No newline at end of file +export type { TransferType };