feat: enhance sorting functionality in tables by implementing dropdown menus

This commit is contained in:
javayhu 2025-07-11 22:10:04 +08:00
parent 9711d13804
commit 75db5e85a7
4 changed files with 100 additions and 20 deletions

View File

@ -39,7 +39,9 @@
"firstPage": "First Page",
"lastPage": "Last Page",
"nextPage": "Next Page",
"previousPage": "Previous Page"
"previousPage": "Previous Page",
"ascending": "Asc",
"descending": "Desc"
}
},
"PricingPage": {

View File

@ -39,7 +39,9 @@
"firstPage": "第一页",
"lastPage": "最后一页",
"nextPage": "下一页",
"previousPage": "上一页"
"previousPage": "上一页",
"ascending": "升序",
"descending": "降序"
}
},
"PricingPage": {

View File

@ -6,6 +6,8 @@ import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
@ -27,6 +29,7 @@ import {
import type { User } from '@/lib/auth-types';
import { formatDate } from '@/lib/formatter';
import { getStripeDashboardCustomerUrl } from '@/lib/urls/urls';
import { IconCaretDownFilled, IconCaretUpFilled } from '@tabler/icons-react';
import {
type ColumnDef,
type ColumnFiltersState,
@ -40,7 +43,6 @@ import {
useReactTable,
} from '@tanstack/react-table';
import {
ArrowUpDownIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
@ -68,20 +70,47 @@ function DataTableColumnHeader<TData, TValue>({
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
const tTable = useTranslations('Common.table');
if (!column.getCanSort()) {
return <div className={className}>{title}</div>;
}
const isSorted = column.getIsSorted(); // 'asc' | 'desc' | false
return (
<div className={className}>
<Button
variant="ghost"
className="cursor-pointer flex items-center gap-2"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{title}
<ArrowUpDownIcon className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="cursor-pointer flex items-center gap-2 h-8 data-[state=open]:bg-accent"
>
{title}
{isSorted === 'asc' && <IconCaretUpFilled className="h-4 w-4" />}
{isSorted === 'desc' && <IconCaretDownFilled className="h-4 w-4" />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-36">
<DropdownMenuRadioGroup
value={isSorted === false ? '' : isSorted}
onValueChange={(value) => {
if (value === 'asc') column.toggleSorting(false);
else if (value === 'desc') column.toggleSorting(true);
}}
>
<DropdownMenuRadioItem value="asc">
<span className="flex items-center gap-2">
{tTable('ascending')}
</span>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="desc">
<span className="flex items-center gap-2">
{tTable('descending')}
</span>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
@ -117,7 +146,7 @@ export function UsersTable({
const t = useTranslations('Dashboard.admin.users');
const tTable = useTranslations('Common.table');
const [sorting, setSorting] = useState<SortingState>([
{ id: 'createdAt', desc: true }
{ id: 'createdAt', desc: true },
]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});

View File

@ -5,6 +5,8 @@ import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
@ -32,6 +34,12 @@ import {
import { CreditDetailViewer } from '@/credits/credit-detail-viewer';
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,
@ -45,13 +53,17 @@ import {
useReactTable,
} from '@tanstack/react-table';
import {
ArrowDownIcon,
ArrowUpDownIcon,
ArrowUpIcon,
BanknoteIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUpIcon,
ChevronsLeftIcon,
ChevronsRightIcon,
ChevronsUpDownIcon,
ClockIcon,
CoinsIcon,
GemIcon,
@ -91,16 +103,51 @@ function DataTableColumnHeader<TData, TValue>({
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
const tTable = useTranslations('Common.table');
// Only show dropdown for sortable columns
if (!column.getCanSort()) {
return <div className={className}>{title}</div>;
}
// Determine current sort state
const isSorted = column.getIsSorted(); // 'asc' | 'desc' | false
return (
<div className={className}>
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="h-8 data-[state=open]:bg-accent"
>
<span>{title}</span>
<ArrowUpDownIcon className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 data-[state=open]:bg-accent flex items-center gap-1"
>
<span>{title}</span>
{isSorted === 'asc' && <IconCaretUpFilled className="h-4 w-4" />}
{isSorted === 'desc' && <IconCaretDownFilled className="h-4 w-4" />}
{/* {!isSorted && <ChevronsUpDownIcon className="h-4 w-4" />} */}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-36">
<DropdownMenuRadioGroup
value={isSorted === false ? '' : isSorted}
onValueChange={(value) => {
if (value === 'asc') column.toggleSorting(false);
else if (value === 'desc') column.toggleSorting(true);
}}
>
<DropdownMenuRadioItem value="asc">
<span className="flex items-center gap-2">
{tTable('ascending')}
</span>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="desc">
<span className="flex items-center gap-2">
{tTable('descending')}
</span>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}