feat: polish intake detail navigation #13
@ -6,6 +6,7 @@ import type { ReactNode } from 'react'
|
||||
import IntakeClient from './IntakeClient'
|
||||
import {
|
||||
createDefaultDocumentWorkspaceConfirmations,
|
||||
getNonIntegratedDocumentWorkspaceCategoryLabel,
|
||||
NON_INTEGRATED_BRAND_PREFIX_MAP,
|
||||
NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES,
|
||||
NON_INTEGRATED_STORAGE_KEY,
|
||||
@ -544,11 +545,18 @@ describe('IntakeClient', () => {
|
||||
expect(pendingRow).not.toBeNull()
|
||||
expect(within(checkedRow as HTMLElement).getByText('已检查')).toBeInTheDocument()
|
||||
expect(within(pendingRow as HTMLElement).getByText('待确认')).toBeInTheDocument()
|
||||
expect(screen.getByRole('columnheader', { name: '产品实拍' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('columnheader', { name: '产品资料' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('columnheader', { name: '仓库抽检' })).toBeInTheDocument()
|
||||
expect(
|
||||
within(checkedRow as HTMLElement).getByLabelText(`${readyCategory}已检查且有数据`),
|
||||
within(checkedRow as HTMLElement).getByLabelText(
|
||||
`${getNonIntegratedDocumentWorkspaceCategoryLabel(readyCategory)}已检查且有数据`,
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
within(pendingRow as HTMLElement).getByLabelText(`${pendingCategory}未检查或无数据`),
|
||||
within(pendingRow as HTMLElement).getByLabelText(
|
||||
`${getNonIntegratedDocumentWorkspaceCategoryLabel(pendingCategory)}未检查或无数据`,
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Check,
|
||||
Copy,
|
||||
Eye,
|
||||
Layers3,
|
||||
@ -13,6 +14,7 @@ import {
|
||||
Pencil,
|
||||
Printer,
|
||||
Trash2,
|
||||
X as XIcon,
|
||||
} from "lucide-react";
|
||||
import MaterialLibraryClient from "@/app/asset-center/material-library/MaterialLibraryClient";
|
||||
import AccessoryLibraryPanel from "@/app/intake/AccessoryLibraryPanel";
|
||||
@ -35,6 +37,7 @@ import {
|
||||
NON_INTEGRATED_BRAND_PREFIX_MAP,
|
||||
NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES,
|
||||
NON_INTEGRATED_PRODUCTS_UPDATED,
|
||||
getNonIntegratedDocumentWorkspaceCategoryLabel,
|
||||
saveNonIntegratedProduct,
|
||||
type NonIntegratedProductRecord,
|
||||
} from "./non-integrated/nonIntegratedStorage";
|
||||
@ -148,6 +151,31 @@ function SpuLabel() {
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentStatusMark({
|
||||
category,
|
||||
ready,
|
||||
}: {
|
||||
category: NonIntegratedDocumentStatusCategory;
|
||||
ready: boolean;
|
||||
}) {
|
||||
const Icon = ready ? Check : XIcon;
|
||||
const label = getNonIntegratedDocumentWorkspaceCategoryLabel(category);
|
||||
|
||||
return (
|
||||
<span
|
||||
aria-label={`${label}${ready ? "已检查且有数据" : "未检查或无数据"}`}
|
||||
title={ready ? "已检查且有数据" : "未检查或无数据"}
|
||||
className={`inline-flex h-6 w-6 items-center justify-center rounded-md border transition-colors ${
|
||||
ready
|
||||
? "border-zinc-950 bg-zinc-950 text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]"
|
||||
: "border-zinc-200 bg-zinc-50 text-zinc-400"
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-3.5 w-3.5" strokeWidth={2.4} aria-hidden="true" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
type NonIntegratedDocumentStatusCategory =
|
||||
(typeof NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES)[number];
|
||||
|
||||
@ -156,6 +184,10 @@ const NON_INTEGRATED_DOCUMENT_STATUS_COLUMNS = [
|
||||
] as NonIntegratedDocumentStatusCategory[];
|
||||
|
||||
const PENDING_PAGE_SIZE = 25;
|
||||
const TABLE_ACTION_BUTTON_BASE =
|
||||
"!h-8 !min-h-8 !w-8 !rounded-md !px-0 !py-0 !shadow-none";
|
||||
const TABLE_ACTION_BUTTON_SECONDARY = `${TABLE_ACTION_BUTTON_BASE} !border-zinc-200 !bg-white !text-zinc-500 hover:!border-zinc-300 hover:!bg-zinc-50 hover:!text-zinc-950 focus:!border-zinc-400 disabled:!border-zinc-100 disabled:!bg-zinc-50 disabled:!text-zinc-300`;
|
||||
const TABLE_ACTION_BUTTON_PRIMARY = `${TABLE_ACTION_BUTTON_BASE} !border-zinc-950 !bg-zinc-950 !text-white hover:!border-zinc-800 hover:!bg-zinc-800 focus:!border-zinc-600 disabled:!border-zinc-200 disabled:!bg-zinc-200 disabled:!text-zinc-400`;
|
||||
|
||||
const BOM_LIBRARY_OPTIONS: {
|
||||
key: BomLibraryKey;
|
||||
@ -1179,7 +1211,7 @@ function NonIntegratedPendingDetail({
|
||||
key={category}
|
||||
className="min-w-[88px] px-3 py-2.5 text-center font-apple-medium whitespace-nowrap"
|
||||
>
|
||||
{category}
|
||||
{getNonIntegratedDocumentWorkspaceCategoryLabel(category)}
|
||||
</th>
|
||||
))}
|
||||
<th className="sticky right-0 z-20 min-w-[152px] border-l border-apple-separator bg-apple-bg-primary px-2.5 py-2.5 text-center font-apple-medium whitespace-nowrap shadow-[-10px_0_14px_-14px_rgba(15,23,42,0.28)]">
|
||||
@ -1280,16 +1312,10 @@ function NonIntegratedPendingDetail({
|
||||
key={category}
|
||||
className="px-2 py-3 text-center align-middle"
|
||||
>
|
||||
<span
|
||||
aria-label={`${category}${isReady ? "已检查且有数据" : "未检查或无数据"}`}
|
||||
className={`inline-flex h-7 w-7 items-center justify-center rounded-full text-sm font-apple-semibold ${
|
||||
isReady
|
||||
? "bg-emerald-100 text-emerald-700"
|
||||
: "bg-rose-100 text-rose-500"
|
||||
}`}
|
||||
>
|
||||
{isReady ? "√" : "×"}
|
||||
</span>
|
||||
<DocumentStatusMark
|
||||
category={category}
|
||||
ready={isReady}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
},
|
||||
@ -1304,7 +1330,7 @@ function NonIntegratedPendingDetail({
|
||||
<Button
|
||||
variant="plain"
|
||||
size="small"
|
||||
className="!h-8 !min-h-8 !w-8 !rounded-apple-lg !px-0 !py-0"
|
||||
className={TABLE_ACTION_BUTTON_SECONDARY}
|
||||
>
|
||||
<Eye className="h-4 w-4" aria-hidden="true" />
|
||||
<span className="sr-only">查看</span>
|
||||
@ -1316,8 +1342,9 @@ function NonIntegratedPendingDetail({
|
||||
title="修改"
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
size="small"
|
||||
className="!h-8 !min-h-8 !w-8 !rounded-apple-lg !px-0 !py-0"
|
||||
className={TABLE_ACTION_BUTTON_PRIMARY}
|
||||
>
|
||||
<Pencil
|
||||
className="h-4 w-4"
|
||||
@ -1331,7 +1358,7 @@ function NonIntegratedPendingDetail({
|
||||
size="small"
|
||||
aria-label="复制"
|
||||
title="复制"
|
||||
className="!h-8 !min-h-8 !w-8 !rounded-apple-lg !px-0 !py-0"
|
||||
className={TABLE_ACTION_BUTTON_SECONDARY}
|
||||
onClick={() => openCopyDialog(product)}
|
||||
disabled={
|
||||
getAvailableCopyBrands(product).length === 0
|
||||
@ -1345,7 +1372,7 @@ function NonIntegratedPendingDetail({
|
||||
size="small"
|
||||
aria-label="删除"
|
||||
title="删除"
|
||||
className="!h-8 !min-h-8 !w-8 !rounded-apple-lg !px-0 !py-0"
|
||||
className={TABLE_ACTION_BUTTON_SECONDARY}
|
||||
onClick={() => handleDelete(product)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" aria-hidden="true" />
|
||||
|
||||
@ -460,6 +460,19 @@ describe("NonIntegratedDetailClient", () => {
|
||||
);
|
||||
|
||||
const sidebar = screen.getByTestId("document-workspace-sidebar");
|
||||
expect(
|
||||
sidebar.querySelector<HTMLButtonElement>(
|
||||
'button[data-testid="document-workspace-basic-info-trigger"]',
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
Array.from(
|
||||
sidebar.querySelectorAll<HTMLButtonElement>(
|
||||
'button[data-testid^="document-workspace"]',
|
||||
),
|
||||
)[0]?.getAttribute("data-testid"),
|
||||
).toBe("document-workspace-basic-info-trigger");
|
||||
|
||||
const triggerIds = Array.from(
|
||||
sidebar.querySelectorAll<HTMLButtonElement>(
|
||||
'button[data-testid^="document-workspace-trigger-"]',
|
||||
@ -1120,14 +1133,16 @@ describe("NonIntegratedDetailClient", () => {
|
||||
|
||||
await actor.click(screen.getByTestId("document-workspace-trigger-产品实拍图"));
|
||||
|
||||
expect(screen.getByRole("button", { name: "返回详情" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId("document-workspace-basic-info-trigger"),
|
||||
).toHaveAttribute("data-active", "false");
|
||||
|
||||
await actor.click(screen.getByRole("button", { name: "返回详情" }));
|
||||
await actor.click(screen.getByTestId("document-workspace-basic-info-trigger"));
|
||||
|
||||
expect(screen.getByText(/产品开发明细/)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "返回详情" }),
|
||||
).not.toBeInTheDocument();
|
||||
screen.getByTestId("document-workspace-basic-info-trigger"),
|
||||
).toHaveAttribute("data-active", "true");
|
||||
});
|
||||
|
||||
it("marks copied document categories as pending until the current category is confirmed", async () => {
|
||||
|
||||
@ -100,6 +100,7 @@ import {
|
||||
NON_INTEGRATED_BRAND_PREFIX_MAP,
|
||||
NON_INTEGRATED_DUPLICATE_SPU_MESSAGE,
|
||||
NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES,
|
||||
getNonIntegratedDocumentWorkspaceCategoryLabel,
|
||||
getNonIntegratedProductById,
|
||||
hasPendingDocumentWorkspaceConfirmation,
|
||||
replaceNonIntegratedProducts,
|
||||
@ -1083,19 +1084,7 @@ const DOCUMENT_WORKSPACE_COPY: Record<
|
||||
function getDocumentWorkspaceCategoryLabel(
|
||||
category: DocumentWorkspaceCategory,
|
||||
) {
|
||||
if (category === PRODUCT_PHOTO_CATEGORY) {
|
||||
return "产品实拍";
|
||||
}
|
||||
|
||||
if (category === DETECTION_WORKSPACE_CATEGORY) {
|
||||
return "仓库抽检";
|
||||
}
|
||||
|
||||
if (category === PRODUCT_DATA_WORKSPACE_CATEGORY) {
|
||||
return "产品资料";
|
||||
}
|
||||
|
||||
return category;
|
||||
return getNonIntegratedDocumentWorkspaceCategoryLabel(category);
|
||||
}
|
||||
|
||||
function getUploadCategoryOptionLabel(category: UploadCategoryKey | string) {
|
||||
@ -15763,27 +15752,15 @@ export default function NonIntegratedDetailClient({
|
||||
图片与文档
|
||||
</p>
|
||||
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1">
|
||||
{!isDocumentWorkspaceMode ? (
|
||||
<Link href="/intake">
|
||||
<Button
|
||||
variant="plain"
|
||||
size="small"
|
||||
className="!min-h-7 !rounded-apple-md !px-2.5 !py-1 !text-xs"
|
||||
>
|
||||
返回列表
|
||||
</Button>
|
||||
</Link>
|
||||
) : null}
|
||||
{isDocumentWorkspaceMode ? (
|
||||
<Link href="/intake">
|
||||
<Button
|
||||
variant="plain"
|
||||
size="small"
|
||||
className="!min-h-7 !rounded-apple-md !px-2.5 !py-1 !text-xs"
|
||||
onClick={() => setDocumentWorkspaceView("detail")}
|
||||
>
|
||||
返回详情
|
||||
返回列表
|
||||
</Button>
|
||||
) : null}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-apple-label-secondary">
|
||||
@ -15791,6 +15768,34 @@ export default function NonIntegratedDetailClient({
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="document-workspace-basic-info-trigger"
|
||||
data-active={
|
||||
documentWorkspaceView === "detail" ? "true" : "false"
|
||||
}
|
||||
onClick={() => setDocumentWorkspaceView("detail")}
|
||||
className={`rounded-apple-lg border px-3 py-2 text-left transition ${
|
||||
documentWorkspaceView === "detail"
|
||||
? "border-apple-label-primary bg-apple-label-primary text-white"
|
||||
: "border-apple-separator bg-apple-bg-primary text-apple-label-primary hover:bg-apple-bg-secondary/40"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-[15px] font-apple-medium leading-5">
|
||||
基础信息
|
||||
</p>
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[11px] ${
|
||||
documentWorkspaceView === "detail"
|
||||
? "bg-white/20 text-white"
|
||||
: "bg-apple-bg-secondary text-apple-label-secondary"
|
||||
}`}
|
||||
>
|
||||
{skuItems.length}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{groupedUploadFiles.map(({ category, files }) => {
|
||||
const isActive =
|
||||
documentWorkspaceView === "document" &&
|
||||
|
||||
@ -47,6 +47,15 @@ export const NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES = [
|
||||
export type NonIntegratedDocumentWorkspaceCategory =
|
||||
(typeof NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES)[number];
|
||||
|
||||
export function getNonIntegratedDocumentWorkspaceCategoryLabel(
|
||||
category: NonIntegratedDocumentWorkspaceCategory | string,
|
||||
) {
|
||||
if (category === "产品实拍图") return "产品实拍";
|
||||
if (category === "产品模型") return "产品资料";
|
||||
if (category === "检测报告") return "仓库抽检";
|
||||
return category;
|
||||
}
|
||||
|
||||
export type NonIntegratedDocumentWorkspaceConfirmationMap = Record<
|
||||
NonIntegratedDocumentWorkspaceCategory,
|
||||
boolean
|
||||
@ -78,6 +87,7 @@ const NON_INTEGRATED_UPLOAD_CATEGORY_MAP: Record<string, string> = {
|
||||
产品核价: "产品核价",
|
||||
工厂质检: "工厂质检",
|
||||
仓库质检: "工厂质检",
|
||||
仓库抽检: "检测报告",
|
||||
产品资料: "产品模型",
|
||||
产品实拍: "产品实拍图",
|
||||
安装资料: "安装方式",
|
||||
|
||||
@ -44,10 +44,18 @@ export function AppShell({ children, user }: AppShellProps) {
|
||||
pathname === '/asset-center' || pathname.startsWith('/asset-center/')
|
||||
const isAssistedAssemblyRoute =
|
||||
pathname === '/assisted-assembly' || pathname.startsWith('/assisted-assembly/')
|
||||
const isDraftGroupsRoute = pathname === '/draft-groups' || pathname.startsWith('/draft-groups/')
|
||||
const isIntakeRoute = pathname === '/intake' || pathname.startsWith('/intake/')
|
||||
const isWarehouseRoute = pathname === '/warehouse' || pathname.startsWith('/warehouse/')
|
||||
const isWorkbenchShellRoute =
|
||||
isHomeRoute ||
|
||||
isAssetCenterRoute ||
|
||||
isAssistedAssemblyRoute ||
|
||||
isDraftGroupsRoute ||
|
||||
isIntakeRoute ||
|
||||
isWarehouseRoute
|
||||
const shouldLockMainViewport =
|
||||
isHomeRoute || isAssetCenterRoute || isAssistedAssemblyRoute || isIntakeRoute || isWarehouseRoute
|
||||
isWorkbenchShellRoute
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldLockMainViewport || typeof document === 'undefined') {
|
||||
@ -82,12 +90,12 @@ export function AppShell({ children, user }: AppShellProps) {
|
||||
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-surface)' }}>
|
||||
<AppTopNav
|
||||
user={user}
|
||||
variant={isHomeRoute || isAssetCenterRoute || isAssistedAssemblyRoute ? 'workbench' : 'default'}
|
||||
variant={isWorkbenchShellRoute ? 'workbench' : 'default'}
|
||||
/>
|
||||
{!isHomeRoute && !isAssetCenterRoute && !isAssistedAssemblyRoute && <AppSideNav />}
|
||||
{!isWorkbenchShellRoute && <AppSideNav />}
|
||||
<main
|
||||
className={
|
||||
isHomeRoute || isAssetCenterRoute || isAssistedAssemblyRoute
|
||||
isWorkbenchShellRoute
|
||||
? 'mt-16 h-[calc(100dvh-64px)] min-h-[calc(100dvh-64px)] overflow-hidden overscroll-none bg-[#f8f9fa]'
|
||||
: shouldLockMainViewport
|
||||
? 'ml-[var(--layout-app-sidebar-width)] mt-14 h-[calc(100vh-56px)] overflow-hidden overscroll-none bg-white'
|
||||
|
||||
@ -52,17 +52,17 @@ describe('AppShell', () => {
|
||||
})
|
||||
|
||||
it('新平台主链页面渲染全局壳子', () => {
|
||||
usePathnameMock.mockReturnValue('/intake')
|
||||
usePathnameMock.mockReturnValue('/draft-groups')
|
||||
|
||||
render(
|
||||
<AppShell>
|
||||
<div>intake content</div>
|
||||
<div>draft groups content</div>
|
||||
</AppShell>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('app-top-nav')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('app-side-nav')).toBeInTheDocument()
|
||||
expect(screen.getByText('intake content')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('app-top-nav')).toHaveAttribute('data-variant', 'workbench')
|
||||
expect(screen.queryByTestId('app-side-nav')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('draft groups content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('首页使用工作台壳子且不再渲染左侧旧导航', () => {
|
||||
@ -92,4 +92,29 @@ describe('AppShell', () => {
|
||||
expect(screen.queryByTestId('app-side-nav')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('assisted assembly content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('正式产品和仓库管理使用顶部工作台导航', () => {
|
||||
usePathnameMock.mockReturnValue('/intake')
|
||||
|
||||
const { rerender } = render(
|
||||
<AppShell>
|
||||
<div>intake content</div>
|
||||
</AppShell>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('app-top-nav')).toHaveAttribute('data-variant', 'workbench')
|
||||
expect(screen.queryByTestId('app-side-nav')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('intake content')).toBeInTheDocument()
|
||||
|
||||
usePathnameMock.mockReturnValue('/warehouse')
|
||||
rerender(
|
||||
<AppShell>
|
||||
<div>warehouse content</div>
|
||||
</AppShell>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('app-top-nav')).toHaveAttribute('data-variant', 'workbench')
|
||||
expect(screen.queryByTestId('app-side-nav')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('warehouse content')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user