Stat cards
Dashboard stat cards display key metrics. Used on the partner and conveyancer home pages with period filters (see Tabs for the period filter pattern).
Basic stat cards
Large number with a label below. Used on the partner dashboard for quick stats like new quotes, active transactions, and referral fees. These are links that navigate to the relevant list page.
<div class="flex justify-around py-6 lg:py-12 text-center">
<a href="@(ListQuotes.Route())?Status=New"
x-target.push="main" class="flex-1">
<span class="text-3xl font-bold text-gray-900
dark:text-white">@Model.NewQuoteCount</span>
<span class="block mt-1 text-sm text-gray-500
dark:text-gray-400">New quotes</span>
</a>
<a href="@(ListTransactions.Route())?Status=Active"
x-target.push="main" class="flex-1">
<span class="text-3xl font-bold text-gray-900
dark:text-white">@Model.ActiveTransactionCount</span>
<span class="block mt-1 text-sm text-gray-500
dark:text-gray-400">Active transactions</span>
</a>
<div class="flex-1">
<span class="text-3xl font-bold text-gray-900
dark:text-white">@Model.ReferralFeesTotal.ToString("C0")</span>
<span class="block mt-1 text-sm text-gray-500
dark:text-gray-400">Referral fees</span>
</div>
</div>
Stat cards with trends
Used on the conveyancer dashboard. Each card shows a badge label, a large count, and a trend indicator (up/down/neutral). The trend compares the current period to the previous period.
Quotes
42 total +5<div koala-card koala-card-flush>
<div class="flex items-baseline gap-3 px-4 pt-4 pb-2
sm:px-6 sm:pt-6 sm:pb-2">
<h3 class="text-lg font-semibold text-gray-900
dark:text-white">Quotes</h3>
<span class="text-sm font-normal text-gray-500
dark:text-gray-400">@Model.QuoteCurrentTotal total</span>
<!-- Trend indicator -->
@if (Model.QuoteTotalChange > 0)
{
<span class="flex items-center text-sm
text-green-500 dark:text-green-400">
<!-- Arrow up SVG --> +@Model.QuoteTotalChange
</span>
}
</div>
<div class="grid grid-cols-2">
<a href="..." x-target.push="main" class="block p-4 sm:p-6">
<div class="mb-2">
<span koala-badge="Neutral">New</span>
</div>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold leading-none text-gray-900
sm:text-3xl dark:text-white">@count</span>
<span class="inline-flex items-center text-sm
text-green-500 dark:text-green-400">
<!-- Arrow up SVG --> +@change
</span>
</div>
</a>
</div>
</div>
Trend indicators
Three states: positive (green, arrow up), negative (red, arrow down), and neutral (gray, arrow right).
<!-- Positive (green, arrow up) -->
<span class="flex items-center text-sm
text-green-500 dark:text-green-400">
<svg class="w-4 h-4" ...>
<path d="m5 12 7-7 7 7"></path>
<path d="M12 19V5"></path>
</svg>
+@Model.Change
</span>
<!-- Negative (red, arrow down) -->
<span class="flex items-center text-sm
text-red-500 dark:text-red-400">
<svg class="w-4 h-4" ...>
<path d="M12 5v14"></path>
<path d="m19 12-7 7-7-7"></path>
</svg>
@Model.Change
</span>
<!-- Neutral (gray, arrow right) -->
<span class="flex items-center text-sm
text-gray-400 dark:text-gray-500">
<svg class="w-4 h-4" ...>
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
0
</span>
Stat cards with sparklines
The conveyancer dashboard renders mini sparkline charts alongside each stat using
ApexCharts.
Sparkline data is passed via data-sparkline
attributes on a container div.
<!-- Sparkline container (rendered by ApexCharts) -->
<div class="w-14 sm:w-20 h-8 flex-shrink-0"
data-sparkline="@Json.Serialize(Model.GetQuoteSparkline(status))"
data-sparkline-labels="@Html.Raw(...)"
data-sparkline-title="@status"
data-sparkline-color="@sparklineColor">
</div>
<!-- Sparkline colours per status -->
QuoteStatus.New => "#6B7280" (gray)
QuoteStatus.Accepted => "#059669" (emerald)
QuoteStatus.Expired => "#D97706" (amber)
QuoteStatus.Cancelled => "#DC2626" (red)
Responsive grid layout
The conveyancer dashboard uses a 5-column grid on XL screens: quotes (2 cols), transactions (2 cols), and fees (1 col). On smaller screens, it stacks to 2 columns then 1 column.
Quotes (2 cols)
Contains a 2x2 grid of status cards
Transactions (2 cols)
Contains a 2x2 grid of status cards
Fees (1 col)
Total fees with trend
<div koala-card koala-card-flush>
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-5">
<div class="xl:col-span-2 border-b lg:border-r
xl:border-b-0 border-gray-200
dark:border-gray-700">
<!-- Quotes section header + 2x2 status grid -->
</div>
<div class="xl:col-span-2 border-b xl:border-b-0
xl:border-r border-gray-200
dark:border-gray-700">
<!-- Transactions section header + 2x2 status grid -->
</div>
<div class="xl:col-span-1">
<!-- Fees total + trend -->
</div>
</div>
</div>
Period filter integration
Stat cards are always paired with a period filter. The filter defaults to 30 days and updates the stats via Alpine-AJAX. See the Tabs page for the period filter pattern.
Home
<div class="flex items-center justify-between gap-3 pb-3 flex-wrap">
<h2 class="text-2xl font-bold text-gray-900
dark:text-white">Home</h2>
<div class="flex items-center gap-1 shrink-0">
@foreach (var (key, label) in new[] {
("7d", "7d"), ("30d", "30d"),
("3m", "3m"), ("12m", "12m") })
{
var isActive = activePeriod == key;
<a href="@(IndexModel.Route())?period=@key"
x-target.push="home"
class="px-3 py-1.5 rounded-lg text-sm font-medium
@(isActive
? "bg-gray-900 text-white ..."
: "text-gray-500 hover:bg-gray-100 ...")">
@label
</a>
}
</div>
</div>