fix(web): KPI в DetailPanel через KpiStatGrid hybrid (stats-12)
Отдельные карточки с icon tile, крупным value и Badge hint — как на дашборде. Списки, агенты, правила. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -6,13 +6,23 @@ import {
|
||||
FramePanel,
|
||||
FrameTitle,
|
||||
} from '@/components/reui/frame'
|
||||
import {
|
||||
KpiStatGrid,
|
||||
type KpiStatCard,
|
||||
type KpiStatVariant,
|
||||
} from '@/components/reui-kit/kpi-stat-grid'
|
||||
import { cn } from '@evofw/ui/lib/utils'
|
||||
|
||||
export interface DetailMetricCard {
|
||||
id: string
|
||||
icon: ReactNode
|
||||
label: string
|
||||
/** Primary numeric / text value (shown large). */
|
||||
description: string
|
||||
/** Outline badge on the right (hybrid KPI). */
|
||||
hint?: string
|
||||
iconClassName?: string
|
||||
variant?: KpiStatVariant
|
||||
footer?: ReactNode
|
||||
}
|
||||
|
||||
@@ -22,7 +32,11 @@ interface DetailPanelProps {
|
||||
}
|
||||
|
||||
function DetailPanelRoot({ children, className }: DetailPanelProps) {
|
||||
return <div className={cn('flex flex-col gap-4 md:gap-6', className)}>{children}</div>
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-4 md:gap-6', className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface DetailPanelHeaderProps {
|
||||
@@ -53,34 +67,29 @@ function DetailPanelHeader({
|
||||
</div>
|
||||
) : null}
|
||||
</FrameHeader>
|
||||
{children ? <FramePanel className="flex flex-col gap-4">{children}</FramePanel> : null}
|
||||
{children ? (
|
||||
<FramePanel className="flex flex-col gap-4">{children}</FramePanel>
|
||||
) : null}
|
||||
</Frame>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact KPI strip — stats-7 pattern (single Frame, divided cells).
|
||||
* Preview: https://reui.io/preview/base/stats-7
|
||||
* Hybrid KPI cards — KpiStatGrid / stats-12.
|
||||
* Preview: https://reui.io/preview/base/stats-12
|
||||
*/
|
||||
function DetailPanelMetrics({ cards }: { cards: DetailMetricCard[] }) {
|
||||
return (
|
||||
<Frame dense spacing="sm" className="w-full">
|
||||
<FramePanel className="grid grid-cols-1 divide-y p-0 sm:grid-cols-3 sm:divide-x sm:divide-y-0">
|
||||
{cards.map((card) => (
|
||||
<div key={card.id} className="flex flex-col gap-1.5 px-4 py-3">
|
||||
<div className="text-muted-foreground flex items-center gap-1.5 text-xs font-medium [&_svg]:size-3.5">
|
||||
{card.icon}
|
||||
<span>{card.label}</span>
|
||||
</div>
|
||||
<p className="text-foreground text-xl font-semibold tracking-tight tabular-nums">
|
||||
{card.description}
|
||||
</p>
|
||||
{card.footer}
|
||||
</div>
|
||||
))}
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
)
|
||||
const kpiCards: KpiStatCard[] = cards.map((card) => ({
|
||||
id: card.id,
|
||||
label: card.label,
|
||||
value: card.description,
|
||||
icon: card.icon,
|
||||
iconClassName: card.iconClassName,
|
||||
hint: card.hint,
|
||||
variant: card.variant,
|
||||
footer: card.footer,
|
||||
}))
|
||||
return <KpiStatGrid cards={kpiCards} />
|
||||
}
|
||||
|
||||
function DetailPanelSection({
|
||||
|
||||
Reference in New Issue
Block a user