117 lines
3.0 KiB
TypeScript
117 lines
3.0 KiB
TypeScript
import type { HTMLAttributes } from "react";
|
|
|
|
import type {
|
|
OrderStatus,
|
|
ReviewDecision,
|
|
StepStatus,
|
|
WorkflowStepName,
|
|
} from "@/lib/types/backend";
|
|
import {
|
|
ORDER_STATUS_META,
|
|
REVIEW_DECISION_META,
|
|
STEP_STATUS_META,
|
|
WORKFLOW_STEP_META,
|
|
type StatusMeta,
|
|
type StatusTone,
|
|
} from "@/lib/types/status";
|
|
|
|
type StatusBadgeVariant =
|
|
| "order"
|
|
| "reviewDecision"
|
|
| "stepStatus"
|
|
| "workflowStep";
|
|
|
|
type StatusBadgeBaseProps = HTMLAttributes<HTMLSpanElement>;
|
|
|
|
type OrderStatusBadgeProps = StatusBadgeBaseProps & {
|
|
status: OrderStatus;
|
|
variant?: "order";
|
|
};
|
|
|
|
type ReviewDecisionBadgeProps = StatusBadgeBaseProps & {
|
|
status: ReviewDecision;
|
|
variant: "reviewDecision";
|
|
};
|
|
|
|
type StepStatusBadgeProps = StatusBadgeBaseProps & {
|
|
status: StepStatus;
|
|
variant: "stepStatus";
|
|
};
|
|
|
|
type WorkflowStepBadgeProps = StatusBadgeBaseProps & {
|
|
status: WorkflowStepName | null;
|
|
variant: "workflowStep";
|
|
};
|
|
|
|
export type StatusBadgeProps =
|
|
| OrderStatusBadgeProps
|
|
| ReviewDecisionBadgeProps
|
|
| StepStatusBadgeProps
|
|
| WorkflowStepBadgeProps;
|
|
|
|
const TONE_STYLES: Record<StatusTone, string> = {
|
|
neutral:
|
|
"border-[rgba(76,69,60,0.14)] bg-[rgba(110,98,84,0.08)] text-[var(--ink-muted)]",
|
|
info: "border-[rgba(57,86,95,0.16)] bg-[rgba(57,86,95,0.1)] text-[#2e4d56]",
|
|
warning:
|
|
"border-[rgba(145,104,46,0.18)] bg-[rgba(202,164,97,0.14)] text-[#7a5323]",
|
|
success:
|
|
"border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.14)] text-[#50633b]",
|
|
danger:
|
|
"border-[rgba(140,74,67,0.18)] bg-[rgba(140,74,67,0.12)] text-[#7f3f38]",
|
|
};
|
|
|
|
function joinClasses(...values: Array<string | false | null | undefined>) {
|
|
return values.filter(Boolean).join(" ");
|
|
}
|
|
|
|
function getMetaFromRecord<T extends string>(
|
|
record: Record<T, StatusMeta>,
|
|
status: string,
|
|
variant: StatusBadgeVariant,
|
|
): StatusMeta {
|
|
const meta = record[status as T];
|
|
|
|
if (!meta) {
|
|
throw new Error(`Invalid status "${status}" for variant "${variant}".`);
|
|
}
|
|
|
|
return meta;
|
|
}
|
|
|
|
function resolveStatusMeta(props: StatusBadgeProps): StatusMeta {
|
|
switch (props.variant) {
|
|
case "reviewDecision":
|
|
return getMetaFromRecord(REVIEW_DECISION_META, props.status, props.variant);
|
|
case "stepStatus":
|
|
return getMetaFromRecord(STEP_STATUS_META, props.status, props.variant);
|
|
case "workflowStep":
|
|
if (props.status === null) {
|
|
return { label: "未开始", tone: "neutral" };
|
|
}
|
|
|
|
return getMetaFromRecord(WORKFLOW_STEP_META, props.status, props.variant);
|
|
case "order":
|
|
case undefined:
|
|
return getMetaFromRecord(ORDER_STATUS_META, props.status, "order");
|
|
}
|
|
}
|
|
|
|
export function StatusBadge({ className, ...props }: StatusBadgeProps) {
|
|
const meta = resolveStatusMeta(props);
|
|
|
|
return (
|
|
<span
|
|
data-tone={meta.tone}
|
|
className={joinClasses(
|
|
"inline-flex items-center rounded-full border px-2 py-0.5 font-[var(--font-mono)] text-[11px] uppercase tracking-[0.14em]",
|
|
TONE_STYLES[meta.tone],
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{meta.label}
|
|
</span>
|
|
);
|
|
}
|