fix(ui): адаптировать формы серверов и прокрутку ресурсов
This commit is contained in:
1 parent
9cd15528c9
commit
ca9474ac83
7 files changed
+74
-11
No files matched your search
@@ -720,7 +720,7 @@ function ServersPageContent() {
|
||||
|
||||
{/* ══ Sheet: Add / Edit Server ══════════════════════════════════════════ */}
|
||||
<Sheet open={open} onOpenChange={closeForm}>
|
||||
<SheetContent side="right" showCloseButton={!saving} className="w-full sm:max-w-lg flex flex-col gap-0 p-0">
|
||||
<SheetContent side="right" showCloseButton={!saving} className="data-[side=right]:w-full sm:max-w-lg flex flex-col gap-0 p-0">
|
||||
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
|
||||
<SheetTitle>{sheetMode === "edit" ? "Редактировать сервер" : "Новый сервер"}</SheetTitle>
|
||||
<SheetDescription>MikroTik RouterOS · Web API (REST)</SheetDescription>
|
||||
@@ -731,7 +731,7 @@ function ServersPageContent() {
|
||||
{!detailsReady && !detailsLoading && editingId && <Button type="button" variant="outline" onClick={() => { const server = serverList.find(s => s.id === editingId); if (server) void openEdit(server) }}>Повторить загрузку</Button>}
|
||||
</div>}
|
||||
|
||||
<fieldset disabled={!canWrite || saving || !detailsReady} className="flex min-h-0 flex-1 flex-col" aria-busy={saving || detailsLoading}>
|
||||
<fieldset disabled={!canWrite || saving || !detailsReady} className="flex min-h-0 min-w-0 flex-1 flex-col" aria-busy={saving || detailsLoading}>
|
||||
<Stepper value={sheetStep} onValueChange={setSheetStep} className="flex-1 flex flex-col min-h-0 px-6 py-5">
|
||||
<StepperNav className="mb-5">
|
||||
<StepperItem step={1}>
|
||||
@@ -772,6 +772,7 @@ function ServersPageContent() {
|
||||
|
||||
<FormField label="Тип узла" required>
|
||||
<SegmentedControl
|
||||
className="max-w-full flex-wrap"
|
||||
value={form.type}
|
||||
onChange={v => set("type", v)}
|
||||
options={[
|
||||
@@ -945,7 +946,7 @@ function ServersPageContent() {
|
||||
</Stepper>
|
||||
</fieldset>
|
||||
|
||||
<SheetFooter className="px-6 py-4 border-t shrink-0 flex-row gap-2">
|
||||
<SheetFooter className="px-6 py-4 border-t shrink-0 flex-row flex-wrap gap-2">
|
||||
<SheetClose render={<Button variant="outline" disabled={saving} />}>Отмена</SheetClose>
|
||||
{sheetStep > 1 && (
|
||||
<Button variant="outline" disabled={saving || !detailsReady} onClick={() => setSheetStep((s) => s - 1)}>Назад</Button>
|
||||
|
||||
@@ -1030,11 +1030,7 @@ function ResourcesTab({ resources, serversList, liveApi }: { resources: ServerRe
|
||||
{/* ── Table ─────────────────────────────────────────────────────────── */}
|
||||
<DataPageCard>
|
||||
<DataPageToolbar
|
||||
segmented={{
|
||||
value: typeFilter,
|
||||
onChange: setTypeFilter,
|
||||
options: typeOpts,
|
||||
}}
|
||||
leading={<SegmentedControl className="max-w-full flex-wrap" value={typeFilter} onChange={setTypeFilter} options={typeOpts} />}
|
||||
search={resSearch}
|
||||
onSearchChange={setResSearch}
|
||||
searchPlaceholder="Поиск по имени, площадке…"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
|
||||
import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
|
||||
|
||||
import { useMemo } from "react"
|
||||
import {
|
||||
@@ -377,7 +377,13 @@ function UptimeResourcesDataGrid({ rows }: UptimeResourcesDataGridProps) {
|
||||
"[&:has([data-resource-crit=true])]:bg-red-500/3",
|
||||
),
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<DataGridContainer border={false} className="w-full rounded-none border-0">
|
||||
<DataGridScrollArea orientation="horizontal">
|
||||
<DataGridTable />
|
||||
</DataGridScrollArea>
|
||||
</DataGridContainer>
|
||||
</DataGridShell>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
## Приёмка 7.5В — формы серверов и таблица Resources (2026-09-23)
|
||||
|
||||
- Форма сервера на телефоне занимает полную ширину; fieldset получил min-w-0, выбор типа узла и кнопки подвала допускают перенос. На 320 px прежде выходили за экран поля, страны и последний шаг. Проверены четыре шага редактирования, выбор Home Router, добавленный WAN, раскрытые дополнительные поля и Escape на 320/390/1440 px. Существующие проверки создания и ошибок сохранения также прошли.
|
||||
- Resources: фильтры типов переносятся внутри панели. Таблица обёрнута установленным ReUI DataGridScrollArea с orientation=horizontal внутри DataGridContainer: правые колонки доступны при прокрутке, общая страница не расширяется. Общая оболочка DataGridShell, vendored ReUI и тема не изменены.
|
||||
- Проверки: 55 профильных E2E серверов/мониторинга прошли; затем ещё 2 уточнённые проверки Resources на 320/390 px подтвердили доступ к значению последней колонки, поиск, пустой результат и восстановление строки. Скриншоты WAN, дополнительных полей и прокрученной таблицы просмотрены. Production build и TypeScript прошли: 34 статические страницы. Lint изменённых файлов чистый, кроме исходных 1 ошибки и 6 предупреждений uptime. ReUI API и audit checklist проверены.
|
||||
- Границы: HTTP-моки, без реального RouterOS/JWT; сортировка в адаптивном тесте проверена на одной строке, порядок нескольких строк этим тестом не доказывается. Сквозной сценарий и смена backend/mode при незавершённой операции остаются следующими пунктами 7.5. Этап 7 целиком ещё не завершён. На контрольной точке осталось 23% пятичасовой и 72% недельной квоты. Локальный коммит без push.
|
||||
|
||||
## Приёмка 7.5Б — адаптивность мониторинга и форм (2026-09-23)
|
||||
|
||||
- Формы Ping/Speed используют всю ширину телефона; min-w-0 у fieldset устраняет выход полей за границы панели. Параметры Speed идут вертикально также на ПК: ширины боковой панели недостаточно для трёх колонок без перекрытия Direction и длительности.
|
||||
|
||||
@@ -61,7 +61,7 @@ URL — источник сохранённого состояния. Back/Forwa
|
||||
3. **7.4 — формы, детали и полный контекст мониторинга.** Восстановление вкладки/фильтров из URL, понятные единицы и подписи, возврат к списку после действия. Не дублировать сохранённые исправления этапа 1; проверить загрузку, отмену, 400/403/500, timeout и повторное нажатие.
|
||||
4. **7.5 — приёмка этапа.** Сценарий «проблема → сервер/проверка → действие → возврат» с сохранением контекста; профильные E2E, права в HTTP-моках, TypeScript, lint и build. Реальные JWT/RouterOS отдельно в тестовом окружении.
|
||||
|
||||
Следующий исполняемый шаг: 7.5 — адаптивность форм серверов и таблицы Resources после прокрутки, сквозные переходы, смена backend/mode при незавершённой операции и фиксация ограничений. Формы Ping/Speed и вкладки проверены на 320/390/1440 px; устранены переполнение форм, перекрытие параметров Speed и сдвиг страницы при выборе вкладки. URL-контекст вкладки, Ping и Resources выполнен. Учесть границы HTTP-моков, экспорт серверов и ограничения сетевых операций из журнала; этап 7 целиком пока не завершён.
|
||||
Следующий исполняемый шаг: 7.5 — сквозные переходы дашборд → объект → действие → возврат, смена backend/mode при незавершённой операции и фиксация ограничений. Формы серверов проверены на 320/390/1440 px, Resources после прокрутки — на 320/390 px; исправлены переполнение формы и обрезка фильтров/таблицы. Формы Ping/Speed и вкладки проверены на 320/390/1440 px; устранены переполнение форм, перекрытие параметров Speed и сдвиг страницы при выборе вкладки. URL-контекст вкладки, Ping и Resources выполнен. Учесть границы HTTP-моков, экспорт серверов и ограничения сетевых операций из журнала; этап 7 целиком пока не завершён.
|
||||
|
||||
## Решение по синхронизации URL (7.1)
|
||||
|
||||
|
||||
@@ -509,3 +509,27 @@ test("права серверов: отзыв перед сохранением
|
||||
await expect(page.getByRole("button", { name: "Сохранить", exact: true })).toBeDisabled()
|
||||
expect(writes).toBe(0)
|
||||
})
|
||||
|
||||
for (const width of [320, 390, 1440]) {
|
||||
test(`приёмка формы сервера ${width}px: все шаги и отмена`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 844 })
|
||||
await setup(page)
|
||||
await edit(page)
|
||||
const dialog = page.getByRole('dialog')
|
||||
await dialog.getByRole("button", { name: "Home Router", exact: true }).click()
|
||||
for (let step = 1; step <= 4; step++) {
|
||||
if (step === 2) await dialog.getByRole("button", { name: "Добавить WAN-аплинк", exact: true }).click()
|
||||
if (step === 4) await dialog.getByRole("button", { name: "Дополнительно", exact: true }).last().click()
|
||||
await expect(dialog.getByRole('button', { name: 'Отмена', exact: true })).toBeInViewport()
|
||||
await expect.poll(() => dialog.locator('button, input').evaluateAll(elements => elements.filter(el => {
|
||||
const rect = el.getBoundingClientRect()
|
||||
return rect.width > 0 && (rect.left < -1 || rect.right > innerWidth + 1)
|
||||
}).map(el => el.textContent || el.tagName))).toEqual([])
|
||||
await page.screenshot({ path: `test-results/server-form-${width}-${step}.png`, animations: 'disabled' })
|
||||
if (step < 4) await dialog.getByRole('button', { name: 'Далее', exact: true }).click()
|
||||
}
|
||||
await expect(dialog.getByRole('button', { name: 'Сохранить', exact: true })).toBeInViewport()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(dialog).toBeHidden()
|
||||
})
|
||||
}
|
||||
@@ -612,3 +612,32 @@ for (const width of [320, 390, 1440]) {
|
||||
await page.screenshot({ path: `test-results/speed-${width}.png`, animations: "disabled" })
|
||||
})
|
||||
}
|
||||
|
||||
for (const width of [320, 390]) {
|
||||
test(`Resources ${width}px: таблица после прокрутки и поиск`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 844 })
|
||||
await resourceSetup(page)
|
||||
const search = page.getByPlaceholder('Поиск по имени, площадке…')
|
||||
await search.scrollIntoViewIfNeeded()
|
||||
await search.fill('Источник')
|
||||
await expect(page.locator('tr[data-row-id]')).toHaveCount(1)
|
||||
const cpu = page.getByRole('button', { name: 'CPU', exact: true })
|
||||
await cpu.click()
|
||||
await expect(cpu).toBeInViewport()
|
||||
const viewport = page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]')
|
||||
await viewport.evaluate(el => { el.scrollLeft = el.scrollWidth })
|
||||
await expect(page.getByRole("columnheader").last()).toBeInViewport()
|
||||
const lastValue = page.locator("tr[data-row-id] td").last().locator("span").last()
|
||||
await lastValue.scrollIntoViewIfNeeded()
|
||||
await expect(lastValue).toBeInViewport()
|
||||
const home = page.getByRole("button", { name: "Home Router", exact: true })
|
||||
const bounds = await home.boundingBox()
|
||||
expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(width)
|
||||
await page.screenshot({ path: `test-results/resources-table-${width}.png`, animations: 'disabled' })
|
||||
await search.fill('нет-совпадений')
|
||||
await expect(page.getByText('Ничего не найдено', { exact: true })).toBeVisible()
|
||||
await search.clear()
|
||||
await expect(page.locator('tr[data-row-id]')).toHaveCount(1)
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user