refactor(intake): extract finished sample shipment workspace

This commit is contained in:
WuYongJie 2026-06-09 13:47:43 +08:00
parent 921c55c97f
commit 9961f8a1b9
2 changed files with 281 additions and 231 deletions

View File

@ -0,0 +1,262 @@
"use client";
import type { ReactNode } from "react";
import { InlineSelect } from "./components/InlineControls";
import {
SkuColumnResizableFrame,
SkuRowField,
} from "./components/SkuTableFields";
import {
FINISHED_SAMPLE_SHIPMENT_COLUMN_DEFAULT_WIDTHS,
FINISHED_SAMPLE_SHIPMENT_SHIPPED_OPTIONS,
type FinishedSampleShipmentColumnWidthKey,
type FinishedSampleShipmentEditableKey,
} from "./skuFieldConfig";
import {
isFinishedSampleShipmentRowComplete,
type ProductFinishedSampleShipmentDisplayRow,
} from "./sampleWorkflowHelpers";
type FinishedSampleShipmentWorkspaceProps = {
allRows: ProductFinishedSampleShipmentDisplayRow[];
displayedRows: ProductFinishedSampleShipmentDisplayRow[];
inheritedSkuBatchTabs: ReactNode;
columnWidths: Record<FinishedSampleShipmentColumnWidthKey, number>;
canEditQuantity: boolean;
canEditDecision: boolean;
canEditTrackingNumber: boolean;
onColumnResize: (
column: FinishedSampleShipmentColumnWidthKey,
width: number,
) => void;
onUpdateRow: (
skuId: string,
key: FinishedSampleShipmentEditableKey,
value: string,
) => void;
onPreviewImage: (src: string, alt: string) => void;
};
export function FinishedSampleShipmentWorkspace({
allRows,
displayedRows,
inheritedSkuBatchTabs,
columnWidths,
canEditQuantity,
canEditDecision,
canEditTrackingNumber,
onColumnResize,
onUpdateRow,
onPreviewImage,
}: FinishedSampleShipmentWorkspaceProps) {
const renderReadOnlyCell = (
value: string,
column: FinishedSampleShipmentColumnWidthKey,
) => (
<SkuRowField
value={value || "-"}
readOnly
mode="singleLineResizable"
columnWidth={columnWidths[column]}
onColumnResize={(width) => onColumnResize(column, width)}
onChange={() => undefined}
/>
);
const renderImageCell = (
row: ProductFinishedSampleShipmentDisplayRow,
index: number,
) => {
if (!row.image) {
return (
<div className="flex h-12 w-12 items-center justify-center rounded border border-dashed border-apple-separator bg-apple-bg-secondary text-[11px] text-apple-label-tertiary">
无图
</div>
);
}
const alt = row.model || `成品寄样图片${index + 1}`;
return (
<button
type="button"
className="block h-12 w-12 overflow-hidden rounded border border-apple-separator bg-apple-bg-secondary"
onClick={() => onPreviewImage(row.image, alt)}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={row.image} alt={alt} className="h-full w-full object-cover" />
</button>
);
};
const renderShipmentInput = (
row: ProductFinishedSampleShipmentDisplayRow,
key: Extract<
FinishedSampleShipmentEditableKey,
"sampleQuantity" | "trackingNumber"
>,
placeholder: string,
readOnly: boolean,
column: Extract<
FinishedSampleShipmentColumnWidthKey,
"sampleQuantity" | "trackingNumber"
>,
) => (
<SkuRowField
value={row[key]}
placeholder={placeholder}
readOnly={readOnly}
mode="singleLineResizable"
tone="manual"
columnWidth={columnWidths[column]}
onColumnResize={(width) => onColumnResize(column, width)}
testId={`finished-sample-shipment-${key}-${row.skuId}`}
onChange={(value) => onUpdateRow(row.skuId, key, value)}
/>
);
return (
<div
data-testid="finished-sample-shipment-workspace"
className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-apple-xl border border-apple-separator bg-apple-bg-primary"
>
<div className="border-b border-apple-separator px-3 py-2">
<div className="flex flex-wrap items-center justify-between gap-2">
{inheritedSkuBatchTabs}
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{allRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再进行成品寄样。
</div>
) : displayedRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[1760px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
{[
"寄样",
"序号",
"图片",
"型号",
"货号",
"套件量",
"供应商名称",
"条码",
"条码规格名称",
"安装方式",
"寄样数量",
"是否寄出",
"快递单号",
].map((label) => (
<th
key={label}
className="whitespace-nowrap border-b border-apple-separator px-2 py-2 text-left font-apple-medium"
>
{label}
</th>
))}
</tr>
</thead>
<tbody>
{displayedRows.map((row, index) => (
<tr
key={row.skuId}
className="border-b border-apple-separator last:border-b-0"
>
<td className="px-2 py-2">
<span
className={`inline-flex whitespace-nowrap rounded-full px-2 py-0.5 text-[11px] ${
isFinishedSampleShipmentRowComplete(row)
? "bg-[#dff7e8] text-[#137a38]"
: "bg-[#fff3df] text-[#9b6511]"
}`}
>
{isFinishedSampleShipmentRowComplete(row)
? "已完成"
: "请寄样"}
</span>
</td>
<td className="px-2 py-2 text-apple-label-secondary">
{index + 1}
</td>
<td className="px-2 py-2">{renderImageCell(row, index)}</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.model, "model")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.skuCode, "skuCode")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.setCount, "setCount")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.supplierName, "supplierName")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.barcode, "barcode")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.specName, "specName")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.installMethod, "installMethod")}
</td>
<td className="px-2 py-2">
{renderShipmentInput(
row,
"sampleQuantity",
"请输入数量",
!canEditQuantity,
"sampleQuantity",
)}
</td>
<td className="px-2 py-2">
<SkuColumnResizableFrame
columnWidth={columnWidths.shipped}
minWidth={
FINISHED_SAMPLE_SHIPMENT_COLUMN_DEFAULT_WIDTHS.shipped
}
onColumnResize={(width) =>
onColumnResize("shipped", width)
}
>
<InlineSelect
ariaLabel="成品寄样是否寄出"
dataTestId={`finished-sample-shipment-shipped-${row.skuId}`}
value={row.shipped}
options={FINISHED_SAMPLE_SHIPMENT_SHIPPED_OPTIONS}
placeholder="请选择"
includeEmptyOption={false}
readOnly={!canEditDecision}
showIndicatorWhenReadOnly
onChange={(value) =>
onUpdateRow(row.skuId, "shipped", value)
}
buttonClassName="h-8 w-full min-w-0 rounded border border-apple-separator bg-apple-bg-secondary px-2 text-xs text-apple-label-primary outline-none disabled:cursor-not-allowed disabled:bg-apple-bg-primary disabled:text-apple-label-secondary"
/>
</SkuColumnResizableFrame>
</td>
<td className="px-2 py-2">
{renderShipmentInput(
row,
"trackingNumber",
"请输入快递单号",
!canEditTrackingNumber,
"trackingNumber",
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);
}

