feat: bootstrap auto virtual tryon admin frontend
This commit is contained in:
181
src/features/reviews/components/review-image-panel.tsx
Normal file
181
src/features/reviews/components/review-image-panel.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Card, CardContent, CardDescription, CardEyebrow, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { StatusBadge } from "@/components/ui/status-badge";
|
||||
import type { AssetViewModel, OrderDetailVM } from "@/lib/types/view-models";
|
||||
|
||||
type ReviewImagePanelProps = {
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
order: OrderDetailVM | null;
|
||||
selectedAssetId: number | null;
|
||||
onSelectAsset: (assetId: number) => void;
|
||||
};
|
||||
|
||||
function joinClasses(...values: Array<string | false | null | undefined>) {
|
||||
return values.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function collectAssets(order: OrderDetailVM): AssetViewModel[] {
|
||||
const uniqueAssets = new Map<number, AssetViewModel>();
|
||||
|
||||
if (order.finalAsset) {
|
||||
uniqueAssets.set(order.finalAsset.id, order.finalAsset);
|
||||
}
|
||||
|
||||
for (const asset of order.assets) {
|
||||
uniqueAssets.set(asset.id, asset);
|
||||
}
|
||||
|
||||
return Array.from(uniqueAssets.values());
|
||||
}
|
||||
|
||||
export function ReviewImagePanel({
|
||||
error,
|
||||
isLoading,
|
||||
order,
|
||||
selectedAssetId,
|
||||
onSelectAsset,
|
||||
}: ReviewImagePanelProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className="h-full">
|
||||
<CardContent className="px-6 py-8 text-sm text-[var(--ink-muted)]">
|
||||
正在加载订单详情与预览资产…
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="h-full">
|
||||
<CardContent className="px-6 py-8">
|
||||
<div className="rounded-[24px] border border-[#b88472] bg-[#f8ece5] px-5 py-4 text-sm text-[#7f4b3b]">
|
||||
{error}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
return (
|
||||
<Card className="h-full">
|
||||
<CardContent className="px-6 py-8">
|
||||
<EmptyState
|
||||
eyebrow="No active order"
|
||||
title="选择一个待审核订单"
|
||||
description="左侧队列选中订单后,这里会展示当前审核所需的结果图和过程资产。"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const assets = collectAssets(order);
|
||||
const selectedAsset =
|
||||
assets.find((asset) => asset.id === selectedAssetId) ??
|
||||
order.finalAsset ??
|
||||
assets[0] ??
|
||||
null;
|
||||
const emptyStateTitle =
|
||||
order.finalAssetState.kind === "business-empty"
|
||||
? order.finalAssetState.title
|
||||
: "暂无可用预览";
|
||||
const emptyStateDescription =
|
||||
order.finalAssetState.kind === "business-empty"
|
||||
? order.finalAssetState.description
|
||||
: "当前订单还没有能用于审核的结果图或过程资产。";
|
||||
|
||||
return (
|
||||
<Card className="h-full">
|
||||
<CardHeader className="gap-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="space-y-2">
|
||||
<CardEyebrow>Review target</CardEyebrow>
|
||||
<div className="space-y-1">
|
||||
<CardTitle>订单 #{order.orderId}</CardTitle>
|
||||
<CardDescription>
|
||||
当前服务模式为 {order.serviceMode},步骤停留在 {order.currentStepLabel}。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<StatusBadge status={order.status} />
|
||||
<StatusBadge variant="workflowStep" status={order.currentStep} />
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{selectedAsset ? (
|
||||
<div className="rounded-[28px] border border-[var(--border-soft)] bg-[linear-gradient(180deg,rgba(250,247,242,0.96),rgba(238,231,221,0.92))] p-5">
|
||||
<div className="flex min-h-[320px] items-center justify-center rounded-[22px] border border-dashed border-[var(--border-strong)] bg-[rgba(255,255,255,0.55)] p-6 text-center">
|
||||
<div className="space-y-3">
|
||||
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.22em] text-[var(--ink-faint)]">
|
||||
{selectedAsset.isMock ? "Mock preview asset" : "Preview asset"}
|
||||
</p>
|
||||
<h3 className="text-xl font-semibold tracking-[-0.02em] text-[var(--ink-strong)]">
|
||||
{selectedAsset.label}
|
||||
</h3>
|
||||
<p className="mx-auto max-w-lg text-sm leading-7 text-[var(--ink-muted)]">
|
||||
当前阶段优先保证审核流程可用,因此预览面板直接展示资产信息卡;真实图片 URI 可在后续 Task 7 的详情页复用。
|
||||
</p>
|
||||
<code className="inline-flex rounded-full bg-[rgba(74,64,53,0.08)] px-4 py-2 text-xs text-[var(--ink-muted)]">
|
||||
{selectedAsset.uri}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
eyebrow="Asset empty"
|
||||
title={emptyStateTitle}
|
||||
description={emptyStateDescription}
|
||||
/>
|
||||
)}
|
||||
|
||||
{order.hasMockAssets ? (
|
||||
<div className="rounded-[24px] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-5 py-4 text-sm text-[#7a5323]">
|
||||
当前订单包含 mock 资产,审核结论仅用于前后台联调,不代表真实生产结果。
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{assets.length ? (
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{assets.map((asset) => {
|
||||
const isSelected = asset.id === selectedAsset?.id;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={asset.id}
|
||||
type="button"
|
||||
onClick={() => onSelectAsset(asset.id)}
|
||||
className={joinClasses(
|
||||
"rounded-[24px] border px-4 py-4 text-left transition duration-150",
|
||||
isSelected
|
||||
? "border-[var(--accent-primary)] bg-[rgba(110,127,82,0.09)]"
|
||||
: "border-[var(--border-soft)] bg-[var(--surface-muted)] hover:bg-[var(--surface)]",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-semibold text-[var(--ink-strong)]">
|
||||
{asset.label}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--ink-muted)]">{asset.stepLabel}</p>
|
||||
</div>
|
||||
{asset.isMock ? (
|
||||
<span className="rounded-full bg-[rgba(145,104,46,0.12)] px-2.5 py-1 font-[var(--font-mono)] text-[11px] uppercase tracking-[0.18em] text-[#7a5323]">
|
||||
Mock
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user