+
+
+
+
+ {result.status === "invalid" &&
{result.error}
}
+ {result.status === "draft" &&
Завершите условие. Текущий срез сохранён.
}
+
+}
diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md
index f27eabe9..08def05d 100644
--- a/docs/ux-ui-progress.md
+++ b/docs/ux-ui-progress.md
@@ -338,6 +338,14 @@ BGP, backups и BackupsHistory используют FilterQuery и соврем
Квота после подготовки: 49% пятичасовой и 97% недельной использовано. Сохранить подготовку локальным коммитом без push. Чужие изменения skill/AGENTS/.codex не включать.
+## Этап 4 — блок 13: подключение Filters статистики (2026-09-14)
+
+StatisticsFilters подключён вместо старого массива Filter. Поля предлагают только is, адаптер дополнительно отклоняет неподдерживаемые выражения. Локальный черновик не изменяет URL до явного «Применить срез»; invalid/draft блокируют применение и сохраняют действующие срезы. Ключ редактора зависит от URL-срезов, поэтому внешний переход сбрасывает черновик. Старые функции slicesToFilters/filtersToSlices страницы удалены.
+
+TypeScript до и после первого браузерного прогона и scoped ESLint прошли. Расширенный браузерный сценарий статистики прошёл: сортировка/переход в срез, восстановление фильтра из URL, редактирование без изменения URL, применение, сброс, отклонение RUS без обрезания до RU и внешняя смена страны на DE. В первом расширенном прогоне исправлен неоднозначный локатор alert; повтор — 1 passed. Проверки через HTTP-моки.
+
+На последнем чтении использовано 62% пятичасовой и 99% недельной квоты. Старые Filters/fallback/helper ещё физически остаются, поэтому этап 4 НЕ завершён. Следующий шаг — удалить неиспользуемый fallback DataPageToolbar и старые Filters/evaluator после проверки импортов, обновить тест совместимости, затем выполнить полную регрессию и production build. Сохранить этот блок локальным коммитом без push.
+
## Карта миграции (исходная инвентаризация этапа 0)
Текущая связка: @tanstack/react-table ^8.21.3, @tanstack/react-virtual ^3.14.4, base-nova. Актуальный ReUI MCP ранее подтвердил useTable/dataGridFeatures для Data Grid и дерево FilterQuery вместо filters/onChange для Filters. Перед установкой перепроверить совместимые версии, сохранить семантику существующих фильтров (простые условия → AND) и закрепить lockfile.
diff --git a/lib/data-filters/statistics-filter-fields.ts b/lib/data-filters/statistics-filter-fields.ts
index 2506d20e..14751d85 100644
--- a/lib/data-filters/statistics-filter-fields.ts
+++ b/lib/data-filters/statistics-filter-fields.ts
@@ -1,46 +1,52 @@
-import type { FilterFieldConfig } from "@/components/reui/filters"
+import type { FilterField } from "@/components/reui/filters/filters-types"
-export const STATISTICS_FILTER_FIELDS: FilterFieldConfig[] = [
+export const STATISTICS_FILTER_FIELDS: FilterField[] = [
{
- key: "country",
+ id: "country",
label: "Страна",
type: "text",
placeholder: "ISO, напр. US",
defaultOperator: "is",
+ operators: [{ value: "is", label: "равно" }],
},
{
- key: "service",
+ id: "service",
label: "Сервис",
type: "text",
placeholder: "https, dns…",
defaultOperator: "is",
+ operators: [{ value: "is", label: "равно" }],
},
{
- key: "asn",
+ id: "asn",
label: "ASN",
type: "text",
placeholder: "номер ASN",
defaultOperator: "is",
+ operators: [{ value: "is", label: "равно" }],
},
{
- key: "serverId",
+ id: "serverId",
label: "Сервер",
type: "text",
placeholder: "id сервера",
defaultOperator: "is",
+ operators: [{ value: "is", label: "равно" }],
},
{
- key: "userId",
+ id: "userId",
label: "Пользователь",
type: "text",
placeholder: "id пользователя",
defaultOperator: "is",
+ operators: [{ value: "is", label: "равно" }],
},
{
- key: "iface",
+ id: "iface",
label: "Интерфейс",
type: "text",
placeholder: "ether1",
defaultOperator: "is",
+ operators: [{ value: "is", label: "равно" }],
},
]
diff --git a/tests/e2e/migrated-tables.spec.ts b/tests/e2e/migrated-tables.spec.ts
index 96219c12..a64e6e1e 100644
--- a/tests/e2e/migrated-tables.spec.ts
+++ b/tests/e2e/migrated-tables.spec.ts
@@ -107,4 +107,25 @@ test("Статистика: CompactDataGrid сохраняет 12 строк, с
await expect(rows.first()).toContainText("Пользователь 12")
await rows.first().click()
await expect(page).toHaveURL(/userId=user-12/)
+ await expect(page.getByRole("button", { name: "user-12", exact: true })).toBeVisible()
+ await page.getByRole("button", { name: "user-12", exact: true }).click()
+ await page.getByPlaceholder("id пользователя").fill("user-2")
+ await page.getByPlaceholder("id пользователя").press("Enter")
+ await expect(page).toHaveURL(/userId=user-12/)
+ await page.getByRole("button", { name: "Применить срез", exact: true }).click()
+ await expect(page).toHaveURL(/userId=user-2/)
+ await page.getByRole("button", { name: "Очистить", exact: true }).click()
+ await expect(page).toHaveURL(/userId=user-2/)
+ await page.getByRole("button", { name: "Применить срез", exact: true }).click()
+ await expect(page).not.toHaveURL(/userId=/)
+ await page.goto("/statistics?from=2026-09-01&to=2026-09-13&country=RU")
+ await page.getByRole("button", { name: "RU", exact: true }).click()
+ await page.getByPlaceholder("ISO, напр. US").fill("RUS")
+ await page.getByPlaceholder("ISO, напр. US").press("Enter")
+ await expect(page.getByRole("alert").filter({ hasText: "Укажите двухбуквенный код страны." })).toBeVisible()
+ await expect(page.getByRole("button", { name: "Применить срез", exact: true })).toBeDisabled()
+ await expect(page).toHaveURL(/country=RU(?:&|$)/)
+ await page.evaluate(() => window.history.pushState(null, "", "/statistics?country=DE"))
+ await expect(page.getByRole("button", { name: "DE", exact: true })).toBeVisible()
+ await expect(page.getByText("Укажите двухбуквенный код страны.", { exact: true })).toHaveCount(0)
})