View File

@ -27,6 +27,7 @@ import {
InlineSelect,
InlineSuggestionInput,
} from "./components/InlineControls";
import { FinishedSampleShipmentWorkspace } from "./FinishedSampleShipmentWorkspace";
import {
FactoryLeadTimeField,
FactoryWarehouseField,
@ -604,7 +605,6 @@ import {
normalizeFinishedSampleShipmentRowsForPayload,
normalizeFinishedSampleShipmentShipped,
normalizeSampleMeasurementRowsForPayload,
type ProductFinishedSampleShipmentDisplayRow,
} from "./sampleWorkflowHelpers";
import {
buildFirstQualityRows,
@ -731,7 +731,6 @@ import {
FINISHED_SAMPLE_SHIPMENT_COLUMN_DEFAULT_WIDTHS,
FINISHED_SAMPLE_SHIPMENT_EXISTING_SAMPLE_TRACKING,
FINISHED_SAMPLE_SHIPMENT_NOT_REQUIRED_TRACKING,
FINISHED_SAMPLE_SHIPMENT_SHIPPED_OPTIONS,
INSTALL_METHOD_OPTIONS,
COMPONENT_SKU_CATEGORY_LABEL_BY_KEY,
COMPONENT_SKU_CATEGORY_OPTIONS,
@ -18403,236 +18402,25 @@ export default function NonIntegratedDetailClient({
}
function renderFinishedSampleShipmentWorkspace() {
const renderReadOnlyCell = (
value: string,
column: FinishedSampleShipmentColumnWidthKey,
) => (
<SkuRowField
value={value || "-"}
readOnly
mode="singleLineResizable"
columnWidth={finishedSampleShipmentColumnWidths[column]}
onColumnResize={(width) =>
handleFinishedSampleShipmentColumnResize(column, width)
}
onChange={() => undefined}
/>
);
const renderImageCell = (
row: ProductFinishedSampleShipmentDisplayRow,
index: number,
) => {
if (!row.image) {
return (
<div className="flex h-12 w-12 items-center justify-center rounded border border-dashed border-apple-separator bg-apple-bg-secondary text-[11px] text-apple-label-tertiary">
无图
</div>
);
}
return (
<button
type="button"
className="block h-12 w-12 overflow-hidden rounded border border-apple-separator bg-apple-bg-secondary"
onClick={() =>
setSkuImagePreview({
kind: "image",
src: row.image,
alt: row.model || `成品寄样图片${index + 1}`,
})
}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={row.image}
alt={row.model || `成品寄样图片${index + 1}`}
className="h-full w-full object-cover"
/>
</button>
);
};
const renderShipmentInput = (
row: ProductFinishedSampleShipmentDisplayRow,
key: Extract<
FinishedSampleShipmentEditableKey,
"sampleQuantity" | "trackingNumber"
>,
placeholder: string,
readOnly: boolean,
column: Extract<
FinishedSampleShipmentColumnWidthKey,
"sampleQuantity" | "trackingNumber"
>,
) => (
<SkuRowField
value={row[key]}
placeholder={placeholder}
readOnly={readOnly}
mode="singleLineResizable"
tone="manual"
columnWidth={finishedSampleShipmentColumnWidths[column]}
onColumnResize={(width) =>
handleFinishedSampleShipmentColumnResize(column, width)
}
testId={`finished-sample-shipment-${key}-${row.skuId}`}
onChange={(value) =>
updateFinishedSampleShipmentRow(row.skuId, key, value)
}
/>
);
return (
<div
data-testid="finished-sample-shipment-workspace"
className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-apple-xl border border-apple-separator bg-apple-bg-primary"
>
<div className="border-b border-apple-separator px-3 py-2">
<div className="flex flex-wrap items-center justify-between gap-2">
{renderInheritedSkuBatchTabs()}
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productFinishedSampleShipmentRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再进行成品寄样。
</div>
) : displayedFinishedSampleShipmentRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[1760px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
{[
"寄样",
"序号",
"图片",
"型号",
"货号",
"套件量",
"供应商名称",
"条码",
"条码规格名称",
"安装方式",
"寄样数量",
"是否寄出",
"快递单号",
].map((label) => (
<th
key={label}
className="whitespace-nowrap border-b border-apple-separator px-2 py-2 text-left font-apple-medium"
>
{label}
</th>
))}
</tr>
</thead>
<tbody>
{displayedFinishedSampleShipmentRows.map((row, index) => (
<tr
key={row.skuId}
className="border-b border-apple-separator last:border-b-0"
>
<td className="px-2 py-2">
<span
className={`inline-flex whitespace-nowrap rounded-full px-2 py-0.5 text-[11px] ${
isFinishedSampleShipmentRowComplete(row)
? "bg-[#dff7e8] text-[#137a38]"
: "bg-[#fff3df] text-[#9b6511]"
}`}
>
{isFinishedSampleShipmentRowComplete(row)
? "已完成"
: "请寄样"}
</span>
</td>
<td className="px-2 py-2 text-apple-label-secondary">
{index + 1}
</td>
<td className="px-2 py-2">{renderImageCell(row, index)}</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.model, "model")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.skuCode, "skuCode")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.setCount, "setCount")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.supplierName, "supplierName")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.barcode, "barcode")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.specName, "specName")}
</td>
<td className="px-2 py-2">
{renderReadOnlyCell(row.installMethod, "installMethod")}
</td>
<td className="px-2 py-2">
{renderShipmentInput(
row,
"sampleQuantity",
"请输入数量",
!canEditFinishedSampleShipmentQuantity,
"sampleQuantity",
)}
</td>
<td className="px-2 py-2">
<SkuColumnResizableFrame
columnWidth={finishedSampleShipmentColumnWidths.shipped}
minWidth={
FINISHED_SAMPLE_SHIPMENT_COLUMN_DEFAULT_WIDTHS.shipped
}
onColumnResize={(width) =>
handleFinishedSampleShipmentColumnResize(
"shipped",
width,
)
}
>
<InlineSelect
ariaLabel="成品寄样是否寄出"
dataTestId={`finished-sample-shipment-shipped-${row.skuId}`}
value={row.shipped}
options={FINISHED_SAMPLE_SHIPMENT_SHIPPED_OPTIONS}
placeholder="请选择"
includeEmptyOption={false}
readOnly={!canEditFinishedSampleShipmentDecision}
showIndicatorWhenReadOnly
onChange={(value) =>
updateFinishedSampleShipmentRow(
row.skuId,
"shipped",
value,
)
}
buttonClassName="h-8 w-full min-w-0 rounded border border-apple-separator bg-apple-bg-secondary px-2 text-xs text-apple-label-primary outline-none disabled:cursor-not-allowed disabled:bg-apple-bg-primary disabled:text-apple-label-secondary"
/>
</SkuColumnResizableFrame>
</td>
<td className="px-2 py-2">
{renderShipmentInput(
row,
"trackingNumber",
"请输入快递单号",
!canEditFinishedSampleShipmentTrackingNumber,
"trackingNumber",
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
<FinishedSampleShipmentWorkspace
allRows={productFinishedSampleShipmentRows}
displayedRows={displayedFinishedSampleShipmentRows}
inheritedSkuBatchTabs={renderInheritedSkuBatchTabs()}
columnWidths={finishedSampleShipmentColumnWidths}
canEditQuantity={canEditFinishedSampleShipmentQuantity}
canEditDecision={canEditFinishedSampleShipmentDecision}
canEditTrackingNumber={canEditFinishedSampleShipmentTrackingNumber}
onColumnResize={handleFinishedSampleShipmentColumnResize}
onUpdateRow={updateFinishedSampleShipmentRow}
onPreviewImage={(src, alt) =>
setSkuImagePreview({
kind: "image",
src,
alt,
})
}
/>
);
}