/* 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 { useEffect, useState } from 'react' import { useQuery } from '@tanstack/react-query' import { getRouteApi } from '@tanstack/react-router' import { type SortingState, type VisibilityState, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table' import { useMediaQuery } from '@/hooks' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { useTableUrlState } from '@/hooks/use-table-url-state' import { DISABLED_ROW_DESKTOP, DISABLED_ROW_MOBILE, DataTablePage, } from '@/components/data-table' import { getUsers, searchUsers } from '../api' import { USER_STATUS, getUserStatusOptions, 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() const { refreshTrigger } = useUsers() const isMobile = useMediaQuery('(max-width: 640px)') const [rowSelection, setRowSelection] = useState({}) const [sorting, setSorting] = useState([]) const [columnVisibility, setColumnVisibility] = useState({}) const { globalFilter, onGlobalFilterChange, columnFilters, onColumnFiltersChange, pagination, onPaginationChange, ensurePageInRange, } = useTableUrlState({ search: route.useSearch(), navigate: route.useNavigate(), pagination: { defaultPage: 1, defaultPageSize: isMobile ? 10 : 20 }, globalFilter: { enabled: true, key: 'filter' }, columnFilters: [ { columnId: 'status', searchKey: 'status', type: 'array' }, { columnId: 'role', searchKey: 'role', type: 'array' }, { columnId: 'group', searchKey: 'group', type: 'string' }, ], }) // Fetch data with React Query const { data, isLoading, isFetching } = useQuery({ queryKey: [ 'users', pagination.pageIndex + 1, pagination.pageSize, globalFilter, refreshTrigger, ], queryFn: async () => { const hasFilter = globalFilter?.trim() const params = { p: pagination.pageIndex + 1, page_size: pagination.pageSize, } const result = hasFilter ? await searchUsers({ ...params, keyword: globalFilter }) : await getUsers(params) if (!result.success) { toast.error( result.message || `Failed to ${hasFilter ? 'search' : 'load'} users` ) return { items: [], total: 0 } } return { items: result.data?.items || [], total: result.data?.total || 0, } }, placeholderData: (previousData) => previousData, }) const users = data?.items || [] const table = useReactTable({ data: users, columns, state: { sorting, columnVisibility, rowSelection, columnFilters, globalFilter, pagination, }, enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onColumnVisibilityChange: setColumnVisibility, globalFilterFn: (row, _columnId, filterValue) => { const searchValue = String(filterValue).toLowerCase() const fields = [ row.getValue('username'), row.original.display_name, row.original.email, ] return fields.some((field) => String(field || '') .toLowerCase() .includes(searchValue) ) }, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), onPaginationChange, onGlobalFilterChange, onColumnFiltersChange, manualPagination: !globalFilter, pageCount: Math.ceil((data?.total || 0) / pagination.pageSize), }) const pageCount = table.getPageCount() useEffect(() => { ensurePageInRange(pageCount) }, [pageCount, ensurePageInRange]) return ( isDisabledUserRow(row.original) ? isMobile ? DISABLED_ROW_MOBILE : DISABLED_ROW_DESKTOP : undefined } bulkActions={} /> ) }