mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
changes
This commit is contained in:
@@ -1,21 +1,23 @@
|
||||
import { Badge } from "@mantine/core";
|
||||
|
||||
export function BookingPriorityBadge({ score }: { score: number }) {
|
||||
if (score >= 1000) {
|
||||
return (
|
||||
<span className="rounded-full bg-red-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-red-700">
|
||||
<Badge color="red" variant="filled" size="sm" radius="lg" tt="uppercase">
|
||||
Urgent
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (score >= 500) {
|
||||
return (
|
||||
<span className="rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-700">
|
||||
<Badge color="yellow" variant="filled" size="sm" radius="lg" tt="uppercase">
|
||||
High
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-slate-600">
|
||||
<Badge color="gray" variant="light" size="sm" radius="lg" tt="uppercase">
|
||||
Normal
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { bookingGlass } from "./booking-ui.styles";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, Group, Stack, Text, SimpleGrid } from "@mantine/core";
|
||||
|
||||
export interface StatItem {
|
||||
label: string;
|
||||
@@ -10,54 +9,76 @@ export interface StatItem {
|
||||
accent?: "default" | "amber" | "emerald" | "rose";
|
||||
}
|
||||
|
||||
const iconAccentStyles = {
|
||||
default: "text-foreground/70",
|
||||
amber: "text-amber-600 dark:text-amber-400",
|
||||
emerald: "text-emerald-600 dark:text-emerald-400",
|
||||
rose: "text-rose-600 dark:text-rose-400",
|
||||
const accentColors = {
|
||||
default: { bg: "var(--mantine-color-gray-1)", color: "var(--mantine-color-gray-6)" },
|
||||
amber: { bg: "var(--mantine-color-yellow-1)", color: "var(--mantine-color-yellow-6)" },
|
||||
emerald: { bg: "var(--mantine-color-green-1)", color: "var(--mantine-color-green-6)" },
|
||||
rose: { bg: "var(--mantine-color-red-1)", color: "var(--mantine-color-red-6)" },
|
||||
};
|
||||
|
||||
export function BookingStatGrid({ items }: { items: StatItem[] }) {
|
||||
return (
|
||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="md">
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const accent = item.accent ?? "default";
|
||||
const accentStyle = accentColors[accent];
|
||||
|
||||
return (
|
||||
<div
|
||||
<Card
|
||||
key={item.label}
|
||||
className={cn(
|
||||
"group relative overflow-hidden rounded-xl p-5 transition-all duration-200 hover:shadow-md",
|
||||
bookingGlass.card,
|
||||
)}
|
||||
p="lg"
|
||||
radius="lg"
|
||||
withBorder
|
||||
style={{
|
||||
background: "white",
|
||||
border: "1px solid var(--mantine-color-gray-2)",
|
||||
transition: "all 0.2s ease",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.08)";
|
||||
e.currentTarget.style.borderColor = "var(--mantine-color-gray-3)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.boxShadow = "none";
|
||||
e.currentTarget.style.borderColor = "var(--mantine-color-gray-2)";
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<Group justify="space-between" align="flex-start">
|
||||
<Stack gap="xs" style={{ flex: 1 }}>
|
||||
<Text size="xs" fw={600} c="dimmed" tt="uppercase">
|
||||
{item.label}
|
||||
</p>
|
||||
<p className="mt-2 text-3xl font-semibold tabular-nums tracking-tight text-foreground">
|
||||
</Text>
|
||||
<Text size="32px" fw={700} style={{ lineHeight: 1, letterSpacing: "-0.02em" }}>
|
||||
{item.value}
|
||||
</p>
|
||||
</Text>
|
||||
{item.hint && (
|
||||
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
||||
<Text size="xs" c="dimmed">
|
||||
{item.hint}
|
||||
</p>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-10 shrink-0 items-center justify-center rounded-xl transition-transform duration-200 group-hover:scale-[1.02]",
|
||||
bookingGlass.iconWellGreen,
|
||||
iconAccentStyles[accent],
|
||||
)}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "44px",
|
||||
height: "44px",
|
||||
borderRadius: "10px",
|
||||
background: accentStyle.bg,
|
||||
color: accentStyle.color,
|
||||
flexShrink: 0,
|
||||
transition: "transform 0.2s ease",
|
||||
}}
|
||||
>
|
||||
<Icon className="size-[18px]" strokeWidth={1.75} />
|
||||
<Icon size={22} strokeWidth={1.75} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</SimpleGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
import { Badge } from "@edr/ui-common";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Badge } from "@mantine/core";
|
||||
import { BOOKING_STATUS_STYLES } from "@/features/bookings/booking-status.config";
|
||||
|
||||
const statusColorMap: Record<string, string> = {
|
||||
DRAFT: "gray",
|
||||
SUBMITTED: "yellow",
|
||||
CHANGES_REQUESTED: "orange",
|
||||
PENDING_APPROVAL: "yellow",
|
||||
APPROVED_PENDING_SIGNATURE: "cyan",
|
||||
APPROVED: "green",
|
||||
CONTRACT_READY: "indigo",
|
||||
SIGNED_CUSTOMER: "cyan",
|
||||
FULLY_EXECUTED: "indigo",
|
||||
PNR_GENERATED: "violet",
|
||||
PAYMENT_VERIFICATION_IN_PROGRESS: "yellow",
|
||||
PAID: "green",
|
||||
IN_TRANSIT: "cyan",
|
||||
COMPLETED: "indigo",
|
||||
REJECTED: "red",
|
||||
CANCELLED: "red",
|
||||
PENDING_CONSOLIDATION: "yellow",
|
||||
CONSOLIDATED: "indigo",
|
||||
};
|
||||
|
||||
export function BookingStatusBadge({ status }: { status: string }) {
|
||||
const style = BOOKING_STATUS_STYLES[status] ?? {
|
||||
label: status,
|
||||
color: "bg-muted text-muted-foreground border-border",
|
||||
color: "gray",
|
||||
};
|
||||
const color = statusColorMap[status] ?? "gray";
|
||||
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"px-2 py-0.5 text-[9px] font-bold uppercase tracking-wider",
|
||||
style.color,
|
||||
)}
|
||||
color={color}
|
||||
variant="light"
|
||||
size="sm"
|
||||
radius="md"
|
||||
tt="uppercase"
|
||||
fw={600}
|
||||
style={{ fontSize: "0.7rem", letterSpacing: "0.05em" }}
|
||||
>
|
||||
{style.label}
|
||||
</Badge>
|
||||
|
||||
@@ -8,27 +8,24 @@ import {
|
||||
Wallet,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { Group, Badge, UnstyledButton, Stack, Text } from "@mantine/core";
|
||||
|
||||
import {
|
||||
BOOKING_LIST_TABS,
|
||||
type BookingStatusTabKey,
|
||||
} from "@/features/bookings/booking-status.config";
|
||||
import { bookingGlass } from "./booking-ui.styles";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TAB_ICONS: Record<BookingStatusTabKey, React.ReactNode> = {
|
||||
all: <LayoutGrid className="size-3.5" strokeWidth={1.75} />,
|
||||
intake: <Inbox className="size-3.5" strokeWidth={1.75} />,
|
||||
in_approval: <ClipboardCheck className="size-3.5" strokeWidth={1.75} />,
|
||||
approved_contract: <FileSignature className="size-3.5" strokeWidth={1.75} />,
|
||||
payment: <Wallet className="size-3.5" strokeWidth={1.75} />,
|
||||
operations: <Train className="size-3.5" strokeWidth={1.75} />,
|
||||
completed: <CheckCircle className="size-3.5" strokeWidth={1.75} />,
|
||||
closed: <XCircle className="size-3.5" strokeWidth={1.75} />,
|
||||
all: <LayoutGrid size={18} strokeWidth={1.75} />,
|
||||
intake: <Inbox size={18} strokeWidth={1.75} />,
|
||||
in_approval: <ClipboardCheck size={18} strokeWidth={1.75} />,
|
||||
approved_contract: <FileSignature size={18} strokeWidth={1.75} />,
|
||||
payment: <Wallet size={18} strokeWidth={1.75} />,
|
||||
operations: <Train size={18} strokeWidth={1.75} />,
|
||||
completed: <CheckCircle size={18} strokeWidth={1.75} />,
|
||||
closed: <XCircle size={18} strokeWidth={1.75} />,
|
||||
};
|
||||
|
||||
const activeTabText = "text-black";
|
||||
|
||||
interface BookingStatusTabsProps {
|
||||
active: BookingStatusTabKey;
|
||||
onChange: (tab: BookingStatusTabKey) => void;
|
||||
@@ -41,66 +38,68 @@ export function BookingStatusTabs({
|
||||
counts,
|
||||
}: BookingStatusTabsProps) {
|
||||
return (
|
||||
<div className={bookingGlass.tabRail}>
|
||||
<div
|
||||
className="flex flex-nowrap gap-1.5 overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
|
||||
role="tablist"
|
||||
aria-label="Booking status filters"
|
||||
>
|
||||
{BOOKING_LIST_TABS.map((tab) => {
|
||||
const isActive = active === tab.key;
|
||||
const count = counts?.[tab.key];
|
||||
return (
|
||||
<button
|
||||
key={tab.key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={isActive}
|
||||
onClick={() => onChange(tab.key)}
|
||||
className={cn(
|
||||
"flex min-w-[8rem] shrink-0 flex-col items-start gap-0.5 rounded-lg px-3 py-2.5 text-left transition-all duration-200",
|
||||
isActive
|
||||
? bookingGlass.activeTab
|
||||
: "text-muted-foreground hover:bg-emerald-500/5 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<span className="flex w-full items-center justify-between gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm font-medium",
|
||||
isActive ? activeTabText : "text-muted-foreground",
|
||||
)}
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="wrap"
|
||||
p="md"
|
||||
style={{
|
||||
background: "var(--mantine-color-gray-0)",
|
||||
borderRadius: "12px",
|
||||
border: "1px solid var(--mantine-color-gray-2)",
|
||||
}}
|
||||
>
|
||||
{BOOKING_LIST_TABS.map((tab) => {
|
||||
const isActive = active === tab.key;
|
||||
const count = counts?.[tab.key];
|
||||
return (
|
||||
<UnstyledButton
|
||||
key={tab.key}
|
||||
onClick={() => onChange(tab.key)}
|
||||
style={{
|
||||
background: isActive ? "white" : "transparent",
|
||||
border: isActive ? "1px solid var(--mantine-color-blue-3)" : "1px solid var(--mantine-color-gray-2)",
|
||||
borderRadius: "10px",
|
||||
padding: "10px 16px",
|
||||
transition: "all 0.2s ease",
|
||||
cursor: "pointer",
|
||||
boxShadow: isActive ? "0 2px 8px rgba(59, 130, 246, 0.1)" : "none",
|
||||
}}
|
||||
>
|
||||
<Group gap="sm" justify="space-between" wrap="nowrap">
|
||||
<Group gap={8}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "32px",
|
||||
height: "32px",
|
||||
borderRadius: "8px",
|
||||
background: isActive ? "var(--mantine-color-blue-1)" : "var(--mantine-color-gray-1)",
|
||||
color: isActive ? "var(--mantine-color-blue-7)" : "var(--mantine-color-gray-6)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex size-7 shrink-0 items-center justify-center rounded-md",
|
||||
isActive
|
||||
? cn(bookingGlass.iconWellGreen, "text-black")
|
||||
: "border border-transparent bg-muted/30",
|
||||
)}
|
||||
>
|
||||
{TAB_ICONS[tab.key]}
|
||||
</span>
|
||||
<span className="whitespace-nowrap">{tab.label}</span>
|
||||
</span>
|
||||
{count !== undefined && count > 0 && (
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full px-2 py-0.5 text-[10px] font-semibold tabular-nums",
|
||||
isActive
|
||||
? cn("bg-emerald-500/15", activeTabText)
|
||||
: "bg-muted/50 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{TAB_ICONS[tab.key]}
|
||||
</div>
|
||||
<Text size="sm" fw={600}>
|
||||
{tab.label}
|
||||
</Text>
|
||||
</Group>
|
||||
{count !== undefined && count > 0 && (
|
||||
<Badge
|
||||
size="sm"
|
||||
variant={isActive ? "filled" : "light"}
|
||||
color={isActive ? "blue" : "gray"}
|
||||
radius="lg"
|
||||
>
|
||||
{count}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user