Files
new-api/web/default/src/lib/utils.ts
T
QuentinHsu 6f415428d3 perf(web): improve frontend table rendering and pinned columns/UI table (#5405)
* refactor(web): centralize data table implementation

- route all TanStack table setup through a shared data-table hook to remove repeated state and row model wiring.
- move table rendering, static table wrappers, empty states, and primitive exports behind the data-table module.
- update feature tables and configuration editors to share the same table UX while preserving their existing workflows.

* refactor(web): trim data table public API

- remove unused data-table exports and dead static table helper types.
- keep internal table header, skeleton, empty state, and faceted filter helpers private to the data-table module.
- route feature imports through the data-table barrel to avoid subpath coupling.

* refactor(web): unify table rendering components

- centralize static table headers, bodies, empty states, and shared class names behind the data-table package.
- migrate settings, pricing, channel, key, subscription, and model tables to the shared table APIs.
- remove data-table exports for low-level table primitives so feature code uses one supported abstraction.

* perf(web): keep list tables fixed within page content

- make shared data table pages fill available height and scroll row data inside the table body.
- add a fixed content layout mode so selected list pages avoid page-level scrolling.
- apply the fixed table behavior to keys, logs, channels, models, users, redemptions, and subscriptions.

* perf(web): refine table pagination controls

- show total row counts instead of redundant page range text.
- tighten visible page buttons so pagination fits constrained table widths.
- align pagination controls and tune text hierarchy for clearer scanning.

* perf(web): stabilize model pricing table columns

- keep model pricing columns at fixed widths so headers do not collapse in narrow layouts.
- truncate long model names and pricing summaries within their cells instead of squeezing adjacent columns.

* refactor(web): simplify data table rendering internals

- split table body rendering into focused helpers for loading, empty, and row states.
- extract static table row and cell class resolution to reduce branching in the main component.
- reuse a single pagination page-size option list to avoid duplicated constants.

* perf(pricing): reduce dynamic pricing table render work

- reuse dynamic pricing field metadata instead of rebuilding it inside table columns.
- precompute formatted dynamic prices per tier and group to avoid repeated entry mapping for each cell.
- simplify select option construction in related dialogs while preserving the same choices.

* refactor(web): streamline pricing table rendering

- reuse translated endpoint select options between trigger data and menu items.
- precompute dynamic pricing maps per group so table cells only resolve formatted values.
- add local dynamic pricing type aliases to keep helper signatures readable.

* refactor(web): merge pricing table imports

* refactor(web): merge upstream ratio table imports

* refactor(web): merge channel selector table imports

* refactor(web): simplify tiered pricing select items

* refactor(web): reuse model ratio row state

* refactor(web): rely on table view row defaults

* refactor(web): reuse pagination state values

* refactor(web): hoist pagination size select items

* refactor(web): clarify static table body rows

* refactor(web): extract table page pagination rendering

* fix(web): remove direct hast type dependency

- rely on Shiki transformer contextual typing for line nodes.
- allow frontend typecheck to pass without an undeclared hast package.

* refactor(web): trim data table hook return API

- return only the TanStack table instance from useDataTable.
- keep internal state handling private because callers do not consume it directly.

* refactor(web): keep static table empty row private

- stop exporting the internal StaticDataTableEmptyRow helper.
- keep the public static table API focused on the table component and column type.

* refactor(web): hide data table view props from barrel

* refactor(web): remove stale long text lint override

* fix(web): keep pinned table columns opaque

- apply pinned column background classes after custom column classes.
- use an opaque hover background so scrolled content cannot show through fixed cells.

* refactor(data-table): organize shared table components

- group table primitives, page composition, toolbar controls, static tables, and hooks by responsibility.
- split shared view types, row rendering, header rendering, and pinned-column styling out of the main table view.
- keep the public data-table barrel stable while documenting the new ownership boundaries.

* fix(web): stabilize split table column sizing

- derive default colgroup widths from visible columns when split headers or header sizing are enabled.
- apply a fixed table layout with computed minimum width so header and body columns stay aligned.
- keep split-header containers from leaking horizontal overflow and avoid extra pinned-column borders.

* fix(web): set stable table utility column widths

- assign fixed widths to selection columns so shared colgroup sizing keeps checkbox cells compact.
- size id columns in redemption and user tables to keep split headers aligned with body rows.

* fix(web): align model metadata icon cells

- render compact provider avatars in the metadata icon column instead of wide wordmarks.
- position icons in a fixed-size wrapper so they line up with the existing icon header alignment.

* fix(status-badge): hide status dot by default

* fix(web): prevent user invite info overlap

- give the invite info and created-at columns explicit widths so table sizing reserves enough space.
- allow invite badges to wrap within the cell instead of spilling into adjacent columns.

* perf(data-table): cache pinned column class resolution

- reuse the pinned column lookup while table props stay stable to reduce repeated per-render work.
- share the resolved column class handler across unified and split-header table layouts.
- localize page-number screen reader labels so pagination remains accessible in every locale.

* refactor(data-table): tighten static table modes

- make StaticDataTable distinguish data-driven and children-only usage through explicit prop shapes.
- remove unsupported columns-without-data fallback after confirming no repository callers rely on it.
- default manual table modes away from unused local row models to reduce repeated table work.

* fix(data-table): make pinned edit column opaque

- use an opaque muted background for the active action column so sticky cells do not reveal scrolled content underneath.

* fix(data-table): prevent narrow column overlap

- apply stable header sizing to remaining desktop data table pages so constrained layouts scroll instead of compressing cells.
- add explicit widths for key, quota, badge, and timestamp columns that contain fixed-format content.
- constrain masked values and timestamp cells with truncation to keep content inside its assigned column.

* fix(table): align table cell content with headers

- remove extra inline padding from masked table text buttons so values start at the cell edge.
- tag status badges and offset leading badges inside table cells to match header text alignment.

* fix(table): prevent admin list column overflow

- widen redemption and subscription table columns so masked codes, timestamps, and localized headers fit.
- localize subscription ID headers and add Received amount translations across supported locales.

* fix(provider-badge): unify provider icon spacing

- add a shared provider badge component for icon and status label layout.
- reuse it in channel type and model vendor columns so OpenAI icons align consistently.
2026-06-11 02:36:41 +08:00

103 lines
3.1 KiB
TypeScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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 [email protected]
*/
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function sleep(ms: number = 1000) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* 清理 CSS 变量名,替换特殊字符
* 用于将模型名称(如 gpt-3.5-turbo)转换为有效的 CSS 变量名(gpt-3-5-turbo)
* @param name - 原始名称
* @returns 清理后的 CSS 变量名
*/
export function sanitizeCssVariableName(name: string): string {
// 将点号、空格、斜杠替换为连字符
// 移除其他不允许在 CSS 变量名中的特殊字符
return name.replace(/[.\s/]/g, '-').replace(/[^\w-]/g, '')
}
/**
* Generates page numbers for pagination with ellipsis
* @param currentPage - Current page number (1-based)
* @param totalPages - Total number of pages
* @returns Array of page numbers and ellipsis strings
*
* Examples:
* - Small dataset (≤4 pages): [1, 2, 3, 4]
* - Near beginning: [1, 2, '...', 10]
* - In middle: [1, '...', 5, '...', 10]
* - Near end: [1, '...', 9, 10]
*/
export function getPageNumbers(currentPage: number, totalPages: number) {
const maxVisiblePages = 4
const rangeWithDots = []
if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
rangeWithDots.push(i)
}
} else {
rangeWithDots.push(1)
if (currentPage <= 2) {
rangeWithDots.push(2)
rangeWithDots.push('...', totalPages)
} else if (currentPage >= totalPages - 1) {
rangeWithDots.push('...')
rangeWithDots.push(totalPages - 1, totalPages)
} else {
rangeWithDots.push('...')
rangeWithDots.push(currentPage)
rangeWithDots.push('...', totalPages)
}
}
return rangeWithDots
}
/**
* Truncate text to a maximum length with ellipsis
*/
export function truncateText(text: string, maxLength: number): string {
if (!text || text.length <= maxLength) return text
return text.slice(0, maxLength) + '...'
}
/**
* Try to parse and pretty-print JSON, fallback to original text if invalid
* @param text - Text that might be JSON
* @returns Pretty-printed JSON or original text
*/
export function tryPrettyJson(text: string): string {
const raw = (text ?? '').toString().trim()
if (!raw) return ''
try {
return JSON.stringify(JSON.parse(raw), null, 2)
} catch {
return raw
}
}