feat: rewrite orders page as dense list
This commit is contained in:
@@ -3,16 +3,16 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardEyebrow, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { MetricChip } from "@/components/ui/metric-chip";
|
||||
import { PageHeader } from "@/components/ui/page-header";
|
||||
import { StatusBadge } from "@/components/ui/status-badge";
|
||||
import { ORDER_STATUS_META } from "@/lib/types/status";
|
||||
import type { OrderStatus } from "@/lib/types/backend";
|
||||
import type { OrderSummaryVM } from "@/lib/types/view-models";
|
||||
import {
|
||||
OrdersToolbar,
|
||||
type OrderFilterServiceMode,
|
||||
type OrderFilterStatus,
|
||||
} from "@/features/orders/components/orders-toolbar";
|
||||
import { OrdersTable } from "@/features/orders/components/orders-table";
|
||||
|
||||
type FilterStatus = OrderStatus | "all";
|
||||
type PaginationData = {
|
||||
page: number;
|
||||
limit: number;
|
||||
@@ -28,10 +28,10 @@ type OrdersHomeProps = {
|
||||
onOpenWorkflow?: (orderId: string) => void;
|
||||
onPageChange?: (page: number) => void;
|
||||
onQuerySubmit?: (query: string) => void;
|
||||
onStatusChange?: (status: FilterStatus) => void;
|
||||
onStatusChange?: (status: OrderFilterStatus) => void;
|
||||
recentOrders: OrderSummaryVM[];
|
||||
selectedQuery?: string;
|
||||
selectedStatus?: FilterStatus;
|
||||
selectedStatus?: OrderFilterStatus;
|
||||
totalPages?: number;
|
||||
};
|
||||
|
||||
@@ -47,27 +47,13 @@ type OrdersOverviewEnvelope = {
|
||||
};
|
||||
|
||||
const TITLE_MESSAGE = "最近订单已接入真实后端接口";
|
||||
const DEFAULT_MESSAGE = "当前首页展示真实最近订单,同时保留订单号直达入口,方便快速跳转详情和流程。";
|
||||
const DEFAULT_MESSAGE = "当前页面直接展示真实订单列表,支持关键词、状态和分页操作。";
|
||||
const DEFAULT_PAGINATION: PaginationData = {
|
||||
page: 1,
|
||||
limit: 6,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
};
|
||||
const ORDER_STATUS_FILTER_OPTIONS: Array<{
|
||||
label: string;
|
||||
value: FilterStatus;
|
||||
}> = [
|
||||
{ value: "all", label: "全部状态" },
|
||||
...Object.entries(ORDER_STATUS_META).map(([value, meta]) => ({
|
||||
value: value as OrderStatus,
|
||||
label: meta.label,
|
||||
})),
|
||||
];
|
||||
|
||||
function formatTimestamp(timestamp: string) {
|
||||
return timestamp.replace("T", " ").replace("Z", " UTC");
|
||||
}
|
||||
|
||||
export function OrdersHome({
|
||||
currentPage = 1,
|
||||
@@ -83,190 +69,55 @@ export function OrdersHome({
|
||||
selectedStatus = "all",
|
||||
totalPages = 0,
|
||||
}: OrdersHomeProps) {
|
||||
const [lookupValue, setLookupValue] = useState("");
|
||||
const [queryValue, setQueryValue] = useState(selectedQuery);
|
||||
const normalizedLookup = lookupValue.trim();
|
||||
const canLookup = /^\d+$/.test(normalizedLookup);
|
||||
const effectiveTotalPages = Math.max(totalPages, 1);
|
||||
const [serviceModeFilter, setServiceModeFilter] =
|
||||
useState<OrderFilterServiceMode>("all");
|
||||
|
||||
useEffect(() => {
|
||||
setQueryValue(selectedQuery);
|
||||
}, [selectedQuery]);
|
||||
|
||||
const visibleOrders =
|
||||
serviceModeFilter === "all"
|
||||
? recentOrders
|
||||
: recentOrders.filter((order) => order.serviceMode === serviceModeFilter);
|
||||
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<section className="space-y-6">
|
||||
<PageHeader
|
||||
eyebrow="Orders home"
|
||||
title="订单总览"
|
||||
description="这里是后台首页入口,当前已经接入真实最近订单列表,并保留订单号直达入口方便快速处理。"
|
||||
meta="真实列表入口"
|
||||
description="订单页直接承担扫描、筛选和跳转职责,不再把首屏浪费在入口说明和大卡片上。"
|
||||
meta="真实列表页"
|
||||
/>
|
||||
|
||||
<div className="rounded-[28px] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-6 py-5">
|
||||
<p className="text-lg font-semibold tracking-[-0.02em] text-[#7a5323]">
|
||||
{TITLE_MESSAGE}
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-7 text-[#7a5323]">
|
||||
{message}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<MetricChip label="mode" value="真实订单列表" />
|
||||
<MetricChip label="rows" value={visibleOrders.length} />
|
||||
<MetricChip label="message" value={TITLE_MESSAGE} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_380px]">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="space-y-2">
|
||||
<CardEyebrow>Direct lookup</CardEyebrow>
|
||||
<div className="space-y-1">
|
||||
<CardTitle>订单号直达</CardTitle>
|
||||
<CardDescription>
|
||||
保留订单号和流程号的直接入口,适合在列表之外快速跳转到指定订单。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm font-medium text-[var(--ink-strong)]">
|
||||
订单号
|
||||
</span>
|
||||
<input
|
||||
value={lookupValue}
|
||||
onChange={(event) => setLookupValue(event.target.value)}
|
||||
placeholder="输入订单号,例如 4201"
|
||||
className="min-h-12 w-full rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 text-sm text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button
|
||||
disabled={!canLookup}
|
||||
onClick={() => onOpenOrder?.(normalizedLookup)}
|
||||
>
|
||||
打开订单详情
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={!canLookup}
|
||||
onClick={() => onOpenWorkflow?.(normalizedLookup)}
|
||||
>
|
||||
打开流程详情
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-sm text-[var(--ink-muted)]">{message}</p>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div className="space-y-2">
|
||||
<CardEyebrow>Recent visits</CardEyebrow>
|
||||
<div className="space-y-1">
|
||||
<CardTitle>最近访问</CardTitle>
|
||||
<CardDescription>
|
||||
这里已经接入真实后端最近订单列表,页面结构继续沿用首版设计。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 lg:min-w-[360px]">
|
||||
<label className="flex flex-col gap-2 text-sm text-[var(--ink-strong)]">
|
||||
<span className="font-medium">订单关键词搜索</span>
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
aria-label="订单关键词搜索"
|
||||
value={queryValue}
|
||||
onChange={(event) => setQueryValue(event.target.value)}
|
||||
placeholder="搜索订单号或 workflow_id"
|
||||
className="min-h-12 flex-1 rounded-[18px] border border-[var(--border-strong)] bg-[var(--surface-muted)] px-4 text-sm text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)] focus:ring-2 focus:ring-[var(--accent-ring)]"
|
||||
/>
|
||||
<Button onClick={() => onQuerySubmit?.(queryValue.trim())}>
|
||||
搜索订单
|
||||
</Button>
|
||||
</div>
|
||||
</label>
|
||||
<label className="flex flex-col gap-2 text-sm text-[var(--ink-strong)]">
|
||||
<span className="font-medium">订单状态筛选</span>
|
||||
<select
|
||||
aria-label="订单状态筛选"
|
||||
className="min-h-12 rounded-[18px] border border-[var(--border-strong)] bg-[var(--surface-muted)] px-4 text-sm text-[var(--ink-strong)] focus:outline-none focus:ring-2 focus:ring-[var(--accent-ring)]"
|
||||
value={selectedStatus}
|
||||
onChange={(event) =>
|
||||
onStatusChange?.(event.target.value as FilterStatus)
|
||||
}
|
||||
>
|
||||
{ORDER_STATUS_FILTER_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{isLoadingRecent ? (
|
||||
<div className="rounded-[24px] border border-dashed border-[var(--border-strong)] bg-[var(--surface-muted)] px-5 py-6 text-sm text-[var(--ink-muted)]">
|
||||
正在加载最近访问记录…
|
||||
</div>
|
||||
) : null}
|
||||
<OrdersToolbar
|
||||
currentPage={currentPage}
|
||||
query={queryValue}
|
||||
serviceMode={serviceModeFilter}
|
||||
status={selectedStatus}
|
||||
totalPages={totalPages}
|
||||
onPageChange={onPageChange}
|
||||
onQueryChange={setQueryValue}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
onServiceModeChange={setServiceModeFilter}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
|
||||
{!isLoadingRecent && recentOrders.length ? (
|
||||
recentOrders.map((order) => (
|
||||
<div
|
||||
key={order.orderId}
|
||||
className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-4"
|
||||
>
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-semibold text-[var(--ink-strong)]">
|
||||
订单 #{order.orderId}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--ink-muted)]">
|
||||
工作流 {order.workflowId ?? "未关联"}
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge status={order.status} />
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap items-center gap-3 text-xs text-[var(--ink-muted)]">
|
||||
<StatusBadge variant="workflowStep" status={order.currentStep} />
|
||||
<span>{order.currentStepLabel}</span>
|
||||
<span>{formatTimestamp(order.updatedAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
|
||||
{!isLoadingRecent && !recentOrders.length ? (
|
||||
<EmptyState
|
||||
eyebrow="No recent orders"
|
||||
title="暂无最近访问记录"
|
||||
description="当前筛选条件下还没有可展示的订单。"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-[var(--border-soft)] pt-3">
|
||||
<p className="text-xs text-[var(--ink-muted)]">
|
||||
第 {Math.min(currentPage, effectiveTotalPages)} / {effectiveTotalPages} 页
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => onPageChange?.(currentPage - 1)}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage >= effectiveTotalPages}
|
||||
onClick={() => onPageChange?.(currentPage + 1)}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<OrdersTable
|
||||
isLoading={isLoadingRecent}
|
||||
items={visibleOrders}
|
||||
onOpenOrder={onOpenOrder}
|
||||
onOpenWorkflow={onOpenWorkflow}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -277,7 +128,8 @@ export function OrdersHomeScreen() {
|
||||
const [message, setMessage] = useState(DEFAULT_MESSAGE);
|
||||
const [isLoadingRecent, setIsLoadingRecent] = useState(true);
|
||||
const [selectedQuery, setSelectedQuery] = useState("");
|
||||
const [selectedStatus, setSelectedStatus] = useState<FilterStatus>("all");
|
||||
const [selectedStatus, setSelectedStatus] =
|
||||
useState<OrderFilterStatus>("all");
|
||||
const [pagination, setPagination] = useState<PaginationData>(DEFAULT_PAGINATION);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user