feat(ui): improve table controls and analytics filters
This commit is contained in:
+40
-41
@@ -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
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user