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
|
||||
onSortingChange?: OnChangeFn<SortingState>
|
||||
initialColumnVisibility?: VisibilityState
|
||||
columnVisibilityStorageKey?: string | false
|
||||
columnVisibility?: VisibilityState
|
||||
onColumnVisibilityChange?: OnChangeFn<VisibilityState>
|
||||
initialRowSelection?: RowSelectionState
|
||||
@@ -122,6 +123,32 @@ function useControllableTableState<TValue>(
|
||||
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>) {
|
||||
const {
|
||||
data,
|
||||
@@ -144,6 +171,18 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
||||
withExpandedRowModel = false,
|
||||
} = options
|
||||
|
||||
const columnVisibilityStorageKey =
|
||||
typeof options.columnVisibilityStorageKey === 'string'
|
||||
? options.columnVisibilityStorageKey
|
||||
: undefined
|
||||
const resolvedInitialColumnVisibility = React.useMemo(
|
||||
() => ({
|
||||
...initialColumnVisibility,
|
||||
...readColumnVisibility(columnVisibilityStorageKey),
|
||||
}),
|
||||
[columnVisibilityStorageKey, initialColumnVisibility]
|
||||
)
|
||||
|
||||
const [sorting, onSortingChange] = useControllableTableState(
|
||||
options.sorting,
|
||||
initialSorting,
|
||||
@@ -152,9 +191,13 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
||||
const [columnVisibility, onColumnVisibilityChange] =
|
||||
useControllableTableState(
|
||||
options.columnVisibility,
|
||||
initialColumnVisibility,
|
||||
resolvedInitialColumnVisibility,
|
||||
options.onColumnVisibilityChange
|
||||
)
|
||||
const hydratedColumnVisibilityStorageKeyRef = React.useRef(
|
||||
columnVisibilityStorageKey
|
||||
)
|
||||
const skipNextColumnVisibilityPersistRef = React.useRef(false)
|
||||
const [rowSelection, onRowSelectionChange] = useControllableTableState(
|
||||
options.rowSelection,
|
||||
initialRowSelection,
|
||||
@@ -228,6 +271,43 @@ export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
||||
ensurePageInRange?.(actualPageCount)
|
||||
}, [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 {
|
||||
table,
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ import { useChannels } from './channels-provider'
|
||||
import { DataTableBulkActions } from './data-table-bulk-actions'
|
||||
|
||||
const route = getRouteApi('/_authenticated/channels/')
|
||||
const CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY =
|
||||
'channels:column-visibility'
|
||||
|
||||
const CHANNEL_SORTABLE_COLUMNS = new Set<ChannelSortBy>([
|
||||
'id',
|
||||
@@ -268,6 +270,7 @@ export function ChannelsTable() {
|
||||
models: false,
|
||||
tag: false,
|
||||
},
|
||||
columnVisibilityStorageKey: CHANNELS_COLUMN_VISIBILITY_STORAGE_KEY,
|
||||
columnFilters,
|
||||
pagination,
|
||||
globalFilter,
|
||||
|
||||
@@ -57,6 +57,8 @@ import { DataTableBulkActions } from './data-table-bulk-actions'
|
||||
import { DataTableRowActions } from './data-table-row-actions'
|
||||
|
||||
const route = getRouteApi('/_authenticated/keys/')
|
||||
const API_KEYS_COLUMN_VISIBILITY_STORAGE_KEY =
|
||||
'api-keys:column-visibility'
|
||||
|
||||
function isDisabledApiKeyRow(apiKey: ApiKey) {
|
||||
return apiKey.status !== API_KEY_STATUS.ENABLED
|
||||
@@ -264,6 +266,7 @@ export function ApiKeysTable() {
|
||||
columns,
|
||||
enableRowSelection: true,
|
||||
columnFilters,
|
||||
columnVisibilityStorageKey: API_KEYS_COLUMN_VISIBILITY_STORAGE_KEY,
|
||||
globalFilter,
|
||||
pagination,
|
||||
globalFilterFn: () => true,
|
||||
|
||||
@@ -49,6 +49,13 @@ const logTypeRowTint: Record<number, string> = {
|
||||
[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[] {
|
||||
const values = Array.isArray(value) ? value : value ? [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>[],
|
||||
columns: columns as ColumnDef<Record<string, unknown>>[],
|
||||
columnFilters,
|
||||
columnVisibilityStorageKey: getColumnVisibilityStorageKey(
|
||||
logCategory,
|
||||
isAdmin
|
||||
),
|
||||
pagination,
|
||||
enableRowSelection: false,
|
||||
onPaginationChange,
|
||||
|
||||
Reference in New Issue
Block a user