feat(web): upgrade lists to ReUI Table v9 and EventCalendar
quality / commitlint (push) Skipped
quality / changes (push) Successful in 9s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 39s
quality / web (push) Successful in 1m29s
quality / go (push) Successful in 1m3s
quality / bird2 (push) Successful in 15s
CD / quality (push) Successful in 3m43s
CD / publish (push) Failing after 8m29s

Единый kitDataGridTableLayout и ResourcePage/FrameDataGrid на всех списках.
Календарь задач переведён на EventCalendar, lookup — на cascader, у операций появился вид доски.
Удалены settings-7 и самописные DataGridSection/toolbar.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
DenozordecandCursor committed 2026-08-23 01:38:56 +07:00
1 parent 927e27640a
commit d56405af7b
157 files changed
+39067 -6110

No files matched your search

@@ -1,35 +1,91 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo, useState, type ReactNode } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { Boxes } from 'lucide-react'
import { useMemo } from 'react'
import { Boxes, SearchIcon } from 'lucide-react'
import { CategoryBadge, ModeBadge } from '@/components/category-badge'
import { DataGridMutedCell, DataGridPrimaryCell } from '@/components/data-grid-cell'
import { DataGridSection } from '@/components/data-grid-shell'
import { DataGridMutedCell, DataGridNameCell } from '@/components/data-grid-cell'
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
import { useClientDataGrid } from '@/hooks/use-client-data-grid'
import type { FilterField, FilterQuery } from '@/components/reui/filters/filters-types'
import {
ResourcePage,
createTextFilterQuery,
renderSingleSelectedLabel,
type DataGridColumnDef,
} from '@/components/reui-kit'
import { moduleTypeRu } from '@/lib/ui-labels'
import type { ModuleRow } from '@/types/api'
const MODULE_TABS = [
{ id: 'all', label: 'Все' },
{ id: 'enabled', label: 'Вкл' },
{ id: 'disabled', label: 'Выкл' },
]
const TYPE_OPTIONS = [
{ value: 'AS_PREFIXES', label: moduleTypeRu('AS_PREFIXES') },
{ value: 'CDN_CIDRS', label: moduleTypeRu('CDN_CIDRS') },
{ value: 'DOMAINS', label: moduleTypeRu('DOMAINS') },
{ value: 'IP_RANGES', label: moduleTypeRu('IP_RANGES') },
]
const filterFields: FilterField[] = [
{
id: 'search',
label: 'Поиск',
icon: <SearchIcon className="size-3.5" aria-hidden />,
type: 'text',
placeholder: 'Название или тип…',
},
{
id: 'type',
label: 'Тип',
type: 'select',
searchable: false,
options: TYPE_OPTIONS,
renderValue: ({ values }) => renderSingleSelectedLabel(values, TYPE_OPTIONS),
},
]
function getFilterFieldValue(item: ModuleRow, field: string): unknown {
switch (field) {
case 'search':
return `${item.name} ${item.type} ${moduleTypeRu(item.type)} ${item.enabled ? 'включён' : 'выключен'}`
case 'type':
return item.type
default:
return undefined
}
}
function tabFilter(item: ModuleRow, tabId: string): boolean {
if (tabId === 'enabled') return item.enabled !== false
if (tabId === 'disabled') return item.enabled === false
return true
}
export function ModulesListGrid({
items,
isLoading = false,
title = 'Все модули',
actions,
}: {
items: ModuleRow[]
isLoading?: boolean
title?: string
actions?: ReactNode
}) {
const navigate = useNavigate()
const [filterQuery, setFilterQuery] = useState<FilterQuery>(() =>
createTextFilterQuery('search'),
)
const columns = useMemo<ColumnDef<ModuleRow>[]>(
const columns = useMemo<DataGridColumnDef<ModuleRow>[]>(
() => [
{
accessorKey: 'name',
header: ({ column }) => <DataGridColumnHeader column={column} title="Название" />,
cell: ({ row }) => (
<div className="flex items-center gap-2">
<Boxes className="size-4 shrink-0 text-muted-foreground" />
<DataGridPrimaryCell title={row.original.name} accent="primary" className="max-w-[280px]" />
</div>
<DataGridNameCell icon={Boxes} title={row.original.name} className="max-w-[280px]" />
),
meta: { headerTitle: 'Название' },
},
@@ -65,34 +121,32 @@ export function ModulesListGrid({
: '—'}
</DataGridMutedCell>
),
sortingFn: (a, b) => {
const av = a.original.last_refreshed_at ?? ''
const bv = b.original.last_refreshed_at ?? ''
return av.localeCompare(bv)
},
meta: { headerTitle: 'Обновлено' },
},
],
[],
)
const { table, globalFilter, setGlobalFilter, filteredCount } = useClientDataGrid({
data: items,
columns,
getSearchText: (row) => `${row.name} ${row.type} ${row.enabled ? 'включён' : 'выключен'}`,
getRowId: (row) => row.id,
})
return (
<DataGridSection
table={table}
recordCount={filteredCount}
<ResourcePage
title={title}
description="Маршрутные списки"
tabs={MODULE_TABS}
tabFilter={tabFilter}
filterFields={filterFields}
filterQuery={filterQuery}
onFilterQueryChange={setFilterQuery}
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
getFilterFieldValue={getFilterFieldValue}
columns={columns}
data={items}
getRowId={(row) => row.id}
isLoading={isLoading}
emptyMessage="Нет модулей"
searchValue={globalFilter}
onSearchChange={setGlobalFilter}
searchPlaceholder="Поиск модулей…"
onRowClick={(row) => void navigate({ to: '/modules/$moduleId', params: { moduleId: row.id } })}
primaryAction={actions}
onRowClick={(row) =>
void navigate({ to: '/modules/$moduleId', params: { moduleId: row.id } })
}
emptyState={{ title: 'Нет модулей' }}
/>
)
}