mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-06 17:46:23 +00:00
feat(data-table): add column visibility storage functionality for API keys, channels, and usage logs
This commit is contained in:
@@ -55,6 +55,7 @@ type DataTableStateOptions = {
|
|||||||
sorting?: SortingState
|
sorting?: SortingState
|
||||||
onSortingChange?: OnChangeFn<SortingState>
|
onSortingChange?: OnChangeFn<SortingState>
|
||||||
initialColumnVisibility?: VisibilityState
|
initialColumnVisibility?: VisibilityState
|
||||||
|
columnVisibilityStorageKey?: string | false
|
||||||
columnVisibility?: VisibilityState
|
columnVisibility?: VisibilityState
|
||||||
onColumnVisibilityChange?: OnChangeFn<VisibilityState>
|
onColumnVisibilityChange?: OnChangeFn<VisibilityState>
|
||||||
initialRowSelection?: RowSelectionState
|
initialRowSelection?: RowSelectionState
|
||||||
@@ -122,6 +123,32 @@ function useControllableTableState<TValue>(
|
|||||||
return [value, setValue]
|
return [value, setValue]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readColumnVisibility(storageKey: string | undefined): VisibilityState {
|
||||||
|
if (!storageKey || typeof window === 'undefined') return {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(storageKey)
|
||||||
|
if (!raw) return {}
|
||||||
|
|
||||||
|
const parsed = JSON.parse(raw) as unknown
|
||||||
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(parsed).reduce<VisibilityState>(
|
||||||
|
(visibility, [key, value]) => {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
visibility[key] = value
|
||||||
|
}
|
||||||
|
return visibility
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -144,6 +171,18 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
|||||||
withExpandedRowModel = false,
|
withExpandedRowModel = false,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
|
const columnVisibilityStorageKey =
|
||||||
|
typeof options.columnVisibilityStorageKey === 'string'
|
||||||
|
? options.columnVisibilityStorageKey
|
||||||
|
: undefined
|
||||||
|
const resolvedInitialColumnVisibility = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
...initialColumnVisibility,
|
||||||
|
...readColumnVisibility(columnVisibilityStorageKey),
|
||||||
|
}),
|
||||||
|
[columnVisibilityStorageKey, initialColumnVisibility]
|
||||||
|
)
|
||||||
|
|
||||||
const [sorting, onSortingChange] = useControllableTableState(
|
const [sorting, onSortingChange] = useControllableTableState(
|
||||||
options.sorting,
|
options.sorting,
|
||||||
initialSorting,
|
initialSorting,
|
||||||
@@ -152,9 +191,13 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
|||||||
const [columnVisibility, onColumnVisibilityChange] =
|
const [columnVisibility, onColumnVisibilityChange] =
|
||||||
useControllableTableState(
|
useControllableTableState(
|
||||||
options.columnVisibility,
|
options.columnVisibility,
|
||||||
initialColumnVisibility,
|
resolvedInitialColumnVisibility,
|
||||||
options.onColumnVisibilityChange
|
options.onColumnVisibilityChange
|
||||||
)
|
)
|
||||||
|
const hydratedColumnVisibilityStorageKeyRef = React.useRef(
|
||||||
|
columnVisibilityStorageKey
|
||||||
|
)
|
||||||
|
const skipNextColumnVisibilityPersistRef = React.useRef(false)
|
||||||
const [rowSelection, onRowSelectionChange] = useControllableTableState(
|
const [rowSelection, onRowSelectionChange] = useControllableTableState(
|
||||||
options.rowSelection,
|
options.rowSelection,
|
||||||
initialRowSelection,
|
initialRowSelection,
|
||||||
@@ -228,6 +271,43 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
|||||||
ensurePageInRange?.(actualPageCount)
|
ensurePageInRange?.(actualPageCount)
|
||||||
}, [actualPageCount, ensurePageInRange])
|
}, [actualPageCount, ensurePageInRange])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (
|
||||||
|
options.columnVisibility !== undefined ||
|
||||||
|
columnVisibilityStorageKey ===
|
||||||
|
hydratedColumnVisibilityStorageKeyRef.current
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hydratedColumnVisibilityStorageKeyRef.current = columnVisibilityStorageKey
|
||||||
|
skipNextColumnVisibilityPersistRef.current = true
|
||||||
|
onColumnVisibilityChange(() => resolvedInitialColumnVisibility)
|
||||||
|
}, [
|
||||||
|
columnVisibilityStorageKey,
|
||||||
|
onColumnVisibilityChange,
|
||||||
|
options.columnVisibility,
|
||||||
|
resolvedInitialColumnVisibility,
|
||||||
|
])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!columnVisibilityStorageKey || typeof window === 'undefined') return
|
||||||
|
|
||||||
|
if (skipNextColumnVisibilityPersistRef.current) {
|
||||||
|
skipNextColumnVisibilityPersistRef.current = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(
|
||||||
|
columnVisibilityStorageKey,
|
||||||
|
JSON.stringify(columnVisibility)
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// Storage can be unavailable in private mode; table controls still work.
|
||||||
|
}
|
||||||
|
}, [columnVisibility, columnVisibilityStorageKey])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
table,
|
table,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ import { useChannels } from './channels-provider'
|
|||||||
import { DataTableBulkActions } from './data-table-bulk-actions'
|
import { DataTableBulkActions } from './data-table-bulk-actions'
|
||||||
|
|
||||||
const route = getRouteApi('/_authenticated/channels/')
|
const route = getRouteApi('/_authenticated/channels/')
|
||||||
|
const CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY =
|
||||||
|
'channels:column-visibility'
|
||||||
|
|
||||||
const CHANNEL_SORTABLE_COLUMNS = new Set<ChannelSortBy>([
|
const CHANNEL_SORTABLE_COLUMNS = new Set<ChannelSortBy>([
|
||||||
'id',
|
'id',
|
||||||
@@ -268,6 +270,7 @@ export function ChannelsTable() {
|
|||||||
models: false,
|
models: false,
|
||||||
tag: false,
|
tag: false,
|
||||||
},
|
},
|
||||||
|
columnVisibilityStorageKey: CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
pagination,
|
pagination,
|
||||||
globalFilter,
|
globalFilter,
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ import { DataTableBulkActions } from './data-table-bulk-actions'
|
|||||||
import { DataTableRowActions } from './data-table-row-actions'
|
import { DataTableRowActions } from './data-table-row-actions'
|
||||||
|
|
||||||
const route = getRouteApi('/_authenticated/keys/')
|
const route = getRouteApi('/_authenticated/keys/')
|
||||||
|
const API_KEYS_COLUMN_VISIBILITY_STORAGE_KEY =
|
||||||
|
'api-keys:column-visibility'
|
||||||
|
|
||||||
function isDisabledApiKeyRow(apiKey: ApiKey) {
|
function isDisabledApiKeyRow(apiKey: ApiKey) {
|
||||||
return apiKey.status !== API_KEY_STATUS.ENABLED
|
return apiKey.status !== API_KEY_STATUS.ENABLED
|
||||||
@@ -264,6 +266,7 @@ export function ApiKeysTable() {
|
|||||||
columns,
|
columns,
|
||||||
enableRowSelection: true,
|
enableRowSelection: true,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
|
columnVisibilityStorageKey: API_KEYS_COLUMN_VISIBILITY_STORAGE_KEY,
|
||||||
globalFilter,
|
globalFilter,
|
||||||
pagination,
|
pagination,
|
||||||
globalFilterFn: () => true,
|
globalFilterFn: () => true,
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ const logTypeRowTint: Record<number, string> = {
|
|||||||
[LOG_TYPE_ENUM.REFUND]: 'bg-blue-50/30 dark:bg-blue-950/15',
|
[LOG_TYPE_ENUM.REFUND]: 'bg-blue-50/30 dark:bg-blue-950/15',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getColumnVisibilityStorageKey(
|
||||||
|
logCategory: LogCategory,
|
||||||
|
isAdmin: boolean
|
||||||
|
): string {
|
||||||
|
return `usage-logs:${logCategory}:${isAdmin ? 'admin' : 'user'}:column-visibility`
|
||||||
|
}
|
||||||
|
|
||||||
function deserializeLogTypeFilter(value: unknown): unknown[] {
|
function deserializeLogTypeFilter(value: unknown): unknown[] {
|
||||||
const values = Array.isArray(value) ? value : value ? [value] : []
|
const values = Array.isArray(value) ? value : value ? [value] : []
|
||||||
return values.filter((item) => String(item) !== LOG_TYPE_ALL_VALUE)
|
return values.filter((item) => String(item) !== LOG_TYPE_ALL_VALUE)
|
||||||
@@ -146,6 +153,10 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
|
|||||||
data: logs as Record<string, unknown>[],
|
data: logs as Record<string, unknown>[],
|
||||||
columns: columns as ColumnDef<Record<string, unknown>>[],
|
columns: columns as ColumnDef<Record<string, unknown>>[],
|
||||||
columnFilters,
|
columnFilters,
|
||||||
|
columnVisibilityStorageKey: getColumnVisibilityStorageKey(
|
||||||
|
logCategory,
|
||||||
|
isAdmin
|
||||||
|
),
|
||||||
pagination,
|
pagination,
|
||||||
enableRowSelection: false,
|
enableRowSelection: false,
|
||||||
onPaginationChange,
|
onPaginationChange,
|
||||||
|
|||||||
Reference in New Issue
Block a user