mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
ui fix
This commit is contained in:
@@ -10,6 +10,12 @@ import {
|
||||
RefreshCw,
|
||||
} from "lucide-react";
|
||||
|
||||
import { MiniRing, MiniSparkline } from "@/components/common/MiniGraph";
|
||||
import { ACCENT_CHIP_BG } from "@/components/overview/OverviewKpiCard";
|
||||
import {
|
||||
overviewAccentGradients,
|
||||
type OverviewAccent,
|
||||
} from "@/components/overview/overview.styles";
|
||||
import type {
|
||||
BookingListSummaryMetrics,
|
||||
BookingListSummaryTabs,
|
||||
@@ -71,14 +77,21 @@ export function BookingRequestsHeader({
|
||||
</Group>
|
||||
|
||||
<Group grow gap="md" align="stretch" wrap="wrap">
|
||||
<HeroStat icon={LayoutList} label="In queue" value={val(metrics?.inQueue)} hint="Matching current filter" />
|
||||
<HeroStat
|
||||
icon={LayoutList}
|
||||
label="In queue"
|
||||
value={val(metrics?.inQueue)}
|
||||
hint="Matching current filter"
|
||||
accent="gold"
|
||||
variant="area"
|
||||
/>
|
||||
<HeroStat
|
||||
icon={Clock}
|
||||
label="Needs action"
|
||||
value={val(metrics?.needsAction)}
|
||||
hint="Submitted or pending"
|
||||
ratio={metrics?.inQueue ? (metrics.needsAction ?? 0) / metrics.inQueue : 0}
|
||||
ratioColor="#f59e0b"
|
||||
accent="orange"
|
||||
/>
|
||||
<HeroStat
|
||||
icon={AlertTriangle}
|
||||
@@ -86,110 +99,89 @@ export function BookingRequestsHeader({
|
||||
value={val(metrics?.urgent)}
|
||||
hint="High priority score"
|
||||
ratio={metrics?.inQueue ? (metrics.urgent ?? 0) / metrics.inQueue : 0}
|
||||
ratioColor="#ef4444"
|
||||
accent="rose"
|
||||
/>
|
||||
<HeroStat
|
||||
icon={CheckCircle2}
|
||||
label="Completed"
|
||||
value={val(tabs?.completed)}
|
||||
hint="Fully executed"
|
||||
accent="gold"
|
||||
variant="line"
|
||||
/>
|
||||
<HeroStat icon={CheckCircle2} label="Completed" value={val(tabs?.completed)} hint="Fully executed" />
|
||||
</Group>
|
||||
|
||||
{tabs ? <PipelineBar tabs={tabs} /> : null}
|
||||
{/* {tabs ? <PipelineBar tabs={tabs} /> : null} */}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
/** Compact ring gauge with the stat icon at its center. */
|
||||
function MiniDonut({
|
||||
pct,
|
||||
color,
|
||||
children,
|
||||
size = 52,
|
||||
stroke = 5,
|
||||
}: {
|
||||
pct?: number | null;
|
||||
color: string;
|
||||
children: ReactNode;
|
||||
size?: number;
|
||||
stroke?: number;
|
||||
}) {
|
||||
const radius = (size - stroke) / 2;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const clamped = pct != null ? Math.min(100, Math.max(0, Math.round(pct))) : null;
|
||||
const dash = clamped != null ? (clamped / 100) * circumference : 0;
|
||||
|
||||
return (
|
||||
<Box style={{ position: "relative", width: size, height: size, flexShrink: 0 }}>
|
||||
<svg width={size} height={size} style={{ transform: "rotate(-90deg)", display: "block" }}>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="var(--mantine-color-gray-2)"
|
||||
strokeWidth={stroke}
|
||||
/>
|
||||
{clamped != null ? (
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={stroke}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={`${dash} ${circumference}`}
|
||||
style={{ transition: "stroke-dasharray 400ms ease" }}
|
||||
/>
|
||||
) : null}
|
||||
</svg>
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* KPI card matching the overview style: icon chip + value + label, with a mini
|
||||
* graph at the bottom. Ratio cards show a ring; the rest show an area/line trend.
|
||||
*/
|
||||
function HeroStat({
|
||||
icon: Icon,
|
||||
label,
|
||||
value,
|
||||
hint,
|
||||
ratio,
|
||||
ratioColor = "var(--mantine-color-green-6)",
|
||||
accent = "emerald",
|
||||
variant = "area",
|
||||
}: {
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
value: ReactNode;
|
||||
hint?: string;
|
||||
ratio?: number;
|
||||
ratioColor?: string;
|
||||
accent?: OverviewAccent;
|
||||
variant?: "area" | "line";
|
||||
}) {
|
||||
const [, accentDeep] = overviewAccentGradients[accent] ?? overviewAccentGradients.default;
|
||||
const chipBg = ACCENT_CHIP_BG[accent] ?? ACCENT_CHIP_BG.default;
|
||||
const pct = ratio != null ? Math.round(Math.min(1, Math.max(0, ratio)) * 100) : null;
|
||||
|
||||
return (
|
||||
<Paper p="md" radius="lg" style={{ flex: "1 1 180px", minWidth: 160, ...CARD_STYLE }}>
|
||||
<Group gap="md" wrap="nowrap" align="center">
|
||||
<MiniDonut pct={pct} color={ratioColor}>
|
||||
<Icon size={19} />
|
||||
</MiniDonut>
|
||||
<Stack gap={2} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="xs" fw={600} tt="uppercase" c="dimmed" style={{ letterSpacing: 0.4 }}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fw={700} size="28px" lh={1} style={{ color: "#0f172a" }}>
|
||||
{value}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{pct != null ? `${pct}% of queue` : hint}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Stack gap={8}>
|
||||
<Group gap="sm" wrap="nowrap" align="center">
|
||||
<Box
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 11,
|
||||
background: chipBg,
|
||||
color: accentDeep,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Icon size={20} strokeWidth={2} />
|
||||
</Box>
|
||||
<Stack gap={1} style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text fw={800} size="24px" lh={1.05} style={{ color: "#0f172a" }} truncate>
|
||||
{value}
|
||||
</Text>
|
||||
<Text size="xs" fw={600} c="dimmed" truncate>
|
||||
{label}
|
||||
{hint ? ` · ${pct != null ? `${pct}% of queue` : hint}` : ""}
|
||||
</Text>
|
||||
</Stack>
|
||||
{pct != null ? (
|
||||
<MiniRing pct={pct} accent={accent} size={44} stroke={5}>
|
||||
<Text size="10px" fw={800} style={{ color: accentDeep }}>
|
||||
{pct}%
|
||||
</Text>
|
||||
</MiniRing>
|
||||
) : null}
|
||||
</Group>
|
||||
|
||||
{pct == null ? (
|
||||
<MiniSparkline variant={variant} accent={accent} baseline={0.5} seed={label} height={22} />
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export function BookingContainersCard({ containers }: BookingContainersCardProps
|
||||
<Table.Td>{container.quantity}</Table.Td>
|
||||
<Table.Td>{container.vgmPerUnitTons} t</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fw={600} c="green.7" size="sm">
|
||||
<Text fw={600} size="sm" style={{ color: "#B26C09" }}>
|
||||
{(container.quantity * container.vgmPerUnitTons).toFixed(2)} t
|
||||
</Text>
|
||||
</Table.Td>
|
||||
|
||||
@@ -21,10 +21,10 @@ export function BookingPaymentCard({
|
||||
Total Amount
|
||||
</Text>
|
||||
<Group align="flex-end" gap="xs">
|
||||
<Title order={1} fw={700} c="green.9" style={{ letterSpacing: "-1px" }}>
|
||||
<Title order={1} fw={700} style={{ letterSpacing: "-1px", color: "#8A5304" }}>
|
||||
{totalAmount.toLocaleString(undefined, { minimumFractionDigits: 2 })}
|
||||
</Title>
|
||||
<Text fw={600} c="green.7" mb={6}>
|
||||
<Text fw={600} mb={6} style={{ color: "#B26C09" }}>
|
||||
{currency}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -83,7 +83,7 @@ export function BookingRequestHero({
|
||||
|
||||
<Group justify="space-between" align="flex-start" wrap="wrap" gap="lg">
|
||||
<Stack gap="sm" style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="xs" c="green.7" fw={700} tt="uppercase" style={{ letterSpacing: 1 }}>
|
||||
<Text size="xs" fw={700} tt="uppercase" style={{ letterSpacing: 1, color: "#B26C09" }}>
|
||||
Booking reference
|
||||
</Text>
|
||||
<Group gap="sm" align="center" wrap="wrap">
|
||||
|
||||
@@ -25,7 +25,7 @@ export function BookingReviewNotesCard({ notes }: BookingReviewNotesCardProps) {
|
||||
<Stack gap="md">
|
||||
{notes.map((note) => (
|
||||
<Group key={note.id} align="flex-start" gap="md" wrap="nowrap">
|
||||
<ThemeIcon size={34} radius="xl" variant="light" color="green">
|
||||
<ThemeIcon size={34} radius="xl" variant="light" color="#F2A516">
|
||||
<FileText size={16} />
|
||||
</ThemeIcon>
|
||||
<Stack gap={2} style={{ flex: 1 }}>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
import { FREIGHT_BRAND } from "@/theme/freight-brand";
|
||||
|
||||
/** Single brand accent. Minimal design uses solid green sparingly, no gradients. */
|
||||
export const BRAND_GREEN = FREIGHT_BRAND;
|
||||
/** Single brand accent (gold). Used sparingly for icons/highlights, no gradients. */
|
||||
export const BRAND_GREEN = "#F2A516";
|
||||
|
||||
/** Centralised style tokens for the booking detail page + cards. */
|
||||
export const detailStyles = {
|
||||
|
||||
Reference in New Issue
Block a user