diff --git a/src/actions/get-credit-transactions.ts b/src/actions/get-credit-transactions.ts
index ec40ae5..2e5b052 100644
--- a/src/actions/get-credit-transactions.ts
+++ b/src/actions/get-credit-transactions.ts
@@ -44,17 +44,30 @@ export const getCreditTransactionsAction = userActionClient
const { pageIndex, pageSize, search, sorting } = parsedInput;
const currentUser = (ctx as { user: User }).user;
- // search by type, amount, paymentId, description, and restrict to current user
+ // Search logic: text fields use ilike, and if search is a number, also search amount fields
+ const searchConditions = [];
+ if (search) {
+ // Always search text fields
+ searchConditions.push(
+ ilike(creditTransaction.type, `%${search}%`),
+ ilike(creditTransaction.paymentId, `%${search}%`),
+ ilike(creditTransaction.description, `%${search}%`)
+ );
+
+ // If search is a valid number, also search numeric fields
+ const numericSearch = Number.parseInt(search, 10);
+ if (!Number.isNaN(numericSearch)) {
+ searchConditions.push(
+ eq(creditTransaction.amount, numericSearch),
+ eq(creditTransaction.remainingAmount, numericSearch)
+ );
+ }
+ }
+
const where = search
? and(
eq(creditTransaction.userId, currentUser.id),
- or(
- ilike(creditTransaction.type, `%${search}%`),
- ilike(creditTransaction.amount, `%${search}%`),
- ilike(creditTransaction.remainingAmount, `%${search}%`),
- ilike(creditTransaction.paymentId, `%${search}%`),
- ilike(creditTransaction.description, `%${search}%`)
- )
+ or(...searchConditions)
)
: eq(creditTransaction.userId, currentUser.id);
diff --git a/src/components/admin/users-table.tsx b/src/components/admin/users-table.tsx
index baf21ee..2485f49 100644
--- a/src/components/admin/users-table.tsx
+++ b/src/components/admin/users-table.tsx
@@ -515,7 +515,7 @@ export function UsersTable({
- {[5, 10, 20, 30, 40, 50].map((pageSize) => (
+ {[10, 20, 30, 40, 50].map((pageSize) => (
{pageSize}
diff --git a/src/components/settings/credits/credit-transactions-table.tsx b/src/components/settings/credits/credit-transactions-table.tsx
index 0e1fd51..39fedfa 100644
--- a/src/components/settings/credits/credit-transactions-table.tsx
+++ b/src/components/settings/credits/credit-transactions-table.tsx
@@ -76,6 +76,7 @@ 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 {
@@ -152,12 +153,27 @@ function DataTableColumnHeader({
);
}
+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;
@@ -171,6 +187,7 @@ export function CreditTransactionsTable({
pageIndex,
pageSize,
search,
+ sorting = [{ id: 'createdAt', desc: true }],
loading,
onSearch,
onPageChange,
@@ -179,9 +196,6 @@ export function CreditTransactionsTable({
}: CreditTransactionsTableProps) {
const t = useTranslations('Dashboard.settings.credits.transactions');
const tTable = useTranslations('Common.table');
- const [sorting, setSorting] = useState([
- { id: 'createdAt', desc: true },
- ]);
const [columnFilters, setColumnFilters] = useState([]);
const [columnVisibility, setColumnVisibility] = useState({});
@@ -449,7 +463,6 @@ export function CreditTransactionsTable({
},
onSortingChange: (updater) => {
const next = typeof updater === 'function' ? updater(sorting) : updater;
- setSorting(next);
onSortingChange?.(next);
},
onColumnFiltersChange: setColumnFilters,
@@ -538,7 +551,12 @@ export function CreditTransactionsTable({
))}
- {table.getRowModel().rows?.length ? (
+ {loading ? (
+ // Show skeleton rows while loading
+ Array.from({ length: pageSize }).map((_, index) => (
+
+ ))
+ ) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
- {loading ? tTable('loading') : tTable('noResults')}
+ {tTable('noResults')}
)}
diff --git a/src/components/settings/credits/credit-transactions.tsx b/src/components/settings/credits/credit-transactions.tsx
index 608f0c9..d9b0351 100644
--- a/src/components/settings/credits/credit-transactions.tsx
+++ b/src/components/settings/credits/credit-transactions.tsx
@@ -4,22 +4,36 @@ import { CreditTransactionsTable } from '@/components/settings/credits/credit-tr
import { useCreditTransactions } from '@/hooks/use-credits';
import type { SortingState } from '@tanstack/react-table';
import { useTranslations } from 'next-intl';
-import { useState } from 'react';
+import {
+ parseAsIndex,
+ parseAsInteger,
+ parseAsString,
+ useQueryStates,
+} from 'nuqs';
+import { useMemo } from 'react';
/**
* Credit transactions component
*/
export function CreditTransactions() {
const t = useTranslations('Dashboard.settings.credits.transactions');
- const [pageIndex, setPageIndex] = useState(0);
- const [pageSize, setPageSize] = useState(10);
- const [search, setSearch] = useState('');
- const [sorting, setSorting] = useState([
- { id: 'createdAt', desc: true },
- ]);
+
+ const [{ page, pageSize, search, sortId, sortDesc }, setQueryStates] =
+ useQueryStates({
+ page: parseAsIndex.withDefault(0), // 0-based internally, 1-based in URL
+ pageSize: parseAsInteger.withDefault(10),
+ search: parseAsString.withDefault(''),
+ sortId: parseAsString.withDefault('createdAt'),
+ sortDesc: parseAsInteger.withDefault(1),
+ });
+
+ const sorting: SortingState = useMemo(
+ () => [{ id: sortId, desc: Boolean(sortDesc) }],
+ [sortId, sortDesc]
+ );
const { data, isLoading } = useCreditTransactions(
- pageIndex,
+ page,
pageSize,
search,
sorting
@@ -29,14 +43,24 @@ export function CreditTransactions() {
setQueryStates({ search: newSearch, page: 0 })}
+ onPageChange={(newPageIndex) => setQueryStates({ page: newPageIndex })}
+ onPageSizeChange={(newPageSize) =>
+ setQueryStates({ pageSize: newPageSize, page: 0 })
+ }
+ onSortingChange={(newSorting) => {
+ if (newSorting.length > 0) {
+ setQueryStates({
+ sortId: newSorting[0].id,
+ sortDesc: newSorting[0].desc ? 1 : 0,
+ });
+ }
+ }}
/>
);
}
diff --git a/src/components/settings/credits/credits-page-client.tsx b/src/components/settings/credits/credits-page-client.tsx
index dc5172f..34cedb8 100644
--- a/src/components/settings/credits/credits-page-client.tsx
+++ b/src/components/settings/credits/credits-page-client.tsx
@@ -5,6 +5,7 @@ import { CreditTransactions } from '@/components/settings/credits/credit-transac
import CreditsBalanceCard from '@/components/settings/credits/credits-balance-card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useTranslations } from 'next-intl';
+import { parseAsStringLiteral, useQueryState } from 'nuqs';
/**
* Credits page client, show credit balance and transactions
@@ -12,9 +13,24 @@ import { useTranslations } from 'next-intl';
export default function CreditsPageClient() {
const t = useTranslations('Dashboard.settings.credits');
+ const [activeTab, setActiveTab] = useQueryState(
+ 'tab',
+ parseAsStringLiteral(['balance', 'transactions']).withDefault('balance')
+ );
+
+ const handleTabChange = (value: string) => {
+ if (value === 'balance' || value === 'transactions') {
+ setActiveTab(value);
+ }
+ };
+
return (
-
+
{t('tabs.balance')}