refactor(intake): extract review issue image manager
This commit is contained in:
parent
df580ae8bd
commit
b02bb244ec
@ -18,7 +18,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { ArrowLeft, Ban, ChevronDown, Plus, X } from "lucide-react";
|
||||
import { ArrowLeft, Ban, ChevronDown, Plus } from "lucide-react";
|
||||
import CameraScannerModal from "@/components/mobile/CameraScannerModal";
|
||||
import { ModelPreviewPanel } from "@/components/platform/ModelPreviewPanel";
|
||||
import Button from "@/components/ui/Button";
|
||||
@ -57,6 +57,7 @@ import { ProductDataWorkspace } from "./ProductDataWorkspace";
|
||||
import { QualityInspectionWorkspace } from "./QualityInspectionWorkspace";
|
||||
import { QualityStandardWorkspace } from "./QualityStandardWorkspace";
|
||||
import { ReviewIssueImageCell } from "./ReviewIssueImageCell";
|
||||
import { ReviewIssueImageManagerDialog } from "./ReviewIssueImageManagerDialog";
|
||||
import { SampleMeasurementWorkspace } from "./SampleMeasurementWorkspace";
|
||||
import {
|
||||
BomBoardMaterialField,
|
||||
@ -19145,142 +19146,25 @@ export default function NonIntegratedDetailClient({
|
||||
</div>
|
||||
|
||||
{reviewImageManager && managedReviewIssue ? (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/35 px-4 py-6">
|
||||
<div
|
||||
data-testid={`review-sheet-${reviewImageManager.target}-image-manager-paste-zone-${managedReviewIssue.id}`}
|
||||
tabIndex={canEditReviewSheet ? 0 : undefined}
|
||||
autoFocus={canEditReviewSheet}
|
||||
aria-label={`${managedReviewImageLabel}管理粘贴区`}
|
||||
title={
|
||||
canEditReviewSheet
|
||||
? `Ctrl+V 继续粘贴${managedReviewImageLabel}`
|
||||
: undefined
|
||||
}
|
||||
onPaste={(event) =>
|
||||
void handleReviewIssueImagePaste(
|
||||
managedReviewIssue.id,
|
||||
reviewImageManager.target,
|
||||
event,
|
||||
)
|
||||
}
|
||||
className="flex max-h-[86vh] w-full max-w-[720px] flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary shadow-apple-lg outline-none focus-visible:ring-2 focus-visible:ring-apple-blue/35"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-apple-separator px-4 py-3">
|
||||
<div>
|
||||
<p className="text-sm font-apple-medium text-apple-label-primary">
|
||||
管理{managedReviewImageLabel}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-apple-label-secondary">
|
||||
共 {managedReviewImages.length} 张,标记为导出主图的图片会优先显示
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setReviewImageManager(null)}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full border border-apple-separator bg-apple-bg-secondary/40 text-apple-label-secondary transition hover:bg-apple-bg-secondary"
|
||||
aria-label="关闭图片管理"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-4">
|
||||
{managedReviewImages.length > 0 ? (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{managedReviewImages.map((image) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="rounded-apple-lg border border-apple-separator bg-apple-bg-secondary/20 p-2"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setWorkspaceImagePreview({
|
||||
kind: "standalone",
|
||||
src: image.url,
|
||||
alt: image.fileName || managedReviewImageLabel,
|
||||
})
|
||||
}
|
||||
className="flex aspect-square w-full items-center justify-center overflow-hidden rounded-apple-lg border border-apple-separator bg-white"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={image.url}
|
||||
alt={image.fileName || managedReviewImageLabel}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</button>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<p className="min-w-0 truncate text-xs text-apple-label-primary">
|
||||
{image.fileName || managedReviewImageLabel}
|
||||
</p>
|
||||
{image.isPrimary ? (
|
||||
<span className="shrink-0 rounded-full bg-zinc-950 px-2 py-0.5 text-[11px] text-white">
|
||||
导出主图
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{canEditReviewSheet ? (
|
||||
<div className="mt-2 flex flex-wrap items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleSetReviewIssuePrimaryImage(
|
||||
managedReviewIssue.id,
|
||||
reviewImageManager.target,
|
||||
image.id,
|
||||
)
|
||||
}
|
||||
className="inline-flex h-8 items-center justify-center rounded-apple-lg border border-apple-separator bg-apple-bg-primary px-2.5 text-xs text-apple-label-primary transition hover:bg-apple-bg-secondary/50"
|
||||
disabled={image.isPrimary}
|
||||
>
|
||||
设为导出主图
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDeleteReviewIssueImage(
|
||||
managedReviewIssue.id,
|
||||
reviewImageManager.target,
|
||||
image.id,
|
||||
)
|
||||
}
|
||||
className="inline-flex h-8 items-center justify-center rounded-apple-lg border border-apple-separator bg-apple-bg-primary px-2.5 text-xs text-apple-label-primary transition hover:bg-apple-bg-secondary/50"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-[180px] items-center justify-center rounded-apple-lg border border-dashed border-apple-separator bg-apple-bg-secondary/20 text-xs text-apple-label-secondary">
|
||||
暂无图片
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canEditReviewSheet ? (
|
||||
<div className="flex items-center justify-end border-t border-apple-separator px-4 py-3">
|
||||
<label className="inline-flex h-9 cursor-pointer items-center justify-center rounded-apple-lg border border-zinc-950 bg-zinc-950 px-3 text-xs font-apple-medium text-white">
|
||||
继续上传
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={(event) =>
|
||||
void handleReviewIssueImageChange(
|
||||
managedReviewIssue.id,
|
||||
reviewImageManager.target,
|
||||
event,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<ReviewIssueImageManagerDialog
|
||||
issue={managedReviewIssue}
|
||||
target={reviewImageManager.target}
|
||||
label={managedReviewImageLabel}
|
||||
images={managedReviewImages}
|
||||
canEdit={canEditReviewSheet}
|
||||
onClose={() => setReviewImageManager(null)}
|
||||
onPaste={handleReviewIssueImagePaste}
|
||||
onPreviewImage={(src, alt) =>
|
||||
setWorkspaceImagePreview({
|
||||
kind: "standalone",
|
||||
src,
|
||||
alt,
|
||||
})
|
||||
}
|
||||
onSetPrimaryImage={handleSetReviewIssuePrimaryImage}
|
||||
onDeleteImage={handleDeleteReviewIssueImage}
|
||||
onImageChange={handleReviewIssueImageChange}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
"use client";
|
||||
|
||||
import type { ChangeEvent, ClipboardEvent } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import type {
|
||||
NonIntegratedReviewImage,
|
||||
NonIntegratedReviewIssue,
|
||||
} from "./nonIntegratedStorage";
|
||||
|
||||
type ReviewIssueImageTarget = "issue" | "reference";
|
||||
|
||||
type ReviewIssueImageManagerDialogProps = {
|
||||
issue: NonIntegratedReviewIssue;
|
||||
target: ReviewIssueImageTarget;
|
||||
label: string;
|
||||
images: NonIntegratedReviewImage[];
|
||||
canEdit: boolean;
|
||||
onClose: () => void;
|
||||
onPaste: (
|
||||
issueId: string,
|
||||
target: ReviewIssueImageTarget,
|
||||
event: ClipboardEvent<HTMLDivElement>,
|
||||
) => void | Promise<void>;
|
||||
onPreviewImage: (src: string, alt: string) => void;
|
||||
onSetPrimaryImage: (
|
||||
issueId: string,
|
||||
target: ReviewIssueImageTarget,
|
||||
imageId: string,
|
||||
) => void;
|
||||
onDeleteImage: (
|
||||
issueId: string,
|
||||
target: ReviewIssueImageTarget,
|
||||
imageId: string,
|
||||
) => void;
|
||||
onImageChange: (
|
||||
issueId: string,
|
||||
target: ReviewIssueImageTarget,
|
||||
event: ChangeEvent<HTMLInputElement>,
|
||||
) => void | Promise<void>;
|
||||
};
|
||||
|
||||
export function ReviewIssueImageManagerDialog({
|
||||
issue,
|
||||
target,
|
||||
label,
|
||||
images,
|
||||
canEdit,
|
||||
onClose,
|
||||
onPaste,
|
||||
onPreviewImage,
|
||||
onSetPrimaryImage,
|
||||
onDeleteImage,
|
||||
onImageChange,
|
||||
}: ReviewIssueImageManagerDialogProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/35 px-4 py-6">
|
||||
<div
|
||||
data-testid={`review-sheet-${target}-image-manager-paste-zone-${issue.id}`}
|
||||
tabIndex={canEdit ? 0 : undefined}
|
||||
autoFocus={canEdit}
|
||||
aria-label={`${label}管理粘贴区`}
|
||||
title={canEdit ? `Ctrl+V 继续粘贴${label}` : undefined}
|
||||
onPaste={(event) => void onPaste(issue.id, target, event)}
|
||||
className="flex max-h-[86vh] w-full max-w-[720px] flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary shadow-apple-lg outline-none focus-visible:ring-2 focus-visible:ring-apple-blue/35"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-apple-separator px-4 py-3">
|
||||
<div>
|
||||
<p className="text-sm font-apple-medium text-apple-label-primary">
|
||||
管理{label}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-apple-label-secondary">
|
||||
共 {images.length} 张,标记为导出主图的图片会优先显示
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full border border-apple-separator bg-apple-bg-secondary/40 text-apple-label-secondary transition hover:bg-apple-bg-secondary"
|
||||
aria-label="关闭图片管理"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-4">
|
||||
{images.length > 0 ? (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{images.map((image) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="rounded-apple-lg border border-apple-separator bg-apple-bg-secondary/20 p-2"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onPreviewImage(image.url, image.fileName || label)}
|
||||
className="flex aspect-square w-full items-center justify-center overflow-hidden rounded-apple-lg border border-apple-separator bg-white"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={image.url}
|
||||
alt={image.fileName || label}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</button>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<p className="min-w-0 truncate text-xs text-apple-label-primary">
|
||||
{image.fileName || label}
|
||||
</p>
|
||||
{image.isPrimary ? (
|
||||
<span className="shrink-0 rounded-full bg-zinc-950 px-2 py-0.5 text-[11px] text-white">
|
||||
导出主图
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{canEdit ? (
|
||||
<div className="mt-2 flex flex-wrap items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSetPrimaryImage(issue.id, target, image.id)}
|
||||
className="inline-flex h-8 items-center justify-center rounded-apple-lg border border-apple-separator bg-apple-bg-primary px-2.5 text-xs text-apple-label-primary transition hover:bg-apple-bg-secondary/50"
|
||||
disabled={image.isPrimary}
|
||||
>
|
||||
设为导出主图
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeleteImage(issue.id, target, image.id)}
|
||||
className="inline-flex h-8 items-center justify-center rounded-apple-lg border border-apple-separator bg-apple-bg-primary px-2.5 text-xs text-apple-label-primary transition hover:bg-apple-bg-secondary/50"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-[180px] items-center justify-center rounded-apple-lg border border-dashed border-apple-separator bg-apple-bg-secondary/20 text-xs text-apple-label-secondary">
|
||||
暂无图片
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canEdit ? (
|
||||
<div className="flex items-center justify-end border-t border-apple-separator px-4 py-3">
|
||||
<label className="inline-flex h-9 cursor-pointer items-center justify-center rounded-apple-lg border border-zinc-950 bg-zinc-950 px-3 text-xs font-apple-medium text-white">
|
||||
继续上传
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={(event) => void onImageChange(issue.id, target, event)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user