/* 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 } from 'react' import { useQuery } from '@tanstack/react-query' import { getRouteApi } from '@tanstack/react-router' import { type ColumnDef, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, useReactTable, } from '@tanstack/react-table' import { useMediaQuery } from '@/hooks' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { cn } from '@/lib/utils' import { useIsAdmin } from '@/hooks/use-admin' import { useTableUrlState } from '@/hooks/use-table-url-state' import { TableCell, TableRow } from '@/components/ui/table' import { DataTablePage } from '@/components/data-table' import { DEFAULT_LOGS_DATA, LOG_TYPE_ALL_VALUE, LOG_TYPE_ENUM, } from '../constants' import { useColumnsByCategory } from '../lib/columns' import { fetchLogsByCategory } from '../lib/utils' import type { LogCategory } from '../types' import { CommonLogsFilterBar } from './common-logs-filter-bar' import { TaskLogsFilterBar } from './task-logs-filter-bar' const route = getRouteApi('/_authenticated/usage-logs/$section') const logTypeRowTint: Record = { [LOG_TYPE_ENUM.ERROR]: 'bg-rose-50/40 dark:bg-rose-950/20', [LOG_TYPE_ENUM.REFUND]: 'bg-blue-50/30 dark:bg-blue-950/15', } function deserializeLogTypeFilter(value: unknown): unknown[] { const values = Array.isArray(value) ? value : value ? [value] : [] return values.filter((item) => String(item) !== LOG_TYPE_ALL_VALUE) } interface UsageLogsTableProps { logCategory: LogCategory } export function UsageLogsTable({ logCategory }: UsageLogsTableProps) { const { t } = useTranslation() const isAdmin = useIsAdmin() const isMobile = useMediaQuery('(max-width: 640px)') const searchParams = route.useSearch() const { columnFilters, onColumnFiltersChange, pagination, onPaginationChange, ensurePageInRange, } = useTableUrlState({ search: route.useSearch(), navigate: route.useNavigate(), pagination: { defaultPage: 1, defaultPageSize: isMobile ? 20 : 100 }, globalFilter: { enabled: false }, columnFilters: [ { columnId: 'created_at', searchKey: 'type', type: 'array' as const, deserialize: deserializeLogTypeFilter, }, { columnId: 'model_name', searchKey: 'model', type: 'string' as const }, { columnId: 'token_name', searchKey: 'token', type: 'string' as const }, { columnId: 'group', searchKey: 'group', type: 'string' as const }, ...(isAdmin ? [ { columnId: 'channel', searchKey: 'channel', type: 'string' as const, }, { columnId: 'username', searchKey: 'username', type: 'string' as const, }, ] : []), ], }) const { data, isLoading, isFetching } = useQuery({ queryKey: [ 'logs', logCategory, isAdmin, pagination.pageIndex + 1, pagination.pageSize, columnFilters, searchParams, t, ], queryFn: async () => { const result = await fetchLogsByCategory({ logCategory, isAdmin, page: pagination.pageIndex + 1, pageSize: pagination.pageSize, searchParams, columnFilters, }) if (!result?.success) { toast.error(result?.message || t('Failed to load logs')) return DEFAULT_LOGS_DATA } return result.data || DEFAULT_LOGS_DATA }, placeholderData: (previousData, previousQuery) => { if (previousQuery?.queryKey[1] === logCategory) { return previousData } return undefined }, }) const logs = data?.items || [] const columns = useColumnsByCategory(logCategory, isAdmin) const isLoadingData = isLoading || (isFetching && !data) const table = useReactTable({ data: logs as Record[], columns: columns as ColumnDef>[], state: { columnFilters, pagination, }, enableRowSelection: false, onPaginationChange, onColumnFiltersChange, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), manualPagination: true, pageCount: Math.ceil((data?.total || 0) / pagination.pageSize), }) const pageCount = table.getPageCount() useEffect(() => { ensurePageInRange(pageCount) }, [pageCount, ensurePageInRange]) const isCommon = logCategory === 'common' return ( >[]} isLoading={isLoadingData} isFetching={isFetching} emptyTitle={t('No Logs Found')} emptyDescription={t( 'No usage logs available. Logs will appear here once API calls are made.' )} skeletonKeyPrefix='usage-log-skeleton' tableClassName='overflow-x-auto' tableHeaderClassName='bg-muted/30 sticky top-0 z-10' toolbar={ isCommon ? ( ) : ( ) } renderRow={(row) => { const logType = (row.original as Record).type as | number | undefined const tintClass = isCommon && logType != null ? (logTypeRowTint[logType] ?? '') : '' return ( {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} ) }} /> ) }