Commit 4e8b5e9b by CaIon

feat(data-table): add column visibility storage functionality for API keys,…

feat(data-table): add column visibility storage functionality for API keys, channels, and usage logs
parent 1aa77e66
...@@ -55,6 +55,7 @@ type DataTableStateOptions = { ...@@ -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>( ...@@ -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>) { ...@@ -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>) { ...@@ -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>) { ...@@ -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' ...@@ -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() { ...@@ -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' ...@@ -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() { ...@@ -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> = { ...@@ -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) { ...@@ -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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment