fix(ui): корректно отображать историю в карточках трафика
This commit is contained in:
1 parent
ccbc1aa029
commit
f3568d836b
5 files changed
+71
-45
No files matched your search
@@ -0,0 +1,37 @@
|
||||
import { Fragment } from "react"
|
||||
|
||||
/** Compact RX/TX history shared by traffic cards. Gaps remain gaps. */
|
||||
export function TrafficMiniChart({ rx, tx, height = 44 }: { rx: number[]; tx: number[]; height?: number }) {
|
||||
const valid = (value: number) => Number.isFinite(value) && value >= 0
|
||||
const values = [...rx, ...tx].filter(valid)
|
||||
if (!values.length) return <p className="text-xs text-muted-foreground">Нет истории трафика</p>
|
||||
const width = 300
|
||||
const count = Math.max(rx.length, tx.length)
|
||||
const max = Math.max(...values, 1) * 1.1
|
||||
const segments = (series: number[]) => {
|
||||
const result: Array<Array<{ x: number; y: number }>> = []
|
||||
let current: Array<{ x: number; y: number }> = []
|
||||
series.forEach((value, index) => {
|
||||
if (!valid(value)) { current = []; return }
|
||||
if (!current.length) result.push(current)
|
||||
current.push({ x: count <= 1 ? width / 2 : index / (count - 1) * width, y: height - value / max * height })
|
||||
})
|
||||
return result
|
||||
}
|
||||
return (
|
||||
<svg role="img" aria-label="Мини-график истории RX/TX" viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height, display: "block" }} preserveAspectRatio="none">
|
||||
{([[rx, "var(--chart-rx)"], [tx, "var(--chart-tx)"]] as const).map(([series, color], direction) => (
|
||||
<g key={color} data-direction={direction === 0 ? "rx" : "tx"}>
|
||||
{segments(series).map((points, index) => {
|
||||
if (points.length === 1) return <circle key={index} cx={points[0].x} cy={points[0].y} r="1.5" fill={color} />
|
||||
const line = points.map(p => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ")
|
||||
return <Fragment key={index}>
|
||||
<path d={`M ${points[0].x},${height} L ${line} L ${points[points.length - 1].x},${height} Z`} fill={color} fillOpacity={0.12} />
|
||||
<polyline points={line} fill="none" stroke={color} strokeWidth="1.4" strokeLinejoin="round" />
|
||||
</Fragment>
|
||||
})}
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import type { FlowMonthlyDto, FlowAnalyticsDto, FlowBreakdownRow, FlowEntityCard
|
||||
import { ArrowDownIcon, ArrowUpIcon, GitBranchIcon, GlobeIcon, LayersIcon, NetworkIcon, RouteIcon, ShieldIcon, UsersIcon } from "lucide-react"
|
||||
import { Badge } from "@/components/reui/badge"
|
||||
import { KpiStatGrid, type KpiStatItem } from "@/components/reui-kit/kpi-stat-grid"
|
||||
import { TrafficMiniChart } from "@/components/reui-kit/traffic-mini-chart"
|
||||
import { TrafficRxTxChart } from "@/components/reui-kit/traffic-rx-tx-chart"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
@@ -46,27 +47,6 @@ const RANGE_LABELS: Record<string, string> = {
|
||||
"30d": "Текущий месяц",
|
||||
}
|
||||
|
||||
function MiniAreaChart({ rx, tx, height = 44 }: { rx: number[]; tx: number[]; height?: number }) {
|
||||
const W = 300
|
||||
const H = height
|
||||
const maxVal = Math.max(...rx, ...tx, 1) * 1.1
|
||||
const xAt = (i: number) => (rx.length <= 1 ? 0 : (i / (rx.length - 1)) * W)
|
||||
const yAt = (v: number) => H - (v / maxVal) * H
|
||||
const area = (arr: number[]) => {
|
||||
const pts = arr.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`).join(" L ")
|
||||
return `M 0,${H} L ${pts} L ${W},${H} Z`
|
||||
}
|
||||
const line = (arr: number[]) => arr.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`).join(" ")
|
||||
return (
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-11" preserveAspectRatio="none">
|
||||
<path d={area(rx)} fill="var(--chart-rx)" fillOpacity="0.15" />
|
||||
<polyline points={line(rx)} fill="none" stroke="var(--chart-rx)" strokeWidth="1.5" />
|
||||
{tx.some((v) => v > 0) ? (
|
||||
<polyline points={line(tx)} fill="none" stroke="var(--chart-tx)" strokeWidth="1.5" />
|
||||
) : null}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function FlowEntityCardView({
|
||||
card,
|
||||
@@ -97,7 +77,7 @@ export function FlowEntityCardView({
|
||||
{card.site}
|
||||
</span>
|
||||
</div>
|
||||
<MiniAreaChart rx={card.rxSeries} tx={card.txSeries} />
|
||||
<TrafficMiniChart rx={card.rxSeries} tx={card.txSeries} />
|
||||
<div className="flex justify-between mt-2 gap-2">
|
||||
<div className="flex items-center gap-1 text-[11px]">
|
||||
<ArrowDownIcon className="size-3 text-success" />
|
||||
|
||||
Reference in new issue
Block a user