'use client'; import { CreditDetailViewer } from '@/components/settings/credits/credit-detail-viewer'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'; import { CREDIT_TRANSACTION_TYPE } from '@/credits/types'; import { formatDate } from '@/lib/formatter'; import { CaretDownIcon, CaretUpIcon } from '@radix-ui/react-icons'; import { IconCaretDownFilled, IconCaretUpFilled, IconSortAscending2, } from '@tabler/icons-react'; import { type ColumnDef, type ColumnFiltersState, type SortingState, type VisibilityState, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table'; import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, BanknoteIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, ClockIcon, CoinsIcon, GemIcon, GiftIcon, HandCoinsIcon, ShoppingCartIcon, } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useState } from 'react'; import { toast } from 'sonner'; import { Badge } from '../../ui/badge'; import { Label } from '../../ui/label'; import { Skeleton } from '../../ui/skeleton'; // Define the credit transaction interface export interface CreditTransaction { id: string; userId: string; type: string; description: string | null; amount: number; remainingAmount: number | null; paymentId: string | null; expirationDate: Date | null; expirationDateProcessedAt: Date | null; createdAt: Date; updatedAt: Date; } interface DataTableColumnHeaderProps extends React.HTMLAttributes { column: any; title: string; } function DataTableColumnHeader({ column, title, className, }: DataTableColumnHeaderProps) { const tTable = useTranslations('Common.table'); // Only show dropdown for sortable columns if (!column.getCanSort()) { return
{title}
; } // Determine current sort state const isSorted = column.getIsSorted(); // 'asc' | 'desc' | false return (
{ if (value === 'asc') column.toggleSorting(false); else if (value === 'desc') column.toggleSorting(true); }} > {tTable('ascending')} {tTable('descending')}
); } function TableRowSkeleton({ columns }: { columns: number }) { return ( {Array.from({ length: columns }).map((_, index) => (
))}
); } interface CreditTransactionsTableProps { data: CreditTransaction[]; total: number; pageIndex: number; pageSize: number; search: string; sorting?: SortingState; loading?: boolean; onSearch: (search: string) => void; onPageChange: (page: number) => void; onPageSizeChange: (size: number) => void; onSortingChange?: (sorting: SortingState) => void; } export function CreditTransactionsTable({ data, total, pageIndex, pageSize, search, sorting = [{ id: 'createdAt', desc: true }], loading, onSearch, onPageChange, onPageSizeChange, onSortingChange, }: CreditTransactionsTableProps) { const t = useTranslations('Dashboard.settings.credits.transactions'); const tTable = useTranslations('Common.table'); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); // show fake data in demo website const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true'; // Map column IDs to translation keys const columnIdToTranslationKey = { type: 'columns.type' as const, amount: 'columns.amount' as const, remainingAmount: 'columns.remainingAmount' as const, description: 'columns.description' as const, paymentId: 'columns.paymentId' as const, expirationDate: 'columns.expirationDate' as const, expirationDateProcessedAt: 'columns.expirationDateProcessedAt' as const, createdAt: 'columns.createdAt' as const, updatedAt: 'columns.updatedAt' as const, } as const; // Get transaction type icon const getTransactionTypeIcon = (type: string) => { switch (type) { case CREDIT_TRANSACTION_TYPE.MONTHLY_REFRESH: return ; case CREDIT_TRANSACTION_TYPE.REGISTER_GIFT: return ; case CREDIT_TRANSACTION_TYPE.PURCHASE_PACKAGE: return ; case CREDIT_TRANSACTION_TYPE.USAGE: return ; case CREDIT_TRANSACTION_TYPE.EXPIRE: return ; case CREDIT_TRANSACTION_TYPE.SUBSCRIPTION_RENEWAL: return ; case CREDIT_TRANSACTION_TYPE.LIFETIME_MONTHLY: return ; default: return null; } }; // Get transaction type display name const getTransactionTypeDisplayName = (type: string) => { switch (type) { case CREDIT_TRANSACTION_TYPE.MONTHLY_REFRESH: return t('types.MONTHLY_REFRESH'); case CREDIT_TRANSACTION_TYPE.REGISTER_GIFT: return t('types.REGISTER_GIFT'); case CREDIT_TRANSACTION_TYPE.PURCHASE_PACKAGE: return t('types.PURCHASE'); case CREDIT_TRANSACTION_TYPE.USAGE: return t('types.USAGE'); case CREDIT_TRANSACTION_TYPE.EXPIRE: return t('types.EXPIRE'); case CREDIT_TRANSACTION_TYPE.SUBSCRIPTION_RENEWAL: return t('types.SUBSCRIPTION_RENEWAL'); case CREDIT_TRANSACTION_TYPE.LIFETIME_MONTHLY: return t('types.LIFETIME_MONTHLY'); default: return type; } }; // Table columns definition const columns: ColumnDef[] = [ { accessorKey: 'type', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{getTransactionTypeIcon(transaction.type)} {getTransactionTypeDisplayName(transaction.type)}
); }, }, { accessorKey: 'amount', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return ; }, }, { accessorKey: 'remainingAmount', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{transaction.remainingAmount !== null ? ( {transaction.remainingAmount.toLocaleString()} ) : ( - )}
); }, }, { accessorKey: 'description', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{transaction.description ? ( {transaction.description}

{transaction.description}

) : ( - )}
); }, }, { accessorKey: 'paymentId', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{transaction.paymentId ? ( { navigator.clipboard.writeText(transaction.paymentId!); toast.success(t('paymentIdCopied')); }} > {transaction.paymentId} ) : ( - )}
); }, }, { accessorKey: 'expirationDate', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{transaction.expirationDate ? ( {formatDate(transaction.expirationDate)} ) : ( - )}
); }, }, { accessorKey: 'expirationDateProcessedAt', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{transaction.expirationDateProcessedAt ? ( {formatDate(transaction.expirationDateProcessedAt)} ) : ( - )}
); }, }, { accessorKey: 'createdAt', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{formatDate(transaction.createdAt)}
); }, }, { accessorKey: 'updatedAt', header: ({ column }) => ( ), cell: ({ row }) => { const transaction = row.original; return (
{formatDate(transaction.updatedAt)}
); }, }, ]; const table = useReactTable({ data, columns, pageCount: Math.ceil(total / pageSize), state: { sorting, columnFilters, columnVisibility, pagination: { pageIndex, pageSize }, }, onSortingChange: (updater) => { const next = typeof updater === 'function' ? updater(sorting) : updater; onSortingChange?.(next); }, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, onPaginationChange: (updater) => { const next = typeof updater === 'function' ? updater({ pageIndex, pageSize }) : updater; if (next.pageIndex !== pageIndex) onPageChange(next.pageIndex); if (next.pageSize !== pageSize) onPageSizeChange(next.pageSize); }, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), manualPagination: true, manualSorting: true, }); return (
{ onSearch(event.target.value); onPageChange(0); }} className="max-w-sm" />
{table .getAllColumns() .filter((column) => column.getCanHide()) .map((column) => { return ( column.toggleVisibility(!!value) } > {t( columnIdToTranslationKey[ column.id as keyof typeof columnIdToTranslationKey ] || 'columns.columns' )} ); })}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} ); })} ))} {loading ? ( // Show skeleton rows while loading Array.from({ length: pageSize }).map((_, index) => ( )) ) : table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} )) ) : ( {tTable('noResults')} )}
{total > 0 && {tTable('totalRecords', { count: total })}}
{tTable('page')} {pageIndex + 1} {' / '} {Math.max(1, Math.ceil(total / pageSize))}
); }