feat(dict): таблица справочника — hide tail columns по умолчанию (без H-scroll)

This commit is contained in:
Александр Зимин
2026-06-02 12:14:00 +00:00
parent 071315b058
commit b6190713e4
2 changed files with 44 additions and 10 deletions
+24 -9
View File
@@ -28,11 +28,20 @@ const DEFAULT_STATE: DictTableState = { sort: null, hidden: [] }
const storageKey = (dictName: string): string => `ord-dict-table:${dictName}`
const readState = (dictName: string): DictTableState => {
if (typeof window === 'undefined') return DEFAULT_STATE
/**
* Прочитать persisted state. Если в storage пусто (юзер ни разу не
* взаимодействовал с этим dict) → используем `fallbackHidden` как initial
* visibility (для info-dense schemas: hide tail columns по умолчанию,
* чтобы таблица помещалась в viewport без H-scroll). Любой toggle/setHidden
* запишет state в storage → дальнейшие посещения respect юзерский выбор
* вне зависимости от fallback.
*/
const readState = (dictName: string, fallbackHidden: string[]): DictTableState => {
const fallback: DictTableState = { sort: null, hidden: fallbackHidden }
if (typeof window === 'undefined') return fallback
try {
const raw = window.localStorage.getItem(storageKey(dictName))
if (!raw) return DEFAULT_STATE
if (!raw) return fallback
const parsed = JSON.parse(raw) as Partial<DictTableState>
return {
sort:
@@ -46,7 +55,7 @@ const readState = (dictName: string): DictTableState => {
: [],
}
} catch {
return DEFAULT_STATE
return fallback
}
}
@@ -60,13 +69,19 @@ const writeState = (dictName: string, state: DictTableState): void => {
}
}
export function useDictTableState(dictName: string) {
const [state, setState] = useState<DictTableState>(() => readState(dictName))
export function useDictTableState(dictName: string, defaultHidden: string[] = []) {
// Stable key для defaultHidden чтобы useEffect не зацикливался на каждый
// render (массив literal каждый раз new reference). Caller обычно строит
// defaultHidden из useMemo, но guard всё равно нужен.
const defaultKey = defaultHidden.join(',')
const [state, setState] = useState<DictTableState>(() => readState(dictName, defaultHidden))
// Перечитать когда смена dictName (navigation между справочниками).
// Перечитать когда смена dictName (navigation между справочниками) или
// когда меняется набор defaultHidden (schema reload → новые extra columns).
useEffect(() => {
setState(readState(dictName))
}, [dictName])
setState(readState(dictName, defaultHidden))
// eslint-disable-next-line react-hooks/exhaustive-deps -- defaultKey stable
}, [dictName, defaultKey])
useEffect(() => {
writeState(dictName, state)
@@ -372,9 +372,28 @@ function DictionaryDetail() {
setPage(0)
}, [search, scopeFilter, aoi])
// First-visit defaults: с info-dense schemas (10+ scalar полей) полная
// таблица не помещается в viewport → H-scroll. Hide хвост по умолчанию
// — показываем businessKey, name, scope, validFrom + первые 2 extra
// (обычно самые важные ключи). Юзер открывает menu Колонки и сам
// добавляет нужные — выбор persisted в localStorage и при следующем
// визите fallback уже не применяется.
const defaultHiddenExtras = useMemo<string[]>(() => {
const props = detailQuery.data?.schemaJson?.properties ?? {}
const eligible: string[] = []
for (const [key, prop] of Object.entries(props)) {
if (key === 'name' || key === 'code' || key === 'businessKey') continue
if (Boolean(prop['x-localized'])) continue
const t = prop.type
if (t === 'array' || t === 'object') continue
eligible.push(key)
}
return eligible.slice(2) // первые 2 видны, остальные default-hidden
}, [detailQuery.data?.schemaJson])
// Table state — sort (cycle none→asc→desc) + hidden columns persisted в
// localStorage per dict через useDictTableState.
const dictTable = useDictTableState(name)
const dictTable = useDictTableState(name, defaultHiddenExtras)
// Getter для applySort — извлекает значение по col key из record.
// Изолирован чтобы sort работал унифицировано для всех типов колонок.