style: ui improvement to kpi in portal

This commit is contained in:
Nathnael
2026-06-24 08:55:09 +00:00
parent 9d5c195d94
commit c9694177d8
2 changed files with 91 additions and 27 deletions

View File

@@ -1,15 +1,30 @@
import { Box, Group, Text } from "@mantine/core";
import { memo } from "react";
import type { LucideIcon } from "lucide-react";
import { memo } from "react";
import { cv } from "../constants";
/** Accent families map a KPI to a soft tile + strong ink pair from the theme. */
type Accent = "green" | "amber" | "blue" | "slate";
const ACCENTS: Record<Accent, { soft: string; ink: string }> = {
green: { soft: cv("edr-soft"), ink: cv("edr-green.7") },
amber: { soft: cv("edr-amber-soft"), ink: cv("edr-amber-text") },
blue: { soft: cv("edr-blue-soft"), ink: cv("edr-blue") },
slate: { soft: cv("edr-slate-soft"), ink: cv("edr-slate") },
};
interface StatKpiProps {
icon: LucideIcon;
label: string;
value: string;
delta: string;
deltaColor: string;
/** Color family for the icon chip. */
accent: Accent;
/** Tint of the delta pill — defaults to the card accent. */
deltaTone?: Accent | "muted";
/** Draw a separating border on the left (on wide layouts). */
divider?: boolean;
loading?: boolean;
}
export const StatKpi = memo(function StatKpi({
@@ -17,30 +32,67 @@ export const StatKpi = memo(function StatKpi({
label,
value,
delta,
deltaColor,
accent,
deltaTone,
divider,
loading,
}: StatKpiProps) {
const a = ACCENTS[accent];
const tone = deltaTone ?? accent;
const pill =
tone === "muted"
? { bg: cv("edr-slate-soft2"), fg: cv("edr-muted") }
: { bg: ACCENTS[tone].soft, fg: ACCENTS[tone].ink };
return (
<Box
px={4}
className={
divider ? "lg:border-l lg:border-edr-border lg:pl-7" : undefined
divider
? "flex flex-col lg:border-l lg:border-edr-divider lg:pl-4"
: "flex flex-col"
}
>
<Group gap={6} align="center" mb={7} wrap="nowrap">
<Icon size={15} color={cv("edr-muted")} className="shrink-0" />
<Text fz={12} fw={600} c="edr-muted" truncate>
{label}
</Text>
</Group>
<Group gap={8} align="flex-end" wrap="nowrap">
<Text fz={22} fw={800} lh={1} c="edr-text" truncate>
{value}
</Text>
<Text fz={12} fw={600} lh={1.3} c={deltaColor} truncate>
{delta}
</Text>
{/* Icon chip + metric label, aligned on one line. */}
<Group gap={12} wrap="nowrap" align="start">
<Box
className="flex size-10 shrink-0 items-center justify-center rounded-lg"
style={{ background: a.soft }}
>
<Icon size={18} color={a.ink} strokeWidth={2} />
</Box>
<Box>
<Box className="flex-row! flex items-end gap-2">
<Text
fz={24}
fw={800}
lh={1.1}
c="edr-text"
truncate
className="tracking-tight"
>
{loading ? "—" : value}
</Text>
{delta && !loading && (
<Box
px={8}
py={3}
className="inline-flex w-fit rounded-full"
style={{ background: pill.bg, maxWidth: "100%" }}
>
<Text fz={10} fw={700} lh={1.4} truncate style={{ color: pill.fg }}>
{delta}
</Text>
</Box>
)}
</Box>
<Text fz={12} mt="xs" fw={600} c="edr-muted" truncate>
{label}
</Text>
</Box>
</Group>
{/* Value + its trend pill, grouped together at the bottom of the cell. */}
</Box>
);
});

View File

@@ -1,7 +1,7 @@
import { formatCurrency } from "@/pages/billing/invoices.mock";
import { SimpleGrid } from "@mantine/core";
import { CheckCircle2, Clock3, Truck, Wallet } from "lucide-react";
import { memo } from "react";
import { formatCurrency } from "@/pages/billing/invoices.mock";
import { formatPct } from "../constants";
import { Card } from "./Card";
import { StatKpi } from "./StatKpi";
@@ -29,39 +29,51 @@ export const StatsSection = memo(function StatsSection({
completionRate,
spendYtd,
spendYtdChangePct,
dashboardLoading,
}: StatsSectionProps) {
return (
<Card className="rounded-2xl border border-edr-border bg-gradient-to-br from-white to-edr-soft px-7 py-5">
<SimpleGrid cols={{ base: 2, lg: 4 }} spacing={0} verticalSpacing={20}>
<Card
padding={24}
className="border-edr-divider! shadow-[0_1px_2px_rgba(16,24,40,0.04)]"
>
<SimpleGrid
cols={{ base: 2, lg: 4 }}
spacing={{ base: 20, lg: 0 }}
>
<StatKpi
icon={Truck}
accent="green"
label="Active Shipments"
value={bookingsLoading ? "—" : activeBookingsLength.toString()}
delta={bookingsLoading ? "" : `+${newActiveThisWeek} this week`}
deltaColor="edr-green.7"
value={activeBookingsLength.toString()}
delta={newActiveThisWeek > 0 ? `+${newActiveThisWeek} this week` : ""}
loading={bookingsLoading}
/>
<StatKpi
icon={Clock3}
accent="amber"
label="Awaiting Payment"
value={outstandingInvoicesLength.toString()}
delta={`${formatCurrency(totalOutstanding || 0, "ETB")} due`}
deltaColor="edr-amber-text"
loading={bookingsLoading}
divider
/>
<StatKpi
icon={CheckCircle2}
accent="blue"
label="Delivered (YTD)"
value={deliveredCount ?? "—"}
delta={completionRate ? `${completionRate}% completed` : ""}
deltaColor="edr-muted"
deltaTone="muted"
loading={dashboardLoading}
divider
/>
<StatKpi
icon={Wallet}
accent="green"
label="Spend YTD"
value={spendYtd ?? "—"}
delta={spendYtdChangePct ? `${formatPct(spendYtdChangePct)} YoY` : ""}
deltaColor="edr-green.7"
loading={dashboardLoading}
divider
/>
</SimpleGrid>