/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { type ColumnDef } from '@tanstack/react-table' import { useTranslation } from 'react-i18next' import { formatQuota, formatTimestamp } from '@/lib/format' import { cn } from '@/lib/utils' import { Checkbox } from '@/components/ui/checkbox' import { Progress } from '@/components/ui/progress' import { Tooltip, TooltipContent, 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 } from '@/components/status-badge' import { TableId } from '@/components/table-id' import { USER_STATUSES, USER_ROLES, 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[] { const { t } = useTranslation() return [ { id: 'select', header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label='Select all' className='translate-y-[2px]' /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label='Select row' className='translate-y-[2px]' /> ), enableSorting: false, enableHiding: false, meta: { label: t('Select') }, }, { accessorKey: 'id', header: ({ column }) => ( ), cell: ({ row }) => { return ( ) }, meta: { label: t('ID'), mobileHidden: true }, }, { accessorKey: 'username', header: ({ column }) => ( ), cell: ({ row }) => { const username = row.getValue('username') as string const displayName = row.original.display_name const remark = row.original.remark return (
{username} {remark && ( } > {remark}

{remark}

)}
{displayName && displayName !== username && ( {displayName} )}
) }, enableHiding: false, meta: { label: t('Username'), mobileTitle: true }, }, { accessorKey: 'status', header: ({ column }) => ( ), cell: ({ row }) => { const user = row.original const requestCount = user.request_count const statusConfig = isUserDeleted(user) ? USER_STATUSES.DELETED : USER_STATUSES[user.status as keyof typeof USER_STATUSES] if (!statusConfig) { return null } return ( }>

{t('Requests:')} {requestCount.toLocaleString()}

) }, filterFn: (row, id, value) => { return value.includes(String(row.getValue(id))) }, enableSorting: false, meta: { label: t('Status'), mobileBadge: true }, }, { id: 'quota', accessorKey: 'quota', header: ({ column }) => ( ), cell: ({ row }) => { const user = row.original const used = user.used_quota const remaining = user.quota const total = used + remaining const percentage = total > 0 ? (remaining / total) * 100 : 0 if (total === 0) { return ( ) } return ( } >
{formatQuota(remaining)} {formatQuota(total)}
{t('Used:')} {formatQuota(used)}
{t('Remaining:')} {formatQuota(remaining)}
{t('Total:')} {formatQuota(total)}
{t('Percentage:')} {percentage.toFixed(1)}%
) }, meta: { label: t('Quota') }, }, { accessorKey: 'group', header: ({ column }) => ( ), cell: ({ row }) => { const group = row.getValue('group') as string return }, filterFn: (row, id, value) => { const group = String(row.getValue(id) || t('User Group')).toLowerCase() const searchValue = String(value).toLowerCase() return group.includes(searchValue) }, meta: { label: t('Group') }, }, { accessorKey: 'role', header: ({ column }) => ( ), cell: ({ row }) => { const roleValue = row.getValue('role') as number const roleConfig = USER_ROLES[roleValue as keyof typeof USER_ROLES] if (!roleConfig) { return null } return (
{roleConfig.icon && ( )} {t(roleConfig.labelKey)}
) }, filterFn: (row, id, value) => { return value.includes(String(row.getValue(id))) }, enableSorting: false, meta: { label: t('Role') }, }, { id: 'invite_info', header: ({ column }) => ( ), cell: ({ row }) => { const user = row.original const affCount = user.aff_count || 0 const affHistoryQuota = user.aff_history_quota || 0 const inviterId = user.inviter_id || 0 return (
} />

{t('Number of users invited')}

} />

{t('Total invitation revenue')}

{inviterId > 0 && ( } />

{t('Invited by user ID')} {inviterId}

)} {inviterId === 0 && ( )}
) }, enableSorting: false, meta: { label: t('Invite Info'), mobileHidden: true }, }, { accessorKey: 'created_at', header: ({ column }) => ( ), cell: ({ row }) => { const ts = row.getValue('created_at') as number | undefined return ( {ts ? formatTimestamp(ts) : '-'} ) }, meta: { label: t('Created At'), mobileHidden: true }, }, { accessorKey: 'last_login_at', header: ({ column }) => ( ), cell: ({ row }) => { const ts = row.getValue('last_login_at') as number | undefined return ( {ts ? formatTimestamp(ts) : '-'} ) }, meta: { label: t('Last Login'), mobileHidden: true }, }, { id: 'actions', cell: ({ row }) => , meta: { label: t('Actions') }, }, ] }