mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 14:20:58 +00:00
style: ui improvement to kpi in portal
This commit is contained in:
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user