feat(dashboard): add date filter and stat groups components

- Implemented DashboardDateFilter component for selecting date ranges.
- Created DashboardStatGroups component to display various dashboard statistics.
- Added dashboard chart data processing logic to support new chart specifications.
- Introduced reusable Chart components for rendering different types of charts.
This commit is contained in:
2026-10-21 14:57:54 +05:30
parent e6efc33ac0
commit 0a212ed1aa
16 changed files with 1129 additions and 350 deletions
+18 -16
View File
@@ -58,10 +58,8 @@ export interface StatCardProps {
valueClassName?: string
/** Extra classes for the icon tile, e.g. a warning tint instead of the primary one. */
iconClassName?: string
/** Makes the card a toggle button that filters the list below it (click again to clear). */
onClick?: () => void
/** The card's filter is the one currently applied — highlighted and announced as pressed. */
active?: boolean
/** Tighter padding and a smaller icon / text, for dense rows such as the dashboard. */
compact?: boolean
}
/**
@@ -80,8 +78,7 @@ export function StatCard({
className,
valueClassName,
iconClassName,
onClick,
active = false,
compact = false,
}: StatCardProps) {
const hasDelta = typeof delta === "number" && delta !== 0
const isPositive = hasDelta && delta > 0
@@ -98,31 +95,36 @@ export function StatCard({
? { type: "button" as const, onClick, "aria-pressed": active, title: active ? "Click to clear this filter" : "Click to filter the list" }
: {})}
className={cn(
"@container min-w-0 rounded-2xl bg-card px-4 py-5 text-left shadow-sm ring-1 ring-foreground/10 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-primary/30",
onClick && "w-full cursor-pointer focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
className,
// After className so the "selected" look wins over a card's own tint.
active && "bg-primary/5 ring-2 ring-primary"
"@container min-w-0 rounded-2xl bg-card shadow-sm ring-1 ring-foreground/10 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-primary/30",
compact ? "px-3 py-2.5" : "px-4 py-5",
className
)}
>
{/* Wide enough: icon beside the text. Narrow (e.g. two per row on a phone): icon above, so the
value keeps the full width instead of being cut off. */}
<div className="flex flex-col gap-2.5 @[12.5rem]:flex-row @[12.5rem]:items-center @[12.5rem]:gap-3.5">
<div
className={cn(
"flex",
// Compact cards keep the icon beside the text at every width; the regular card stacks it above when narrow.
compact ? "flex-row items-center gap-2.5" : "flex-col gap-2.5 @[12.5rem]:flex-row @[12.5rem]:items-center @[12.5rem]:gap-3.5"
)}
>
{Icon && (
<div
className={cn(
"flex size-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary @[12.5rem]:size-11",
"flex shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary",
compact ? "size-9" : "size-9 @[12.5rem]:size-11",
iconClassName
)}
>
<Icon className="size-4.5 @[12.5rem]:size-5" aria-hidden />
<Icon className={compact ? "size-4" : "size-4.5 @[12.5rem]:size-5"} aria-hidden />
</div>
)}
<div className="flex min-w-0 flex-1 flex-col">
<p className="truncate text-sm text-muted-foreground">{label}</p>
<p className={cn("text-muted-foreground", compact ? "text-xs leading-tight" : "truncate text-sm")}>{label}</p>
<div className="flex items-end justify-between gap-2">
<p className={cn("text-base leading-tight font-semibold tracking-tight wrap-break-word text-foreground @[12.5rem]:text-lg", valueClassName)}>
<p className={cn("leading-tight font-semibold tracking-tight wrap-break-word text-foreground", compact ? "text-base" : "text-base @[12.5rem]:text-lg", valueClassName)}>
{formatValue(value)}
</p>
{trend && trend.length > 1 && <Sparkline points={trend} />}