diff --git a/furniture-3d-system/src/app/intake/work-record/WorkRecordTable.tsx b/furniture-3d-system/src/app/intake/work-record/WorkRecordTable.tsx index 045cf464..366ca98b 100644 --- a/furniture-3d-system/src/app/intake/work-record/WorkRecordTable.tsx +++ b/furniture-3d-system/src/app/intake/work-record/WorkRecordTable.tsx @@ -1,6 +1,8 @@ "use client"; +import { useState, type ReactNode } from "react"; import { Trash2 } from "lucide-react"; +import { SkuColumnResizableFrame } from "@/app/intake/non-integrated/components/SkuTableFields"; import { WorkRecordPrimarySelect } from "@/app/intake/work-record/WorkRecordPrimarySelect"; import { getWorkRecordRequiredFieldErrors, @@ -9,6 +11,49 @@ import { } from "@/app/intake/work-record/workRecordValidation"; import type { WorkRecordRow, WorkRecordScoreRule } from "@/lib/work-record"; +const WORK_RECORD_COLUMN_DEFAULT_WIDTHS = { + date: 128, + designer: 112, + primary: 144, + secondary: 256, + approvalOrTask: 224, + spu: 128, + quantity: 96, + unitScore: 96, + taskTotal: 112, + fileStoragePath: 180, +} as const; + +const WORK_RECORD_ACTION_COLUMN_WIDTH = 48; + +const WORK_RECORD_COLUMN_HEADERS: Array<{ + key: WorkRecordColumnWidthKey; + label: string; +}> = [ + { key: "date", label: "日期" }, + { key: "designer", label: "设计师" }, + { key: "primary", label: "一级" }, + { key: "secondary", label: "二级" }, + { key: "approvalOrTask", label: "审批编号/具体事宜" }, + { key: "spu", label: "SPU" }, + { key: "quantity", label: "数量" }, + { key: "unitScore", label: "单分值" }, + { key: "taskTotal", label: "单任务合计" }, + { key: "fileStoragePath", label: "文件储存路径" }, +]; + +type WorkRecordColumnWidthKey = keyof typeof WORK_RECORD_COLUMN_DEFAULT_WIDTHS; + +function normalizeWorkRecordColumnWidth( + column: WorkRecordColumnWidthKey, + width: number, +) { + return Math.min( + 520, + Math.max(WORK_RECORD_COLUMN_DEFAULT_WIDTHS[column], Math.round(width)), + ); +} + function formatScore(value: number | null) { return value === null ? "" : String(value); } @@ -32,6 +77,29 @@ function inputClassName(invalid: boolean) { }`; } +function WorkRecordColumnResizeFrame({ + children, + column, + columnWidths, + onColumnResize, +}: { + children: ReactNode; + column: WorkRecordColumnWidthKey; + columnWidths: Record; + onColumnResize: (column: WorkRecordColumnWidthKey, width: number) => void; +}) { + return ( + onColumnResize(column, width)} + > + {children} + + ); +} + export function WorkRecordTable({ canEditRecords, currentUserId, @@ -50,22 +118,49 @@ export function WorkRecordTable({ scoreRules: WorkRecordScoreRule[]; }) { const primaryOptions = Array.from(new Set(scoreRules.map((rule) => rule.primary))); + const [columnWidths, setColumnWidths] = useState>( + () => ({ ...WORK_RECORD_COLUMN_DEFAULT_WIDTHS }), + ); + const tableWidth = WORK_RECORD_COLUMN_HEADERS.reduce( + (total, column) => total + columnWidths[column.key], + WORK_RECORD_ACTION_COLUMN_WIDTH, + ); + + function handleColumnResize(column: WorkRecordColumnWidthKey, width: number) { + setColumnWidths((current) => ({ + ...current, + [column]: normalizeWorkRecordColumnWidth(column, width), + })); + } + + const columnResizeProps = { + columnWidths, + onColumnResize: handleColumnResize, + }; return (
- +
+ + {WORK_RECORD_COLUMN_HEADERS.map((column) => ( + + ))} + + - - - - - - - - - - + {WORK_RECORD_COLUMN_HEADERS.map((column) => ( + + ))} + @@ -83,58 +178,62 @@ export function WorkRecordTable({ return ( - - - + + + - - - - - - - + + + ); diff --git a/furniture-3d-system/src/app/intake/work-record/WorkRecordWorkspace.test.tsx b/furniture-3d-system/src/app/intake/work-record/WorkRecordWorkspace.test.tsx index b46fabf5..1f81ba4f 100644 --- a/furniture-3d-system/src/app/intake/work-record/WorkRecordWorkspace.test.tsx +++ b/furniture-3d-system/src/app/intake/work-record/WorkRecordWorkspace.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { WorkRecordWorkspace } from "@/app/intake/work-record/WorkRecordWorkspace"; import { @@ -209,4 +209,25 @@ describe("WorkRecordWorkspace", () => { expect(quantityInput).toHaveAttribute("step", "1"); expect(await screen.findByText("数量必须是正整数")).toBeInTheDocument(); }); + + it("keeps the file storage path column compact and lets users widen it", async () => { + const { container } = renderWorkspace(); + await screen.findByDisplayValue("SPU-001"); + + const columns = container.querySelectorAll("colgroup col"); + expect(columns).toHaveLength(11); + expect(columns[9]).toHaveStyle({ width: "180px" }); + + const resizeHandles = screen.getAllByRole("separator"); + const pointerDown = new Event("pointerdown", { bubbles: true }); + Object.defineProperty(pointerDown, "clientX", { value: 100 }); + fireEvent(resizeHandles[9], pointerDown); + + const pointerMove = new Event("pointermove"); + Object.defineProperty(pointerMove, "clientX", { value: 220 }); + fireEvent(window, pointerMove); + fireEvent.pointerUp(window); + + expect(columns[9]).toHaveStyle({ width: "300px" }); + }); });
日期设计师一级二级审批编号/具体事宜SPU数量单分值单任务合计文件储存路径 + {column.label} +
- { - const rules = scoreRules.filter((rule) => rule.primary === primary); - const onlyRule = rules.length === 1 ? rules[0] : null; - const unitScore = onlyRule?.unitScore ?? null; + + + { + const rules = scoreRules.filter((rule) => rule.primary === primary); + const onlyRule = rules.length === 1 ? rules[0] : null; + const unitScore = onlyRule?.unitScore ?? null; + const nextRecord = onUpdateRecord(record.id, { + primary, + secondary: onlyRule?.secondary ?? "", + scoreRuleId: onlyRule?.id ?? null, + unitScore, + taskTotal: calculateTaskTotal(record.quantity, unitScore), + }); + commitIfReady(nextRecord); + }} + /> + + + + + - - - onUpdateRecord(record.id, { approvalOrTask: event.target.value })} onBlur={() => { + + onUpdateRecord(record.id, { approvalOrTask: event.target.value })} onBlur={() => { if (canCommitRecord(record)) onCommitRecord(record.id); - }} className={inputClassName(Boolean(requiredErrors.approvalOrTask))} /> - {requiredErrors.approvalOrTask ?

{requiredErrors.approvalOrTask}

: null} + }} className={inputClassName(Boolean(requiredErrors.approvalOrTask))} />
+ {requiredErrors.approvalOrTask ?

{requiredErrors.approvalOrTask}

: null}
- onUpdateRecord(record.id, { spu: event.target.value })} onBlur={() => { + + onUpdateRecord(record.id, { spu: event.target.value })} onBlur={() => { if (canCommitRecord(record)) onCommitRecord(record.id); - }} className={inputClassName(Boolean(requiredErrors.spu))} /> - {requiredErrors.spu ?

{requiredErrors.spu}

: null} + }} className={inputClassName(Boolean(requiredErrors.spu))} />
+ {requiredErrors.spu ?

{requiredErrors.spu}

: null}
{ + { const quantity = event.target.value; onUpdateRecord(record.id, { quantity, @@ -142,10 +241,10 @@ export function WorkRecordTable({ }); }} onBlur={() => { if (canCommitRecord(record)) onCommitRecord(record.id); - }} className={inputClassName(hasInvalidQuantity)} /> - {hasInvalidQuantity ?

数量必须是正整数

: null} + }} className={inputClassName(hasInvalidQuantity)} />
+ {hasInvalidQuantity ?

数量必须是正整数

: null}
{ + { const unitScore = event.target.value === "" ? null : Number(event.target.value); onUpdateRecord(record.id, { unitScore, @@ -153,15 +252,17 @@ export function WorkRecordTable({ }); }} onBlur={() => { if (canCommitRecord(record)) onCommitRecord(record.id); - }} className="h-8 w-full rounded-[6px] border border-apple-separator bg-white px-2 text-xs text-apple-label outline-none transition focus:border-apple-blue disabled:cursor-default disabled:bg-apple-bg-secondary disabled:text-apple-label-secondary" /> -
+ }} className="h-8 w-full rounded-[6px] border border-apple-separator bg-white px-2 text-xs text-apple-label outline-none transition focus:border-apple-blue disabled:cursor-default disabled:bg-apple-bg-secondary disabled:text-apple-label-secondary" />
+ onUpdateRecord(record.id, { fileStoragePath: event.target.value })} onBlur={() => { if (canCommitRecord(record)) onCommitRecord(record.id); - }} className="h-8 min-w-0 flex-1 rounded-[6px] border border-apple-separator bg-white px-2 text-xs text-apple-label outline-none transition focus:border-apple-blue disabled:cursor-default disabled:bg-apple-bg-secondary disabled:text-apple-label-secondary" /> - {canEdit ? : null} - + }} className="h-8 w-full rounded-[6px] border border-apple-separator bg-white px-2 text-xs text-apple-label outline-none transition focus:border-apple-blue disabled:cursor-default disabled:bg-apple-bg-secondary disabled:text-apple-label-secondary" /> + + + {canEdit ? : null}