adding new module named "Cards"

This commit is contained in:
bagusajisaputroo
2025-06-10 15:02:17 +07:00
parent 4a56209c58
commit e57c6af2ff
9 changed files with 940 additions and 1 deletions

View File

@ -10,8 +10,9 @@ interface apiConfigProps {
service_disbursement: string; service_disbursement: string;
service_notification: string; service_notification: string;
service_feedback: string; service_feedback: string;
api_dashboard: string;
service_card: string; service_card: string;
api_dashboard:string
} }
const API_URL = import.meta.env.VITE_APP_API_URL; const API_URL = import.meta.env.VITE_APP_API_URL;

View File

@ -0,0 +1,50 @@
import { Container, DataGridInner } from '@/components';
// import {
// // ManageTransferTypeContext,
// // ManageTransferTypeContextProvider
// // } from './hooks/ManageTransferTypeContext';
// // import AddDialog from './blocks/AddDialog';
import { Breadcrumbs, Link } from '@mui/material';
// import DeleteDialog from './blocks/DeleteDialog';
// import { EditDialog } from './blocks/EditDialog';
import { Helmet } from 'react-helmet';
import { ManageHistoryCardsProvider } from './hooks/ManageHistoryCard';
// import { ManageCardsContext, ManageCardsProvider } from './hooks/ManageCardContext';
// import RevokeDialog from './blocks/RevokeDialog';
const HistoryCard = () => {
return (
<>
<Helmet>
<title>TPAY | Card History</title>
</Helmet>
<ManageHistoryCardsProvider>
<Container>
<h1 className="text-xl font-medium leading-none text-gray-900 mb-3">History Cards</h1>
<Breadcrumbs sx={{ mb: 2 }}>
<Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">History Cards</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">History Cards</span>
</Link>
</Breadcrumbs>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
{/* <RevokeDialog /> */}
{/* <AddDialog />
<DeleteDialog />
<EditDialog /> */}
</Container>
</ManageHistoryCardsProvider>
</>
);
};
export default HistoryCard;

View File

