mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-06 17:46:23 +00:00
315 lines
8.9 KiB
TypeScript
Vendored
315 lines
8.9 KiB
TypeScript
Vendored
/*
|
|
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 <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
import * as React from 'react'
|
|
import {
|
|
type ColumnDef,
|
|
type ColumnFiltersState,
|
|
type ExpandedState,
|
|
type OnChangeFn,
|
|
type PaginationState,
|
|
type RowSelectionState,
|
|
type SortingState,
|
|
type TableOptions,
|
|
type Updater,
|
|
type VisibilityState,
|
|
getCoreRowModel,
|
|
getExpandedRowModel,
|
|
getFacetedRowModel,
|
|
getFacetedUniqueValues,
|
|
getFilteredRowModel,
|
|
getPaginationRowModel,
|
|
getSortedRowModel,
|
|
useReactTable,
|
|
} from '@tanstack/react-table'
|
|
|
|
type DataTableFeatureOptions<TData> = Pick<
|
|
TableOptions<TData>,
|
|
| 'enableRowSelection'
|
|
| 'getRowId'
|
|
| 'getSubRows'
|
|
| 'globalFilterFn'
|
|
| 'autoResetPageIndex'
|
|
| 'manualFiltering'
|
|
| 'manualPagination'
|
|
| 'manualSorting'
|
|
>
|
|
|
|
type DataTableStateOptions = {
|
|
initialSorting?: SortingState
|
|
sorting?: SortingState
|
|
onSortingChange?: OnChangeFn<SortingState>
|
|
initialColumnVisibility?: VisibilityState
|
|
columnVisibilityStorageKey?: string | false
|
|
columnVisibility?: VisibilityState
|
|
onColumnVisibilityChange?: OnChangeFn<VisibilityState>
|
|
initialRowSelection?: RowSelectionState
|
|
rowSelection?: RowSelectionState
|
|
onRowSelectionChange?: OnChangeFn<RowSelectionState>
|
|
initialExpanded?: ExpandedState
|
|
expanded?: ExpandedState
|
|
onExpandedChange?: OnChangeFn<ExpandedState>
|
|
columnFilters?: ColumnFiltersState
|
|
onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>
|
|
globalFilter?: string
|
|
onGlobalFilterChange?: OnChangeFn<string>
|
|
initialPagination?: PaginationState
|
|
pagination?: PaginationState
|
|
onPaginationChange?: OnChangeFn<PaginationState>
|
|
}
|
|
|
|
type DataTableRowModelOptions = {
|
|
withFilteredRowModel?: boolean
|
|
withPaginationRowModel?: boolean
|
|
withSortedRowModel?: boolean
|
|
withFacetedRowModel?: boolean
|
|
withExpandedRowModel?: boolean
|
|
}
|
|
|
|
type UseDataTableOptions<TData> = DataTableFeatureOptions<TData> &
|
|
DataTableStateOptions &
|
|
DataTableRowModelOptions & {
|
|
data: TData[]
|
|
columns: ColumnDef<TData, unknown>[]
|
|
totalCount?: number
|
|
pageCount?: number
|
|
ensurePageInRange?: (pageCount: number) => void
|
|
}
|
|
|
|
function resolveUpdater<TValue>(
|
|
updater: Updater<TValue>,
|
|
previous: TValue
|
|
): TValue {
|
|
return typeof updater === 'function'
|
|
? (updater as (old: TValue) => TValue)(previous)
|
|
: updater
|
|
}
|
|
|
|
function useControllableTableState<TValue>(
|
|
controlledValue: TValue | undefined,
|
|
defaultValue: TValue,
|
|
onChange: OnChangeFn<TValue> | undefined
|
|
): [TValue, OnChangeFn<TValue>] {
|
|
const [uncontrolledValue, setUncontrolledValue] =
|
|
React.useState<TValue>(defaultValue)
|
|
|
|
const value = controlledValue ?? uncontrolledValue
|
|
|
|
const setValue = React.useCallback<OnChangeFn<TValue>>(
|
|
(updater) => {
|
|
if (controlledValue === undefined) {
|
|
setUncontrolledValue((previous) => resolveUpdater(updater, previous))
|
|
}
|
|
onChange?.(updater)
|
|
},
|
|
[controlledValue, onChange]
|
|
)
|
|
|
|
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,
|
|
columns,
|
|
totalCount,
|
|
pageCount: explicitPageCount,
|
|
ensurePageInRange,
|
|
manualFiltering,
|
|
manualPagination,
|
|
manualSorting,
|
|
initialSorting = [],
|
|
initialColumnVisibility = {},
|
|
initialRowSelection = {},
|
|
initialExpanded = {},
|
|
initialPagination = { pageIndex: 0, pageSize: 20 },
|
|
withFilteredRowModel = !manualFiltering,
|
|
withPaginationRowModel = !manualPagination,
|
|
withSortedRowModel = !manualSorting,
|
|
withFacetedRowModel = !manualFiltering,
|
|
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,
|
|
options.onSortingChange
|
|
)
|
|
const [columnVisibility, onColumnVisibilityChange] =
|
|
useControllableTableState(
|
|
options.columnVisibility,
|
|
resolvedInitialColumnVisibility,
|
|
options.onColumnVisibilityChange
|
|
)
|
|
const hydratedColumnVisibilityStorageKeyRef = React.useRef(
|
|
columnVisibilityStorageKey
|
|
)
|
|
const skipNextColumnVisibilityPersistRef = React.useRef(false)
|
|
const [rowSelection, onRowSelectionChange] = useControllableTableState(
|
|
options.rowSelection,
|
|
initialRowSelection,
|
|
options.onRowSelectionChange
|
|
)
|
|
const [expanded, onExpandedChange] = useControllableTableState(
|
|
options.expanded,
|
|
initialExpanded,
|
|
options.onExpandedChange
|
|
)
|
|
const [pagination, onPaginationChange] = useControllableTableState(
|
|
options.pagination,
|
|
initialPagination,
|
|
options.onPaginationChange
|
|
)
|
|
|
|
const resolvedPageCount =
|
|
explicitPageCount ??
|
|
(totalCount !== undefined
|
|
? Math.ceil(totalCount / pagination.pageSize)
|
|
: undefined)
|
|
|
|
const table = useReactTable({
|
|
data,
|
|
columns,
|
|
rowCount: totalCount,
|
|
pageCount: resolvedPageCount,
|
|
state: {
|
|
sorting,
|
|
columnVisibility,
|
|
rowSelection,
|
|
expanded,
|
|
columnFilters: options.columnFilters,
|
|
globalFilter: options.globalFilter,
|
|
pagination,
|
|
},
|
|
enableRowSelection: options.enableRowSelection,
|
|
getRowId: options.getRowId,
|
|
getSubRows: options.getSubRows,
|
|
globalFilterFn: options.globalFilterFn,
|
|
autoResetPageIndex: options.autoResetPageIndex,
|
|
manualFiltering,
|
|
manualPagination,
|
|
manualSorting,
|
|
onSortingChange,
|
|
onColumnVisibilityChange,
|
|
onRowSelectionChange,
|
|
onExpandedChange,
|
|
onColumnFiltersChange: options.onColumnFiltersChange,
|
|
onGlobalFilterChange: options.onGlobalFilterChange,
|
|
onPaginationChange,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getFilteredRowModel: withFilteredRowModel
|
|
? getFilteredRowModel()
|
|
: undefined,
|
|
getPaginationRowModel: withPaginationRowModel
|
|
? getPaginationRowModel()
|
|
: undefined,
|
|
getSortedRowModel: withSortedRowModel ? getSortedRowModel() : undefined,
|
|
getFacetedRowModel: withFacetedRowModel ? getFacetedRowModel() : undefined,
|
|
getFacetedUniqueValues: withFacetedRowModel
|
|
? getFacetedUniqueValues()
|
|
: undefined,
|
|
getExpandedRowModel: withExpandedRowModel
|
|
? getExpandedRowModel()
|
|
: undefined,
|
|
})
|
|
|
|
const actualPageCount = table.getPageCount()
|
|
React.useEffect(() => {
|
|
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,
|
|
}
|
|
}
|