mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 04:50:54 +00:00
317 lines
6.7 KiB
TypeScript
317 lines
6.7 KiB
TypeScript
import { Badge, Button, Group, Tooltip } from "@mantine/core";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { api } from "@/services/api";
|
|
|
|
import type {
|
|
CompanyProfile,
|
|
CompanyStatus,
|
|
CompanyType,
|
|
CustomerBookingStatus,
|
|
CustomerPaymentStatus,
|
|
ProfileStatus,
|
|
ProfileType,
|
|
} from "@/types/customer";
|
|
|
|
import { humanize } from "./format";
|
|
|
|
const badgeStyle = {
|
|
fontSize: "0.7rem",
|
|
letterSpacing: "0.04em",
|
|
whiteSpace: "nowrap" as const,
|
|
};
|
|
|
|
/** Shared status palette — active/paid green, pending amber, terminal red. */
|
|
const STATUS_COLOR: Record<CompanyStatus | ProfileStatus, string> = {
|
|
active: "edr-green",
|
|
pending: "yellow",
|
|
suspended: "orange",
|
|
blacklisted: "red",
|
|
};
|
|
|
|
const COMPANY_TYPE_COLOR: Record<CompanyType, string> = {
|
|
customer: "edr-green",
|
|
freight_forwarder: "blue",
|
|
dj_freight_forwarder: "indigo",
|
|
transporter: "grape",
|
|
};
|
|
|
|
const PROFILE_TYPE_COLOR: Record<ProfileType, string> = {
|
|
importer: "teal",
|
|
exporter: "cyan",
|
|
freight_forwarder: "blue",
|
|
dj_freight_forwarder: "indigo",
|
|
transporter: "grape",
|
|
};
|
|
|
|
export function CompanyStatusBadge({ status }: { status: CompanyStatus }) {
|
|
return (
|
|
<Badge
|
|
color={STATUS_COLOR[status] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
tt="capitalize"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{status}
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
export function CompanyTypeBadge({ type }: { type: CompanyType }) {
|
|
return (
|
|
<Badge
|
|
color={COMPANY_TYPE_COLOR[type] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{humanize(type)}
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Profile chips for a company row: one chip per role (Importer / Exporter / …)
|
|
* carrying its reference code. Caps at three (a company has at most three
|
|
* profiles); any extra collapse into a `+N` chip.
|
|
*/
|
|
export function ProfileChips({
|
|
profiles,
|
|
max = 3,
|
|
}: {
|
|
profiles: CompanyProfile[];
|
|
max?: number;
|
|
}) {
|
|
if (!profiles.length) {
|
|
return (
|
|
<Badge color="gray" variant="light" size="sm" radius="md" style={badgeStyle}>
|
|
No profiles
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
const shown = profiles.slice(0, max);
|
|
const extra = profiles.length - shown.length;
|
|
|
|
return (
|
|
<Group gap={6} wrap="wrap">
|
|
{shown.map((profile) => (
|
|
<Tooltip
|
|
key={profile.id}
|
|
label={`${humanize(profile.type)} · ${humanize(profile.status)}`}
|
|
withArrow
|
|
>
|
|
<Badge
|
|
color={PROFILE_TYPE_COLOR[profile.type] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{humanize(profile.type)} · {profile.reference}
|
|
</Badge>
|
|
</Tooltip>
|
|
))}
|
|
{extra > 0 ? (
|
|
<Badge color="gray" variant="light" size="sm" radius="md" style={badgeStyle}>
|
|
+{extra}
|
|
</Badge>
|
|
) : null}
|
|
</Group>
|
|
);
|
|
}
|
|
|
|
export function ProfileTypeBadge({ type }: { type: ProfileType }) {
|
|
return (
|
|
<Badge
|
|
color={PROFILE_TYPE_COLOR[type] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{humanize(type)}
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
export function ProfileStatusBadge({ status }: { status: ProfileStatus }) {
|
|
return (
|
|
<Badge
|
|
color={STATUS_COLOR[status] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
tt="capitalize"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{status}
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
const BOOKING_STATUS_COLOR: Record<CustomerBookingStatus, string> = {
|
|
DRAFT: "gray",
|
|
SUBMITTED: "yellow",
|
|
PENDING_APPROVAL: "yellow",
|
|
APPROVED: "cyan",
|
|
PAID: "edr-green",
|
|
IN_TRANSIT: "blue",
|
|
COMPLETED: "indigo",
|
|
REJECTED: "red",
|
|
CANCELLED: "red",
|
|
};
|
|
|
|
export function BookingStatusBadge({ status }: { status: CustomerBookingStatus }) {
|
|
return (
|
|
<Badge
|
|
color={BOOKING_STATUS_COLOR[status] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
tt="uppercase"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{humanize(status)}
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
const PAYMENT_STATUS_COLOR: Record<CustomerPaymentStatus, string> = {
|
|
"action-required": "orange",
|
|
processing: "yellow",
|
|
success: "edr-green",
|
|
failed: "red",
|
|
canceled: "gray",
|
|
refunded: "grape",
|
|
};
|
|
|
|
export function PaymentStatusBadge({ status }: { status: CustomerPaymentStatus }) {
|
|
return (
|
|
<Badge
|
|
color={PAYMENT_STATUS_COLOR[status] ?? "gray"}
|
|
variant="light"
|
|
size="sm"
|
|
radius="md"
|
|
tt="capitalize"
|
|
fw={600}
|
|
style={badgeStyle}
|
|
>
|
|
{humanize(status)}
|
|
</Badge>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Inline approval action buttons for a profile row.
|
|
* Transitions: pending → approve/reject | active → suspend | suspended → reactivate/blacklist | blacklisted → reinstate
|
|
*/
|
|
export function ProfileApprovalActions({
|
|
profileId,
|
|
status,
|
|
}: {
|
|
profileId: string;
|
|
status: ProfileStatus;
|
|
}) {
|
|
const { mutate, isPending } = useMutation(
|
|
api.customers.setProfileStatus.mutationOptions(),
|
|
);
|
|
|
|
const act = (next: ProfileStatus) =>
|
|
mutate({ profileId, status: next });
|
|
|
|
if (status === "pending") {
|
|
return (
|
|
<Group gap={6} wrap="nowrap">
|
|
<Button
|
|
size="xs"
|
|
variant="light"
|
|
color="edr-green"
|
|
radius="md"
|
|
loading={isPending}
|
|
onClick={() => act("active")}
|
|
>
|
|
Approve
|
|
</Button>
|
|
<Button
|
|
size="xs"
|
|
variant="light"
|
|
color="red"
|
|
radius="md"
|
|
loading={isPending}
|
|
onClick={() => act("blacklisted")}
|
|
>
|
|
Reject
|
|
</Button>
|
|
</Group>
|
|
);
|
|
}
|
|
|
|
if (status === "active") {
|
|
return (
|
|
<Button
|
|
size="xs"
|
|
variant="light"
|
|
color="orange"
|
|
radius="md"
|
|
loading={isPending}
|
|
onClick={() => act("suspended")}
|
|
>
|
|
Suspend
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
if (status === "suspended") {
|
|
return (
|
|
<Group gap={6} wrap="nowrap">
|
|
<Button
|
|
size="xs"
|
|
variant="light"
|
|
color="edr-green"
|
|
radius="md"
|
|
loading={isPending}
|
|
onClick={() => act("active")}
|
|
>
|
|
Reactivate
|
|
</Button>
|
|
<Button
|
|
size="xs"
|
|
variant="light"
|
|
color="red"
|
|
radius="md"
|
|
loading={isPending}
|
|
onClick={() => act("blacklisted")}
|
|
>
|
|
Blacklist
|
|
</Button>
|
|
</Group>
|
|
);
|
|
}
|
|
|
|
if (status === "blacklisted") {
|
|
return (
|
|
<Button
|
|
size="xs"
|
|
variant="light"
|
|
color="gray"
|
|
radius="md"
|
|
loading={isPending}
|
|
onClick={() => act("pending")}
|
|
>
|
|
Reinstate
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|