oa-frontend-pretrial-cleanup 收敛PC试运行前页面风格
这个提交包含在:
@@ -14,7 +14,7 @@ import { useAuth } from "../auth/AuthContext";
|
||||
import { hasAnyPermission } from "../auth/permissions";
|
||||
import { DateTextInput } from "../components/common/DateTextInput";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { FilterBar, ListWorkspace, StatStrip, WorkspaceHeader } from "../components/patterns/ListWorkspace";
|
||||
import { FilterBar, ListWorkspace, StatStrip, WorkspaceHeader, WorkspaceNotice } from "../components/patterns/ListWorkspace";
|
||||
import { MetricCard } from "../components/patterns/MetricCard";
|
||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||
import { Button } from "../components/ui/button";
|
||||
@@ -22,6 +22,7 @@ import { Input } from "../components/ui/input";
|
||||
import { Label } from "../components/ui/label";
|
||||
import { Textarea } from "../components/ui/textarea";
|
||||
import { formatDisplayDate } from "../lib/displayFormat";
|
||||
import { hasTrialNoise } from "../lib/trialNoise";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const STATUS_OPTIONS: Array<{ value: "all" | ProjectStatus; label: string }> = [
|
||||
@@ -76,6 +77,7 @@ export function ProjectsPage() {
|
||||
const [startsAt, setStartsAt] = useState("");
|
||||
const [targetEndAt, setTargetEndAt] = useState("");
|
||||
const [isConfidential, setIsConfidential] = useState(false);
|
||||
const [showTrialData, setShowTrialData] = useState(false);
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: ["projects", "list", status, mine, q],
|
||||
@@ -99,8 +101,18 @@ export function ProjectsPage() {
|
||||
);
|
||||
|
||||
const projectItems = listQuery.data?.items ?? [];
|
||||
const visibleProjectItems = useMemo(
|
||||
() =>
|
||||
showTrialData
|
||||
? projectItems
|
||||
: projectItems.filter(
|
||||
(item) => !hasTrialNoise(item.projectNo, item.name, item.clientName, item.clientAlias, item.summary),
|
||||
),
|
||||
[projectItems, showTrialData],
|
||||
);
|
||||
const hiddenTrialProjectCount = projectItems.length - visibleProjectItems.length;
|
||||
const projectStats = useMemo(() => {
|
||||
return projectItems.reduce(
|
||||
return visibleProjectItems.reduce(
|
||||
(acc, item) => {
|
||||
acc.total += 1;
|
||||
if (item.status === "active") {
|
||||
@@ -112,7 +124,7 @@ export function ProjectsPage() {
|
||||
},
|
||||
{ total: 0, active: 0, overdueMilestones: 0, openRisks: 0 },
|
||||
);
|
||||
}, [projectItems]);
|
||||
}, [visibleProjectItems]);
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
@@ -183,7 +195,7 @@ export function ProjectsPage() {
|
||||
|
||||
<div className="page-shell-grid">
|
||||
<div className="space-y-6">
|
||||
<FilterBar className="grid gap-3 md:grid-cols-[minmax(0,1fr),180px,160px]">
|
||||
<FilterBar className="grid gap-3 md:grid-cols-[minmax(0,1fr),180px,160px,160px]">
|
||||
<Input placeholder="搜索项目编号、项目名、客户名" value={q} onChange={(event) => updateSearch({ q: event.target.value })} />
|
||||
<select className="select-std" value={status} onChange={(event) => updateSearch({ status: event.target.value })}>
|
||||
{STATUS_OPTIONS.map((item) => (
|
||||
@@ -195,18 +207,27 @@ export function ProjectsPage() {
|
||||
<Button type="button" variant={mine ? "default" : "secondary"} onClick={() => updateSearch({ mine: !mine })}>
|
||||
{mine ? "只看我的" : "切换到我的"}
|
||||
</Button>
|
||||
<Button type="button" variant={showTrialData ? "default" : "secondary"} onClick={() => setShowTrialData((value) => !value)}>
|
||||
{showTrialData ? "隐藏测试数据" : "显示测试数据"}
|
||||
</Button>
|
||||
</FilterBar>
|
||||
|
||||
{!showTrialData && hiddenTrialProjectCount > 0 ? (
|
||||
<WorkspaceNotice title={`已隐藏 ${hiddenTrialProjectCount} 条 TEST/SMOKE 项目`} tone="neutral">
|
||||
试运行默认只展示正式项目;需要排障时可临时显示测试数据。
|
||||
</WorkspaceNotice>
|
||||
) : null}
|
||||
|
||||
<SectionPanel
|
||||
title="项目列表"
|
||||
description={projectItems.length ? `当前筛出 ${projectItems.length} 个项目` : "按项目名、客户或状态快速缩小范围"}
|
||||
description={visibleProjectItems.length ? `当前显示 ${visibleProjectItems.length} 个项目` : "按项目名、客户或状态快速缩小范围"}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{listQuery.isLoading ? <div className="text-sm text-zinc-500">项目加载中...</div> : null}
|
||||
{listQuery.isError ? <div className="text-sm text-red-600">{resolveError(listQuery.error, "加载项目失败")}</div> : null}
|
||||
|
||||
<div className="grid gap-4">
|
||||
{projectItems.map((item) => (
|
||||
{visibleProjectItems.map((item) => (
|
||||
<Link key={item.id} to={`/projects/${item.id}`} className="card-std block space-y-3 p-5 hover:border-primary/40">
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
@@ -228,7 +249,7 @@ export function ProjectsPage() {
|
||||
<div className="text-sm text-zinc-500">{summaryLine(item)}</div>
|
||||
</Link>
|
||||
))}
|
||||
{!listQuery.isLoading && !projectItems.length ? (
|
||||
{!listQuery.isLoading && !visibleProjectItems.length ? (
|
||||
<div className="rounded-xl border border-dashed border-zinc-200 bg-zinc-50 px-4 py-8 text-sm text-zinc-500">
|
||||
当前没有符合筛选条件的项目。
|
||||
</div>
|
||||
|
||||
在新工单中引用
屏蔽一个用户