Update .env.example and README.md to clarify JWT_SECRET requirements; enhance telemt API route handling to preserve query parameters; adjust TypeScript configuration to exclude specific directories; improve data grid components with new features and optimizations.
Build and Push Telemt Panel Docker Image / build-and-push (push) Successful in 3m58s
Build and Push Telemt Panel Docker Image / create-release (push) Skipped

This commit is contained in:
Denozordec committed 2026-08-04 19:22:26 +07:00
1 parent 9bebc9e92d
commit d35fa5a285
65 files changed
+6735 -1027

No files matched your search

@@ -1,12 +1,13 @@
"use client"
"use no memo"
import { createContext, type ReactNode, useContext, useMemo } from "react"
import {
type Column,
type ColumnFiltersState,
type RowData,
type SortingState,
type Table,
import { createContext, useContext, useMemo, useRef } from "react"
import type { ReactNode } from "react"
import type {
Column,
ColumnFiltersState,
RowData,
SortingState,
Table,
} from "@tanstack/react-table"
import { cn } from "@telemt/ui/lib/utils"
@@ -19,6 +20,7 @@ declare module "@tanstack/react-table" {
cellClassName?: string
skeleton?: ReactNode
expandedContent?: (row: TData) => ReactNode
autoSize?: boolean
}
}
@@ -55,6 +57,73 @@ export interface DataGridContextProps<TData extends object> {
table: Table<TData>
recordCount: number
isLoading: boolean
/**
* Internal coordinator for `meta.autoSize` columns. Lives at the core level
* so every table variant and viewport instance shares one application state.
*/
autoSize?: DataGridAutoSizeController
}
export type DataGridAutoSizeController = {
/**
* Grows the first visible `meta.autoSize` column by the given free space.
* Applies at most once per column id; safe to call from every viewport
* measurement. Returns true when a sizing update was dispatched.
*/
apply: (fillWidth: number) => boolean
}
function createDataGridAutoSizeController<TData extends object>(
table: Table<TData>
): DataGridAutoSizeController {
let applied: { columnId: string; base: number; grown: number } | null = null
return {
apply(fillWidth: number) {
const columnSizing = table.getState().columnSizing
// Re-arm after reset flows (double-click resetSize, resetColumnSizing,
// controlled state replacement) so the column re-fills instead of
// leaving a dead blank strip.
if (applied && columnSizing[applied.columnId] === undefined) {
applied = null
}
if (fillWidth <= 0) return false
const autoSizeColumn = table
.getVisibleLeafColumns()
.find(
(column) => column.columnDef.meta?.autoSize && column.getCanResize()
)
if (!autoSizeColumn || applied?.columnId === autoSizeColumn.id) {
return false
}
// Candidate switched (e.g. the grown column was hidden and another
// meta.autoSize column took over): revert the previous growth if the
// user hasn't manually resized that column since, so visibility
// toggles cannot ratchet the table wider than its container forever.
const revert =
applied && columnSizing[applied.columnId] === applied.grown
? applied
: null
const base = columnSizing[autoSizeColumn.id] ?? autoSizeColumn.getSize()
const grown = base + fillWidth
applied = { columnId: autoSizeColumn.id, base, grown }
table.setColumnSizing((old) => {
const next = { ...old, [autoSizeColumn.id]: grown }
if (revert && next[revert.columnId] === revert.grown) {
next[revert.columnId] = revert.base
}
return next
})
return true
},
}
}
export type DataGridRequestParams = {
@@ -83,6 +152,7 @@ export interface DataGridProps<TData extends object> {
rowRounded?: boolean
stripped?: boolean
headerBackground?: boolean
footerBackground?: boolean
headerBorder?: boolean
headerSticky?: boolean
width?: "auto" | "fixed"
@@ -126,36 +196,56 @@ function DataGridProvider<TData extends object>({
...props
}: DataGridProps<TData> & { table: Table<TData> }) {
const tableState = table.getState()
const resolvedColumnsResizeMode =
props.tableLayout?.columnsResizeMode ?? "onEnd"
// Keep resize mode aligned with the DataGrid contract every render so
// Latest-props ref: context reads always resolve fresh props through the
// getter below without the memoized context value depending on unstable
// ReactNode/function prop identities (inline emptyMessage/onRowClick would
// otherwise publish a new context value on every consumer render - at
// mousemove rate during a resize drag, piercing the body-rows memo).
const propsRef = useRef(props)
propsRef.current = props
// Re-assert an explicit tableLayout resize mode every render so
// consumer-level useReactTable options cannot flip it back between drags.
if (props.tableLayout?.columnsResizable) {
table.options.columnResizeMode = resolvedColumnsResizeMode
// Without one, the consumer's own tanstack columnResizeMode (default
// "onEnd") is honored.
if (
props.tableLayout?.columnsResizable &&
props.tableLayout.columnsResizeMode
) {
table.options.columnResizeMode = props.tableLayout.columnsResizeMode
}
// One autoSize coordinator per table instance so split header/body viewports
// cannot apply the growth twice.
const autoSize = useMemo(
() => createDataGridAutoSizeController(table),
[table]
)
// Memoize context value so consumers don't re-render during column resize.
// Column sizing state is intentionally excluded from deps -- CSS variables
// on the <table> element handle width updates without React re-renders.
// ReactNode/function props (messages, onRowClick) are also excluded: they
// are served fresh through the props getter, so unstable inline identities
// cannot invalidate the context value.
const value = useMemo(
() => ({
props,
get props() {
return propsRef.current
},
table,
recordCount: props.recordCount,
isLoading: props.isLoading || false,
autoSize,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
table,
autoSize,
props.recordCount,
props.isLoading,
props.loadingMode,
props.loadingMessage,
props.fetchingMoreMessage,
props.allRowsLoadedMessage,
props.emptyMessage,
props.onRowClick,
props.className,
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(props.tableLayout),
@@ -165,6 +255,7 @@ function DataGridProvider<TData extends object>({
tableState.pagination,
tableState.columnFilters,
tableState.rowSelection,
tableState.rowPinning,
tableState.expanded,
tableState.columnVisibility,
tableState.columnOrder,
@@ -194,12 +285,14 @@ function DataGrid<TData extends object>({
rowRounded: false,
stripped: false,
headerSticky: false,
headerBackground: true,
headerBackground: false,
footerBackground: false,
headerBorder: true,
width: "fixed",
columnsVisibility: false,
columnsResizable: false,
columnsResizeMode: "onEnd",
// columnsResizeMode has no default on purpose: when unset, the
// consumer's tanstack columnResizeMode (default "onEnd") is honored.
columnsPinnable: false,
columnsMovable: false,
columnsDraggable: false,
@@ -210,7 +303,10 @@ function DataGrid<TData extends object>({
base: "",
header: "",
headerRow: "",
headerSticky: "sticky top-0 z-15 bg-background/90 backdrop-blur-xs",
// z-40 keeps the sticky header above pinned body cells (zIndex 30 in
// getPinningStyles), which would otherwise paint over it while
// scrolling vertically with columnsPinnable enabled.
headerSticky: "sticky top-0 z-40 bg-background/90 backdrop-blur-xs",
body: "",
bodyRow: "",
footer: "",
@@ -246,21 +342,16 @@ function DataGrid<TData extends object>({
function DataGridContainer({
children,
className,
border = true,
}: {
children: ReactNode
className?: string
/** Accepted for backwards compatibility; currently has no effect. */
border?: boolean
}) {
return (
<div
data-slot="data-grid"
className={cn(
"w-full overflow-hidden",
border &&
"border-border rounded-lg border",
className
)}
className={cn("w-full overflow-hidden", className)}
>
{children}
</div>