mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
refactor(cabinet): unify all statistics onto the shared StatCard
Bring every stat strip across the admin panel and user cabinet onto the canonical StatCard (icon chip + label-on-top + tone-coloured value), matching the sales/Remnawave style the rest of the app already used. - Convert ~20 hand-rolled stat strips across 21 pages/components - Remove 4 divergent local StatCard components (Dashboard, Users, BanSystem, Payments) and migrate Remnawave's local StatCard (33 cards) - Extend StatCard with optional subValue, loading skeleton, trailing slot, and icon-size normalisation (all backward-compatible) - StatsGrid nav tiles now use StatCard while keeping link/chevron/loading - Preserve all data, i18n keys, conditionals and interactive filters; fix the dead 'info' colour by mapping it to accent
This commit is contained in:
@@ -24,6 +24,12 @@ interface StatCardProps {
|
||||
/** Tints the icon chip and (unless valueClassName is set) the value colour. */
|
||||
tone?: keyof typeof TONE;
|
||||
valueClassName?: string;
|
||||
/** Optional secondary line shown under the value (e.g. a subtitle or context). */
|
||||
subValue?: string;
|
||||
/** When true, shows a skeleton placeholder instead of the value. */
|
||||
loading?: boolean;
|
||||
/** Optional node rendered at the right edge of the label row (e.g. a chevron for nav cards). */
|
||||
trailing?: ReactNode;
|
||||
/** Optional period-over-period change shown under the value. */
|
||||
delta?: StatCardDelta | null;
|
||||
}
|
||||
@@ -34,6 +40,9 @@ export function StatCard({
|
||||
icon,
|
||||
tone = 'neutral',
|
||||
valueClassName,
|
||||
subValue,
|
||||
loading,
|
||||
trailing,
|
||||
delta,
|
||||
}: StatCardProps) {
|
||||
const toneStyle = TONE[tone];
|
||||
@@ -42,19 +51,32 @@ export function StatCard({
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-dark-800/30 p-3 transition-colors hover:bg-dark-800/50">
|
||||
<div className="truncate text-xs text-dark-500 sm:text-sm">{label}</div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="truncate text-xs text-dark-500 sm:text-sm">{label}</span>
|
||||
{trailing}
|
||||
</div>
|
||||
{/* Chip is centred against the value line only (delta sits below the whole
|
||||
row), so the icon lands in the same spot on every card. */}
|
||||
row), so the icon lands in the same spot on every card. The forced svg
|
||||
size normalises every icon regardless of what the call site passes. */}
|
||||
<div className="mt-1.5 flex items-center gap-2.5">
|
||||
{icon && (
|
||||
<span
|
||||
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${toneStyle.chip}`}
|
||||
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg [&>svg]:h-5 [&>svg]:w-5 ${toneStyle.chip}`}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
<div className={`min-w-0 flex-1 truncate text-lg font-semibold sm:text-xl ${valueClass}`}>
|
||||
{value}
|
||||
<div className="min-w-0 flex-1">
|
||||
{loading ? (
|
||||
<div className="skeleton h-7 w-20 rounded" />
|
||||
) : (
|
||||
<>
|
||||
<div className={`truncate text-lg font-semibold sm:text-xl ${valueClass}`}>
|
||||
{value}
|
||||
</div>
|
||||
{subValue && <div className="truncate text-xs text-dark-500">{subValue}</div>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{trendStyle && (
|
||||
|
||||
Reference in New Issue
Block a user