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>
153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
import { useMemo, useState, type ReactNode } from 'react'
|
|
import { useNavigate } from '@tanstack/react-router'
|
|
import { Boxes, SearchIcon } from 'lucide-react'
|
|
|
|
import { CategoryBadge, ModeBadge } from '@/components/category-badge'
|
|
import { DataGridMutedCell, DataGridNameCell } from '@/components/data-grid-cell'
|
|
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
|
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<DataGridColumnDef<ModuleRow>[]>(
|
|
() => [
|
|
{
|
|
accessorKey: 'name',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Название" />,
|
|
cell: ({ row }) => (
|
|
<DataGridNameCell icon={Boxes} title={row.original.name} className="max-w-[280px]" />
|
|
),
|
|
meta: { headerTitle: 'Название' },
|
|
},
|
|
{
|
|
accessorKey: 'type',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Тип" />,
|
|
cell: ({ row }) => <CategoryBadge>{moduleTypeRu(row.original.type)}</CategoryBadge>,
|
|
meta: { headerTitle: 'Тип' },
|
|
},
|
|
{
|
|
accessorKey: 'priority',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Приоритет" />,
|
|
cell: ({ row }) => (
|
|
<span className="font-mono text-sm tabular-nums">{row.original.priority}</span>
|
|
),
|
|
meta: { headerTitle: 'Приоритет' },
|
|
},
|
|
{
|
|
id: 'enabled',
|
|
accessorFn: (row) => (row.enabled ? 'enabled' : 'disabled'),
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Состояние" />,
|
|
cell: ({ row }) => <ModeBadge enabled={row.original.enabled} />,
|
|
meta: { headerTitle: 'Состояние' },
|
|
},
|
|
{
|
|
id: 'last_refreshed_at',
|
|
accessorFn: (row) => row.last_refreshed_at ?? '',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Обновлено" />,
|
|
cell: ({ row }) => (
|
|
<DataGridMutedCell>
|
|
{row.original.last_refreshed_at
|
|
? new Date(row.original.last_refreshed_at).toLocaleString('ru-RU')
|
|
: '—'}
|
|
</DataGridMutedCell>
|
|
),
|
|
meta: { headerTitle: 'Обновлено' },
|
|
},
|
|
],
|
|
[],
|
|
)
|
|
|
|
return (
|
|
<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}
|
|
primaryAction={actions}
|
|
onRowClick={(row) =>
|
|
void navigate({ to: '/modules/$moduleId', params: { moduleId: row.id } })
|
|
}
|
|
emptyState={{ title: 'Нет модулей' }}
|
|
/>
|
|
)
|
|
}
|