fix(ui): адаптировать панели трафика для телефона
This commit is contained in:
1 parent
8ac40ba78a
commit
3dc85a22e9
4 files changed
+43
-14
No files matched your search
+11
-11
@@ -529,9 +529,9 @@ function DetailHeader({ range, setRange, children }: {
|
|||||||
range: Range; setRange: (r: Range) => void; children: ReactNode
|
range: Range; setRange: (r: Range) => void; children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start justify-between mb-3 gap-3">
|
<div className="flex flex-wrap items-start justify-between mb-3 gap-3">
|
||||||
<div className="flex items-center gap-2 flex-wrap min-w-0">{children}</div>
|
<div className="flex items-center gap-2 flex-wrap min-w-0">{children}</div>
|
||||||
<div className="flex gap-1 shrink-0">
|
<div className="flex flex-wrap gap-1">
|
||||||
{TRAFFIC_RANGE_KEYS.map((r) => (
|
{TRAFFIC_RANGE_KEYS.map((r) => (
|
||||||
<Button key={r} size="sm" variant={r === range ? "default" : "ghost"}
|
<Button key={r} size="sm" variant={r === range ? "default" : "ghost"}
|
||||||
className="h-7 px-2 text-xs" onClick={() => setRange(r)}>
|
className="h-7 px-2 text-xs" onClick={() => setRange(r)}>
|
||||||
@@ -1178,7 +1178,7 @@ export default function TrafficPage() {
|
|||||||
|
|
||||||
{/* ── mode tab bar ── */}
|
{/* ── mode tab bar ── */}
|
||||||
<div className="border-b bg-background shrink-0">
|
<div className="border-b bg-background shrink-0">
|
||||||
<div className="flex items-center px-6">
|
<div className="flex items-center overflow-x-auto px-3 sm:px-6">
|
||||||
{GROUP_MODES.map(({ mode, icon, label }) => (
|
{GROUP_MODES.map(({ mode, icon, label }) => (
|
||||||
<button key={mode} onClick={() => handleModeChange(mode)}
|
<button key={mode} onClick={() => handleModeChange(mode)}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -1193,7 +1193,7 @@ export default function TrafficPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 min-w-0 overflow-y-auto p-3 sm:p-6">
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
{!(effectiveMode === "flows" && range === "30d") && (!isLive || !(effectiveMode === "flows" ? liveError : trafficError)) && <KpiStatGrid
|
{!(effectiveMode === "flows" && range === "30d") && (!isLive || !(effectiveMode === "flows" ? liveError : trafficError)) && <KpiStatGrid
|
||||||
isLoading={isLive && (effectiveMode === "flows" ? liveBusy : trafficLoading)}
|
isLoading={isLive && (effectiveMode === "flows" ? liveBusy : trafficLoading)}
|
||||||
@@ -1219,7 +1219,7 @@ export default function TrafficPage() {
|
|||||||
{flowError}
|
{flowError}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-[300px_1fr] gap-5 items-start">
|
<div className="grid min-w-0 grid-cols-1 xl:grid-cols-[300px_minmax(0,1fr)] gap-5 items-start">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div role="group" aria-label="Источник потоков" className="flex gap-1">
|
<div role="group" aria-label="Источник потоков" className="flex gap-1">
|
||||||
<button
|
<button
|
||||||
@@ -1275,7 +1275,7 @@ export default function TrafficPage() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{range === "30d" && <p className="text-xs text-muted-foreground">Выбор сервера: активность за последние 24 часа. Месячный отчёт — справа.</p>}
|
{range === "30d" && <p className="text-xs text-muted-foreground">Выбор сервера: активность за последние 24 часа. Месячный отчёт — в деталях сервера.</p>}
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{sortedFlowCards.map((card) => (
|
{sortedFlowCards.map((card) => (
|
||||||
<FlowEntityCardView
|
<FlowEntityCardView
|
||||||
@@ -1292,8 +1292,8 @@ export default function TrafficPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Frame dense className="w-full flex flex-col">
|
<Frame dense className="w-full min-w-0 flex flex-col">
|
||||||
<FramePanel className="flex-1 px-5 pb-5 pt-5">
|
<FramePanel className="flex-1 min-w-0 p-3 sm:p-5">
|
||||||
{analyticsLoading && <p role="status">Загрузка аналитики…</p>}
|
{analyticsLoading && <p role="status">Загрузка аналитики…</p>}
|
||||||
{currentAnalytics?.error && <Alert variant="destructive" role="alert"><AlertTitle>Аналитика недоступна</AlertTitle><AlertDescription>{currentAnalytics.error}<Button size="sm" variant="outline" onClick={() => setAnalyticsRetry(value => value + 1)}>Повторить аналитику</Button></AlertDescription></Alert>}
|
{currentAnalytics?.error && <Alert variant="destructive" role="alert"><AlertTitle>Аналитика недоступна</AlertTitle><AlertDescription>{currentAnalytics.error}<Button size="sm" variant="outline" onClick={() => setAnalyticsRetry(value => value + 1)}>Повторить аналитику</Button></AlertDescription></Alert>}
|
||||||
<FlowAnalyticsDetail
|
<FlowAnalyticsDetail
|
||||||
@@ -1326,7 +1326,7 @@ export default function TrafficPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-[300px_1fr] gap-5 items-start">
|
<div className="grid min-w-0 grid-cols-1 xl:grid-cols-[300px_minmax(0,1fr)] gap-5 items-start">
|
||||||
{/* ── left panel ── */}
|
{/* ── left panel ── */}
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
|
|
||||||
@@ -1375,8 +1375,8 @@ export default function TrafficPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── detail panel ── */}
|
{/* ── detail panel ── */}
|
||||||
<Frame dense className="w-full flex flex-col">
|
<Frame dense className="w-full min-w-0 flex flex-col">
|
||||||
<FramePanel className="flex-1 px-5 pb-5 pt-5">
|
<FramePanel className="flex-1 min-w-0 p-3 sm:p-5">
|
||||||
{isLive && effectiveMode === "servers" && (
|
{isLive && effectiveMode === "servers" && (
|
||||||
<div className="mb-3 pb-3 border-b">
|
<div className="mb-3 pb-3 border-b">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ export function FlowAnalyticsDetail({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-start justify-between mb-3 gap-3">
|
<div className="flex flex-wrap items-start justify-between mb-3 gap-3">
|
||||||
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
||||||
<StatusDot status={card.status} />
|
<StatusDot status={card.status} />
|
||||||
<h2 className="text-base font-semibold truncate">{card.name}</h2>
|
<h2 className="text-base font-semibold truncate">{card.name}</h2>
|
||||||
@@ -383,7 +383,7 @@ export function FlowAnalyticsDetail({
|
|||||||
{card.site}
|
{card.site}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 shrink-0">
|
<div className="flex flex-wrap items-center gap-3 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Switch
|
<Switch
|
||||||
disabled={range === "30d"}
|
disabled={range === "30d"}
|
||||||
@@ -417,7 +417,7 @@ export function FlowAnalyticsDetail({
|
|||||||
Без mesh
|
Без mesh
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{RANGE_KEYS.filter(r => r !== "30d" || monthlyAvailable).map((r) => (
|
{RANGE_KEYS.filter(r => r !== "30d" || monthlyAvailable).map((r) => (
|
||||||
<button
|
<button
|
||||||
key={r}
|
key={r}
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
|
|
||||||
|
## Блок 8.4А — адаптивная оболочка трафика (2026-09-24)
|
||||||
|
|
||||||
|
- Общие списки/детали трафика и flow переходят в одну колонку на узких экранах; на широких — 300px + гибкая колонка. Отступы Frame адаптивны, заголовки и периоды переносятся, навигация разделов прокручивается по горизонтали.
|
||||||
|
- Flow-переключатели и периоды помещаются на телефоне. Подпись месячного отчёта больше не зависит от положения панели справа.
|
||||||
|
- 8 flow-E2E прошли: 5 сценариев данных и 3 ширины (320/390/1440) с обычной аналитикой и месячным отчётом, проверкой доступности кнопки периода и отсутствия горизонтального переполнения документа. Дополнительно прошли 7 прежних E2E обычного трафика. Lint панели/тестов и diff-check чистые. Скриншот 320 px просмотрен; снимки test-results/flow-monthly-{320,390,1440}.png не коммитятся. Этот блок меняет CSS-компоновку и подпись; отдельную production-сборку не повторяли после успешной сборки 8.3В.
|
||||||
|
- Этап 8.4 ещё не завершён: нужна профильная мобильная приёмка серверов/клиентов/интерфейсов, пустых состояний, единиц и длинных значений. Остались проверки смены backend/сервера и позднего SSE, валидация flow sample, месячный выбор архивного сервера. Не считать этап 8 принятым целиком.
|
||||||
|
- На контрольной точке использовано 94% пятичасового и 61% недельного лимита. Следующий шаг — мобильные сценарии обычного трафика на 320/390 px по этому журналу.
|
||||||
|
|
||||||
## Блок 8.3В — достоверный месячный отчёт (2026-09-24)
|
## Блок 8.3В — достоверный месячный отчёт (2026-09-24)
|
||||||
|
|
||||||
- «Текущий месяц» явно означает календарный месяц UTC. Удалён адаптер, заполнявший отсутствующие скорости, сессии и графики нулями. Месячный DTO отображается отдельно: объём, страны, сервисы и ASN; колонки скорости скрыты.
|
- «Текущий месяц» явно означает календарный месяц UTC. Удалён адаптер, заполнявший отсутствующие скорости, сессии и графики нулями. Месячный DTO отображается отдельно: объём, страны, сервисы и ASN; колонки скорости скрыты.
|
||||||
|
|||||||
@@ -102,3 +102,24 @@ test('Flow: месяц показывает только доступные аг
|
|||||||
await expect(page.getByRole('button', { name: 'Текущий месяц', exact: true })).toHaveCount(0)
|
await expect(page.getByRole('button', { name: 'Текущий месяц', exact: true })).toHaveCount(0)
|
||||||
await expect(page.getByRole('switch', { name: 'Без дублей' })).toBeEnabled()
|
await expect(page.getByRole('switch', { name: 'Без дублей' })).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for (const width of [320, 390, 1440]) {
|
||||||
|
test(`Flow: адаптивность ${width}px`, async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width, height: 900 })
|
||||||
|
await setup(page)
|
||||||
|
for (const monthly of [false, true]) {
|
||||||
|
if (monthly) {
|
||||||
|
await page.getByRole('button', { name: 'Текущий месяц', exact: true }).click()
|
||||||
|
await expect(page.getByRole('region', { name: 'Месячный отчёт' })).toBeVisible()
|
||||||
|
}
|
||||||
|
const button = page.getByRole('button', { name: 'Текущий месяц', exact: true })
|
||||||
|
const box = await button.boundingBox()
|
||||||
|
expect(box).not.toBeNull()
|
||||||
|
expect(box!.x).toBeGreaterThanOrEqual(0)
|
||||||
|
expect(box!.x + box!.width).toBeLessThanOrEqual(width + 1)
|
||||||
|
const overflow = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth + 1)
|
||||||
|
expect(overflow).toBe(false)
|
||||||
|
}
|
||||||
|
await page.screenshot({ path: `test-results/flow-monthly-${width}.png`, fullPage: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user