@ -0,0 +1,267 @@
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 { Button } from '@/components/ui/button';
import { useNavigate } from 'react-router';
interface CardProps {
id: number;
serial_number: string;
provisioning_date: string;
customer: CustomerProps;
}
interface CustomerProps {
fullname: string;
msisdn: string;
}
interface ContextProps {
cards: CardProps[];
showSearchDialog: boolean;
handleSearchDialog: (show: boolean) => void;
showRevokeDialog: boolean;
handleRevokeDialog: (show: boolean, selected_card: string | null) => void;
showAddDialog: boolean;
handleAddDialog: (show: boolean) => void;
showDeleteDialog: boolean;
handleDeleteDialog: (show: boolean, selected_card: string | null) => void;
selectedCard: string | null;
getCardsLists: (
limit: number,
page: number,
with_deleted: boolean,
order_field: any,
order_direction: any
) => Promise<{ data: CardProps[]; totalCount: number } | undefined>;
}
const initialProps: ContextProps = {
cards: [],
showSearchDialog: false,
handleSearchDialog: (show: boolean) => {},
showRevokeDialog: false,
handleRevokeDialog: (show: boolean, selected_card: string | null) => {},
showAddDialog: false,
handleAddDialog: (show: boolean) => {},
showDeleteDialog: false,
handleDeleteDialog: (show: boolean, selected_card: string | null) => {},
selectedCard: null,
getCardsLists: async () => ({ data: [], totalCount: 0 })
};
const ManageHistoryCardsContext = createContext<ContextProps>(initialProps);
const API_URL = apiConfig.service_master_data;
const API_URL_CARD = apiConfig.service_card;
const ManageHistoryCardsProvider = ({ children }: { children: React.ReactNode }) => {
const [showSearchDialog, setShowSearchDialog] = useState(false);
const [showRevokeDialog, setShowRevokeDialog] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedCard, setSelectedCard] = useState<string | null>(null);
const [cards, setCards] = useState<CardProps[]>([]);
const { GetData } = useCallApi();
const handleSearchDialog = useCallback((show: boolean) => {
setShowSearchDialog(show);
}, []);
const handleAddDialog = useCallback((show: boolean) => {
setShowAddDialog(show);
}, []);
const handleRevokeDialog = useCallback((show: boolean, selected_card: string | null) => {
setSelectedCard(show ? selected_card : null);
setShowRevokeDialog(show);
}, []);
const handleDeleteDialog = useCallback((show: boolean, selected_card: string | null) => {
setSelectedCard(show ? selected_card : null);
setShowDeleteDialog(show);
}, []);
const columns = useMemo<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
accessorKey: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.serial_number,
accessorKey: 'serial_number',
header: ({ column }) => <DataGridColumnHeader title="Serial Number" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.serial_number,
accessorKey: 'serial_number',
header: ({ column }) => <DataGridColumnHeader title="Serial Number" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.action,
accessorKey: 'action',
header: ({ column }) => <DataGridColumnHeader title="Action" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => {
const date = new Date(row.created_at);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}, ${hours}.${minutes}`;
},
accessorKey: 'created_at',
header: ({ column }) => <DataGridColumnHeader title="Created Date" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
}
// {
// id: 'actionss',
// header: ({ column }) => <DataGridColumnHeader title="" column={column} />,
// enableSorting: false,
// enableHiding: false,
// cell: (data) => {
// const row = data.row.original;
// return (
// <>
// <button
// className="btn btn-sm btn-icon btn-clear btn-light"
// onClick={() => handleRevokeDialog(true, row.id.toString())}
// title="Revoke Card"
// >
// <KeenIcon icon="notepad-edit" />
// </button>
// {/* <button
// className="btn btn-sm btn-icon btn-clear btn-light"
// onClick={() => handleDeleteDialog(true, row.id.toString())}
// title="Delete Card"
// >
// <KeenIcon icon="trash" />
// </button> */}
// </>
// );
// },
// meta: {
// headerClassName: 'w-[100px] text-center',
// cellClassName: 'text-center'
// }
// }
],
[handleRevokeDialog, handleDeleteDialog]
);
// Fixed function signature to match the interface
const getCardsLists = useCallback(async (
limit: number,
page: number,
with_deleted: boolean,
order_field: any,
order_direction: any
) => {
try {
const sorting = order_field ? [{ id: order_field, desc: order_direction === 'ASC' }] : [{ id: 'created_at', desc: false }];
const response = await GetData(`${API_URL_CARD}/card/list-history`, {
limit,
page: page + 1,
with_deleted,
order_field,
order_direction,
filter: JSON.stringify({})
});
setCards(response?.data.list || []);
return { data: response?.data.list || [], totalCount: response?.data.total_count || 0 };
} catch (error) {
console.error('Error fetching cards', error);
return { data: [], totalCount: 0 };
}
}, [GetData]);
// Internal function for DataGrid
const getCardsListsForDataGrid = useCallback(async (page: number, limit: number, sorting: any, filter: any) => {
try {
const sortingArray = sorting.length === 0 ? [{ id: 'created_at', desc: false }] : sorting;
const filterObj = filter.length === 0 ? {} : { any: filter[0].value?.toLowerCase() };
const response = await GetData(`${API_URL_CARD}/card/list-history`, {
limit,
page: page + 1,
with_deleted: false,
order_field: sortingArray[0].id,
order_direction: sortingArray[0].desc === false ? 'DESC' : 'ASC',
// filter: JSON.stringify(filterObj)
});
setCards(response?.data.list || []);
return { data: response?.data.list || [], totalCount: response?.data.total_count || 0 };
} catch (error) {
console.error('Error fetching cards', error);
return { data: [], totalCount: 0 };
}
}, [GetData]);
return (
<ManageHistoryCardsContext.Provider
value={{
cards,
showSearchDialog,
handleSearchDialog,
showAddDialog,
handleAddDialog,
showDeleteDialog,
handleDeleteDialog,
showRevokeDialog,
handleRevokeDialog,
selectedCard,
getCardsLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
layout={{ card: true }}
sorting={[{ id: 'created_at', desc: true }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getCardsListsForDataGrid(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManageHistoryCardsContext.Provider>
);
};
export { ManageHistoryCardsProvider, ManageHistoryCardsContext };
export type { CardProps };

View File

@ -0,0 +1,14 @@
import { useContext } from 'react';
import { ManageHistoryCardsContext } from './ManageHistoryCard';
// import { ManageCardsContext } from './ManageCardContext';
const useManageHistoryCardsContext = () => {
const context = useContext(ManageHistoryCardsContext);
if (!context) throw new Error('useManageHistoryCardsContext must be used within AuthProvider');
return context;
};
export { useManageHistoryCardsContext };

View File

@ -0,0 +1,51 @@
import { Container, DataGridInner } from '@/components';
// import {
// // ManageTransferTypeContext,
// // ManageTransferTypeContextProvider
// // } from './hooks/ManageTransferTypeContext';
// // import AddDialog from './blocks/AddDialog';
import { Breadcrumbs, Link } from '@mui/material';
// import DeleteDialog from './blocks/DeleteDialog';
// import { EditDialog } from './blocks/EditDialog';
import { Helmet } from 'react-helmet';
import { ManageCardsContext, ManageCardsProvider } from './hooks/ManageCardContext';
import RevokeDialog from './blocks/RevokeDialog';
const ManageCards = () => {
return (
<>
<Helmet>
<title>TPAY | Manage Cards</title>
</Helmet>
<ManageCardsProvider>
<Container>
<h1 className="text-xl font-medium leading-none text-gray-900 mb-3">Manage Cards</h1>
<Breadcrumbs sx={{ mb: 2 }}>
<Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Manage Cards</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Manage Cards</span>
</Link>
</Breadcrumbs>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<RevokeDialog/>
{/* <AddDialog />
<DeleteDialog />
<EditDialog /> */}
</Container>
</ManageCardsProvider>
</>
);
};
export default ManageCards;

View File

@ -0,0 +1,273 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Alert, useDataGrid } from '@/components';
import axios from 'axios';
import { apiConfig } from '@/config/api.config';
import { toast } from 'sonner';
import {
Dialog,
DialogBody,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { getAuth, useAuthContext } from '@/auth';
import { useCallApi } from '@/hooks';
import { doSaveLogActivity } from '@/actions/GlobalActions';
import { RefreshCw, AlertTriangle } from 'lucide-react';
import { useManageCardsContext } from '../hooks/useManageCardContext';
const API_URL = apiConfig.service_card;
const RevokeDialog = () => {
const { showRevokeDialog, handleRevokeDialog, selectedCard, cards } = useManageCardsContext();
const { reload } = useDataGrid();
const { PostData, PutData, GetData } = useCallApi();
const parsedUser = getAuth()?.user;
const [isLoading, setIsLoading] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [alert, setAlert] = useState({
show: false,
message: ''
});
const [cardDetails, setCardDetails] = useState({
id: '',
serial_number: '',
customer_fullname: '',
customer_msisdn: '',
provisioning_date: ''
});
const [errors, setErrors] = useState<Record<string, string>>({});
console.log('selectedCard', selectedCard);
const resetForm = () => {
setCardDetails({
id: '',
serial_number: '',
customer_fullname: '',
customer_msisdn: '',
provisioning_date: ''
});
setErrors({});
setAlert({ show: false, message: '' });
};
const doRevokeCard = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
try {
const response = await PostData(`${API_URL}/card/revoke`, {
id: selectedCard
});
if (response?.status) {
handleRevokeDialog(false, null);
resetForm();
toast.success('Card has been revoked successfully');
reload();
const createActivity = {
module: 'Manage Card',
description: `Revoke Card => ID: ${selectedCard}, Serial: ${cardDetails.serial_number}`,
action: 'C'
};
doSaveLogActivity(createActivity);
} else {
toast.error('Failed to revoke card');
setAlert({ show: true, message: response?.message || 'Failed to revoke card' });
}
} catch (error: any) {
console.error('Error revoking card:', error);
toast.error('Something went wrong, please try again.');
setAlert({
show: true,
message: error?.response?.data?.message || 'Something went wrong, please try again.'
});
} finally {
setIsSubmitting(false);
}
},
[selectedCard, cardDetails.serial_number, PostData, handleRevokeDialog, reload]
);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doRevokeCard(e);
};
const doFetchCardData = useCallback(
async (id: string) => {
setIsLoading(true);
try {
// Find card details from the cards context or fetch from API
const card = cards.find((c) => c.id.toString() === id);
if (card) {
setCardDetails({
id: card.id.toString(),
serial_number: card.serial_number,
customer_fullname: card.customer.fullname,
customer_msisdn: card.customer.msisdn,
provisioning_date: new Date(card.provisioning_date).toLocaleDateString()
});
} else {
// If not found in context, fetch from API
const response = await GetData(`${API_URL}/card/getdata/${id}`, { id });
if (response?.status && response.data) {
const cardData = response.data;
setCardDetails({
id: cardData.id.toString(),
serial_number: cardData.serial_number,
customer_fullname: cardData.customer?.fullname || '',
customer_msisdn: cardData.customer?.msisdn || '',
provisioning_date: new Date(cardData.provisioning_date).toLocaleDateString()
});
} else {
setAlert({ show: true, message: 'Failed to load card details' });
}
}
} catch (error) {
console.error('Error fetching card data:', error);
setAlert({ show: true, message: 'Failed to load card details' });
} finally {
setIsLoading(false);
}
},
[cards, GetData]
);
useEffect(() => {
if (selectedCard && showRevokeDialog) {
doFetchCardData(selectedCard);
}
}, [selectedCard, showRevokeDialog, doFetchCardData]);
useEffect(() => {
if (showRevokeDialog === false) {
resetForm();
}
}, [showRevokeDialog]);
return (
<Dialog open={showRevokeDialog} onOpenChange={(open) => handleRevokeDialog(open, null)}>
<DialogContent className="container-fixed max-w-[600px] flex flex-col p-5 overflow-hidden">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-orange-500" />
Revoke Card
</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<DialogBody>
<div className="flex flex-col">
{alert.show && (
<Alert variant="danger" className="mb-4">
<h3>{alert.message}</h3>
</Alert>
)}
{isLoading ? (
<div className="flex flex-col items-center justify-center p-8">
<div className="animate-pulse flex space-x-4 w-full">
<div className="flex-1 space-y-4 py-1">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="space-y-2">
<div className="h-4 bg-gray-200 rounded"></div>
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
</div>
</div>
<p className="mt-4 text-gray-500">Loading Card Details...</p>
</div>
) : (
<form onSubmit={handleSubmit}>
<div className="card-body grid gap-5">
{/* Card Details Display
<div className="bg-gray-50 p-4 rounded-lg border">
<h4 className="font-semibold text-gray-800 mb-3">Card Information</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
<div>
<span className="font-medium text-gray-600">Card ID:</span>
<p className="text-gray-800">{cardDetails.id}</p>
</div>
<div>
<span className="font-medium text-gray-600">Serial Number:</span>
<p className="text-gray-800">{cardDetails.serial_number}</p>
</div>
<div>
<span className="font-medium text-gray-600">Customer Name:</span>
<p className="text-gray-800">{cardDetails.customer_fullname}</p>
</div>
<div>
<span className="font-medium text-gray-600">Customer MSISDN:</span>
<p className="text-gray-800">{cardDetails.customer_msisdn}</p>
</div>
<div className="md:col-span-2">
<span className="font-medium text-gray-600">Provisioning Date:</span>
<p className="text-gray-800">{cardDetails.provisioning_date}</p>
</div>
</div>
</div> */}
<h1 className='font-bold text-gray-800'> Are you sure you want to revoke this card? This action cannot be undone.</h1>
<p className="text-red-600 text-sm mt-1">
Revoking this card will deactivate it permanently. The customer will no longer
be able to use this card for any transactions or services.
</p>
{/* Warning Message */}
{/* <div className="bg-orange-50 border border-orange-200 p-4 rounded-lg">
<div className="flex items-start gap-3">
<AlertTriangle className="h-5 w-5 text-orange-500 mt-0.5 flex-shrink-0" />
<div className="">
<h5 className="font-medium text-orange-800">Warning</h5>
<p className="text-orange-700 text-sm mt-1">
Revoking this card will deactivate it permanently. The customer will no
longer be able to use this card for any transactions or services.
</p>
</div>
</div>
</div> */}
{/* Action Buttons */}
<div className="flex justify-end gap-3 pt-2.5">
<Button
variant="outline"
type="button"
onClick={() => handleRevokeDialog(false, null)}
disabled={isSubmitting}
>
Cancel
</Button>
<Button
variant="destructive"
type="submit"
disabled={isSubmitting}
className="bg-red-600 hover:bg-red-700"
>
{isSubmitting ? (
<>
<RefreshCw className="animate-spin h-4 w-4 mr-2" />
Revoking...
</>
) : (
'Revoke Card'
)}
</Button>
</div>
</div>
</form>
)}
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default RevokeDialog;

View File

@ -0,0 +1,264 @@
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 { Button } from '@/components/ui/button';
import { useNavigate } from 'react-router';
interface CardProps {
id: number;
serial_number: string;
provisioning_date: string;
customer: CustomerProps;
}
interface CustomerProps {
fullname: string;
msisdn: string;
}
interface ContextProps {
cards: CardProps[];
showSearchDialog: boolean;
handleSearchDialog: (show: boolean) => void;
showRevokeDialog: boolean;
handleRevokeDialog: (show: boolean, selected_card: string | null) => void;
showAddDialog: boolean;
handleAddDialog: (show: boolean) => void;
showDeleteDialog: boolean;
handleDeleteDialog: (show: boolean, selected_card: string | null) => void;
selectedCard: string | null;
getCardsLists: (
limit: number,
page: number,
with_deleted: boolean,
order_field: any,
order_direction: any
) => Promise<{ data: CardProps[]; totalCount: number } | undefined>;
}
const initialProps: ContextProps = {
cards: [],
showSearchDialog: false,
handleSearchDialog: (show: boolean) => {},
showRevokeDialog: false,
handleRevokeDialog: (show: boolean, selected_card: string | null) => {},
showAddDialog: false,
handleAddDialog: (show: boolean) => {},
showDeleteDialog: false,
handleDeleteDialog: (show: boolean, selected_card: string | null) => {},
selectedCard: null,
getCardsLists: async () => ({ data: [], totalCount: 0 })
};
const ManageCardsContext = createContext<ContextProps>(initialProps);
const API_URL = apiConfig.service_master_data;
const API_URL_CARD = apiConfig.service_card;
const ManageCardsProvider = ({ children }: { children: React.ReactNode }) => {
const [showSearchDialog, setShowSearchDialog] = useState(false);
const [showRevokeDialog, setShowRevokeDialog] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedCard, setSelectedCard] = useState<string | null>(null);
const [cards, setCards] = useState<CardProps[]>([]);
const { GetData } = useCallApi();
const handleSearchDialog = useCallback((show: boolean) => {
setShowSearchDialog(show);
}, []);
const handleAddDialog = useCallback((show: boolean) => {
setShowAddDialog(show);
}, []);
const handleRevokeDialog = useCallback((show: boolean, selected_card: string | null) => {
setSelectedCard(show ? selected_card : null);
setShowRevokeDialog(show);
}, []);
const handleDeleteDialog = useCallback((show: boolean, selected_card: string | null) => {
setSelectedCard(show ? selected_card : null);
setShowDeleteDialog(show);
}, []);
const columns = useMemo<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
accessorKey: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.serial_number,
accessorKey: 'serial_number',
header: ({ column }) => <DataGridColumnHeader title="Serial Number" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.customer.fullname,
accessorKey: 'customer.fullname',
header: ({ column }) => <DataGridColumnHeader title="Customer Fullname" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.customer.msisdn,
accessorKey: 'customer.msisdn',
header: ({ column }) => <DataGridColumnHeader title="Customer MSISDN" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => {
const date = new Date(row.provisioning_date);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
accessorKey: 'provisioning_date',
header: ({ column }) => <DataGridColumnHeader title="Provisioning Date" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
id: 'actions',
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
enableSorting: false,
enableHiding: false,
cell: (data) => {
const row = data.row.original;
return (
<>
<button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleRevokeDialog(true, row.id.toString())}
title="Revoke Card"
>
<KeenIcon icon="notepad-edit" />
</button>
{/* <button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleDeleteDialog(true, row.id.toString())}
title="Delete Card"
>
<KeenIcon icon="trash" />
</button> */}
</>
);
},
meta: {
headerClassName: 'w-[100px] text-center',
cellClassName: 'text-center'
}
}
],
[handleRevokeDialog, handleDeleteDialog]
);
// Fixed function signature to match the interface
const getCardsLists = useCallback(async (
limit: number,
page: number,
with_deleted: boolean,
order_field: any,
order_direction: any
) => {
try {
const sorting = order_field ? [{ id: order_field, desc: order_direction === 'ASC' }] : [{ id: 'provisioning_date', desc: false }];
const response = await GetData(`${API_URL_CARD}/card/list`, {
limit,
page: page + 1,
with_deleted,
order_field,
order_direction,
filter: JSON.stringify({})
});
setCards(response?.data.list || []);
return { data: response?.data.list || [], totalCount: response?.data.total_count || 0 };
} catch (error) {
console.error('Error fetching cards', error);
return { data: [], totalCount: 0 };
}
}, [GetData]);
// Internal function for DataGrid
const getCardsListsForDataGrid = useCallback(async (page: number, limit: number, sorting: any, filter: any) => {
try {
const sortingArray = sorting.length === 0 ? [{ id: 'provisioning_date', desc: false }] : sorting;
const filterObj = filter.length === 0 ? {} : { any: filter[0].value?.toLowerCase() };
const response = await GetData(`${API_URL_CARD}/card/list`, {
limit,
page: page + 1,
with_deleted: false,
order_field: sortingArray[0].id,
order_direction: sortingArray[0].desc === false ? 'DESC' : 'ASC',
filter: JSON.stringify(filterObj)
});
setCards(response?.data.list || []);
return { data: response?.data.list || [], totalCount: response?.data.total_count || 0 };
} catch (error) {
console.error('Error fetching cards', error);
return { data: [], totalCount: 0 };
}
}, [GetData]);
return (
<ManageCardsContext.Provider
value={{
cards,
showSearchDialog,
handleSearchDialog,
showAddDialog,
handleAddDialog,
showDeleteDialog,
handleDeleteDialog,
showRevokeDialog,
handleRevokeDialog,
selectedCard,
getCardsLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
layout={{ card: true }}
sorting={[{ id: 'provisioning_date', desc: true }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getCardsListsForDataGrid(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManageCardsContext.Provider>
);
};
export { ManageCardsProvider, ManageCardsContext };
export type { CardProps };

View File

@ -0,0 +1,12 @@
import { useContext } from 'react';
import { ManageCardsContext } from './ManageCardContext';
const useManageCardsContext = () => {
const context = useContext(ManageCardsContext);
if (!context) throw new Error('useManageCardsContext must be used within AuthProvider');
return context;
};
export { useManageCardsContext };

View File

@ -51,6 +51,8 @@ import AgentBalance from '@/pages/members/agent-balance/AgentBalance';
import HistoryTransactionDisbursement from '@/pages/disbursement/history-transaction/HistoryTransaction'; import HistoryTransactionDisbursement from '@/pages/disbursement/history-transaction/HistoryTransaction';
import SearchMember from '@/pages/members/search-member/SearchMember'; import SearchMember from '@/pages/members/search-member/SearchMember';
import WithdrawalEmoney from '@/pages/transaction/withdrawl-emoney/WithdrawalEmoney'; import WithdrawalEmoney from '@/pages/transaction/withdrawl-emoney/WithdrawalEmoney';
import ManageCards from '@/pages/cards/manage-card/ManageCard';
import HistoryCard from '@/pages/cards/history-card/HistoryCard';
// DISBURSEMENT // DISBURSEMENT
@ -98,6 +100,11 @@ const AppRoutingSetup = (): ReactElement => {
<Route path="/members/search-member" element={<SearchMember />} /> <Route path="/members/search-member" element={<SearchMember />} />
<Route path="/members/search-member" element={<SearchMember />} /> <Route path="/members/search-member" element={<SearchMember />} />
{/* cards */}
<Route path="/cards/manage-card" element={<ManageCards />} />
<Route path="/cards/card-history" element={<HistoryCard />} />
<Route path="/access/access-type-management" element={<AccessType />} /> <Route path="/access/access-type-management" element={<AccessType />} />
<Route path="/accounts/account-management" element={<ManageAccount />} /> <Route path="/accounts/account-management" element={<ManageAccount />} />