feat(ui): improve table controls and analytics filters

This commit is contained in:
CaIon
2026-04-30 13:57:10 +08:00
parent 8bff691089
commit 8f3c41ae77
35 changed files with 858 additions and 472 deletions
+40 -41
View File
@@ -10,17 +10,23 @@ import {
TooltipTrigger,
} from '@/components/ui/tooltip'
import { DataTableColumnHeader } from '@/components/data-table'
import { GroupBadge } from '@/components/group-badge'
import { LongText } from '@/components/long-text'
import { StatusBadge, dotColorMap } from '@/components/status-badge'
import {
USER_STATUSES,
USER_ROLES,
DEFAULT_GROUP,
isUserDeleted,
} from '../constants'
import { type User } from '../types'
import { DataTableRowActions } from './data-table-row-actions'
function getQuotaProgressColor(percentage: number): string {
if (percentage <= 10) return '[&_[data-slot=progress-indicator]]:bg-rose-500'
if (percentage <= 30) return '[&_[data-slot=progress-indicator]]:bg-amber-500'
return '[&_[data-slot=progress-indicator]]:bg-emerald-500'
}
export function useUsersColumns(): ColumnDef<User>[] {
const { t } = useTranslation()
return [
@@ -66,24 +72,32 @@ export function useUsersColumns(): ColumnDef<User>[] {
),
cell: ({ row }) => {
const username = row.getValue('username') as string
const displayName = row.original.display_name
const remark = row.original.remark
return (
<div className='flex items-center gap-2'>
<LongText className='max-w-[120px] font-medium'>
{username}
</LongText>
{remark && (
<Tooltip>
<TooltipTrigger asChild>
<StatusBadge variant='success' copyable={false}>
<LongText className='max-w-[80px]'>{remark}</LongText>
</StatusBadge>
</TooltipTrigger>
<TooltipContent>
<p className='text-xs'>{remark}</p>
</TooltipContent>
</Tooltip>
<div className='flex min-w-[160px] flex-col gap-1'>
<div className='flex items-center gap-2'>
<LongText className='max-w-[140px] font-medium'>
{username}
</LongText>
{remark && (
<Tooltip>
<TooltipTrigger asChild>
<StatusBadge variant='success' copyable={false}>
<LongText className='max-w-[80px]'>{remark}</LongText>
</StatusBadge>
</TooltipTrigger>
<TooltipContent>
<p className='text-xs'>{remark}</p>
</TooltipContent>
</Tooltip>
)}
</div>
{displayName && displayName !== username && (
<LongText className='text-muted-foreground max-w-[180px] text-xs'>
{displayName}
</LongText>
)}
</div>
)
@@ -91,20 +105,6 @@ export function useUsersColumns(): ColumnDef<User>[] {
enableHiding: false,
meta: { label: t('Username'), mobileTitle: true },
},
{
accessorKey: 'display_name',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Display Name')} />
),
cell: ({ row }) => {
return (
<LongText className='max-w-[150px]'>
{row.getValue('display_name') || '-'}
</LongText>
)
},
meta: { label: t('Display Name'), mobileHidden: true },
},
{
accessorKey: 'status',
header: ({ column }) => (
@@ -176,12 +176,17 @@ export function useUsersColumns(): ColumnDef<User>[] {
<TooltipTrigger asChild>
<div className='w-[150px] cursor-help space-y-1'>
<div className='flex justify-between text-xs'>
<span>{formatQuota(remaining)}</span>
<span className='text-muted-foreground'>
<span className='font-medium tabular-nums'>
{formatQuota(remaining)}
</span>
<span className='text-muted-foreground tabular-nums'>
{formatQuota(total)}
</span>
</div>
<Progress value={percentage} className='h-2' />
<Progress
value={percentage}
className={cn('h-1.5', getQuotaProgressColor(percentage))}
/>
</div>
</TooltipTrigger>
<TooltipContent>
@@ -212,16 +217,10 @@ export function useUsersColumns(): ColumnDef<User>[] {
),
cell: ({ row }) => {
const group = row.getValue('group') as string
return (
<StatusBadge
label={group || DEFAULT_GROUP}
variant='neutral'
copyable={false}
/>
)
return <GroupBadge group={group} />
},
filterFn: (row, id, value) => {
const group = String(row.getValue(id) || DEFAULT_GROUP).toLowerCase()
const group = String(row.getValue(id) || t('User Group')).toLowerCase()
const searchValue = String(value).toLowerCase()
return group.includes(searchValue)
},
+13 -5
View File
@@ -27,6 +27,8 @@ import {
TableRow,
} from '@/components/ui/table'
import {
DISABLED_ROW_DESKTOP,
DISABLED_ROW_MOBILE,
DataTablePagination,
DataTableToolbar,
TableSkeleton,
@@ -41,12 +43,17 @@ import {
getUserRoleOptions,
isUserDeleted,
} from '../constants'
import type { User } from '../types'
import { DataTableBulkActions } from './data-table-bulk-actions'
import { useUsersColumns } from './users-columns'
import { useUsers } from './users-provider'
const route = getRouteApi('/_authenticated/users/')
function isDisabledUserRow(user: User) {
return isUserDeleted(user) || user.status === USER_STATUS.DISABLED
}
export function UsersTable() {
const { t } = useTranslation()
const columns = useUsersColumns()
@@ -186,6 +193,9 @@ export function UsersTable() {
emptyDescription={t(
'No users available. Try adjusting your search or filters.'
)}
getRowClassName={(row) =>
isDisabledUserRow(row.original) ? DISABLED_ROW_MOBILE : undefined
}
/>
) : (
<>
@@ -226,16 +236,14 @@ export function UsersTable() {
) : (
table.getRowModel().rows.map((row) => {
const user = row.original
const isDeleted = isUserDeleted(user)
const isDisabled = user.status === USER_STATUS.DISABLED
return (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className={
isDeleted || isDisabled ? 'opacity-50' : undefined
}
className={cn(
isDisabledUserRow(user) && DISABLED_ROW_DESKTOP
)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>