feat(statistics): добавить BI-разрез и сводную матрицу
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-test (push) Successful in 2m23s
Docker images / frontend-image (push) Successful in 3m30s
Docker images / updater-image (push) Successful in 52s
Docker images / backend-image (push) Successful in 3m8s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 14s
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-test (push) Successful in 2m23s
Docker images / frontend-image (push) Successful in 3m30s
Docker images / updater-image (push) Successful in 52s
Docker images / backend-image (push) Successful in 3m8s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 14s
Сопоставить клиентов с ifIndex как на карте трафика, чтобы KPI пользователей не обнулялся. На экране — разрез остальных измерений и сводная матрица. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"use client"
|
||||
|
||||
import { useMemo } from "react"
|
||||
import { CompactDataGrid, type CompactDataGridColumn } from "@/components/data-grids/compact-data-grid"
|
||||
import { formatBytes } from "@/lib/fmt-rate"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { StatisticsPivotDto } from "@mmapp/contracts/statistics"
|
||||
|
||||
interface PivotGridRow {
|
||||
id: string
|
||||
label: string
|
||||
total: number
|
||||
[key: string]: string | number
|
||||
}
|
||||
|
||||
export function StatisticsPivotGrid({
|
||||
data,
|
||||
onCellClick,
|
||||
isLoading,
|
||||
}: {
|
||||
data: StatisticsPivotDto
|
||||
onCellClick?: (rowId: string, colId: string) => void
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
const rows: PivotGridRow[] = useMemo(
|
||||
() =>
|
||||
data.rows.map((r) => {
|
||||
const next: PivotGridRow = { id: r.id, label: r.label, total: r.total }
|
||||
for (const col of data.columns) {
|
||||
next[`c:${col.id}`] = r.cells[col.id] ?? 0
|
||||
}
|
||||
return next
|
||||
}),
|
||||
[data],
|
||||
)
|
||||
|
||||
const columns: CompactDataGridColumn<PivotGridRow>[] = [
|
||||
{
|
||||
id: "label",
|
||||
header: "Измерение",
|
||||
accessorKey: "label",
|
||||
cell: (row) => <span className="truncate font-medium">{row.label}</span>,
|
||||
},
|
||||
...data.columns.map((col) => ({
|
||||
id: `c:${col.id}`,
|
||||
header: col.label,
|
||||
accessorKey: `c:${col.id}` as const,
|
||||
cell: (row: PivotGridRow) => {
|
||||
const value = Number(row[`c:${col.id}`] ?? 0)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"tabular-nums text-left hover:underline",
|
||||
col.id === "__other__" || row.id === "__other__" ? "text-muted-foreground" : "",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (row.id === "__other__" || col.id === "__other__") return
|
||||
onCellClick?.(row.id, col.id)
|
||||
}}
|
||||
>
|
||||
{data.metric === "packets" ? value.toLocaleString("ru-RU") : formatBytes(value)}
|
||||
</button>
|
||||
)
|
||||
},
|
||||
})),
|
||||
{
|
||||
id: "total",
|
||||
header: "Итого",
|
||||
accessorKey: "total",
|
||||
cell: (row) => (
|
||||
<span className="tabular-nums font-medium">
|
||||
{data.metric === "packets" ? row.total.toLocaleString("ru-RU") : formatBytes(row.total)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<CompactDataGrid
|
||||
data={rows}
|
||||
columns={columns}
|
||||
isLoading={isLoading}
|
||||
emptyTitle="Нет данных сводной"
|
||||
emptyDescription="Выберите разные измерения строк и колонок."
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user