'use client' // LEGACY_READ_ONLY: 旧域名仅用于展示历史审核素材。 import Image from 'next/image' import { useEffect, useMemo, useState } from 'react' import type { WorkReviewProject, WorkReviewReview, WorkReviewRole, WorkReviewVersion, } from '@/types/work-review' import { WorkReviewImageGallery } from './WorkReviewImageGallery' import type { WorkReviewDisplayAnnotation } from './WorkReviewAnnotationMarker' import { WorkReviewDecisionBadge, WorkReviewStatusBadge } from './WorkReviewStatusBadge' import { canResubmitWorkReviewProject } from '@/lib/work-review-policy' function formatDateOnly(value: string) { const raw = String(value || '') const datePart = raw.match(/^\d{4}-\d{2}-\d{2}/)?.[0] return datePart || '-' } function formatDateTime(value?: string | null) { if (!value) return '-' const date = new Date(value) if (Number.isNaN(date.getTime())) return '-' return new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false, }).format(date) } function proxyImageUrl(value: string) { if (!/^https?:\/\//i.test(value)) return value try { const parsed = new URL(value) if (['s3.jiansun.vip', 's3.yydd.ai', 's3.1080p.hk'].includes(parsed.hostname.toLowerCase())) { return `/api/proxy-media?url=${encodeURIComponent(value)}` } } catch { // 使用原地址。 } return value } function getVersionReview(version: WorkReviewVersion, role: WorkReviewRole) { return version.reviews.find((review) => review.reviewerRole === role) || null } function getRequiredReviewRoles(project: WorkReviewProject): WorkReviewRole[] { return project.requiredReviewRoles?.length ? project.requiredReviewRoles : ['manager', 'final_reviewer'] } function getVersionAnnotations( version: WorkReviewVersion, requiredRoles: WorkReviewRole[], ): WorkReviewDisplayAnnotation[][] { const imageCount = version.imageGroups?.length || version.imageUrls?.length || 0 const result = Array.from({ length: imageCount }, () => [] as WorkReviewDisplayAnnotation[]) for (const review of version.reviews || []) { if (!requiredRoles.includes(review.reviewerRole)) continue for (const imageComment of review.imageComments || []) { if (!result[imageComment.imageIndex]) continue result[imageComment.imageIndex].push(...(imageComment.annotations || []).map((annotation, index) => ({ annotation, markerNumber: index + 1, reviewerName: review.reviewerName, reviewerRole: review.reviewerRole, }))) } } return result } function ReviewProgress({ project, version, }: { project: WorkReviewProject version: WorkReviewVersion }) { const requiredRoles = getRequiredReviewRoles(project) const allRoles: Array<{ role: WorkReviewRole; label: string; name: string }> = [ { role: 'manager', label: '评审人', name: project.manager.name }, { role: 'final_reviewer', label: '元泽', name: project.finalReviewer.name }, ] const roles = allRoles.filter((item) => requiredRoles.includes(item.role)) return (
{roles.map((item) => { const review = getVersionReview(version, item.role) return ( {item.label}{review ? '已评' : '待评'} ) })}
) } export function WorkReviewProjectCard({ project, expanded, canDelete, deleteBusy, onToggle, onDelete, onResubmit, onReview, }: { project: WorkReviewProject expanded: boolean canDelete: boolean deleteBusy?: boolean onToggle: () => void onDelete: () => void onResubmit: () => void onReview: () => void }) { const latestVersion = project.versions[0] const [selectedVersionId, setSelectedVersionId] = useState(latestVersion?.id || '') useEffect(() => { if (!project.versions.some((version) => version.id === selectedVersionId)) { setSelectedVersionId(project.versions[0]?.id || '') } }, [project.versions, selectedVersionId]) const selectedVersion = useMemo( () => project.versions.find((version) => version.id === selectedVersionId) || latestVersion, [latestVersion, project.versions, selectedVersionId], ) const cover = latestVersion?.imageGroups?.[0]?.previewUrl || latestVersion?.imageUrls?.[0] || '' const isLatestSelected = selectedVersion?.id === latestVersion?.id const canResubmitLatestVersion = canResubmitWorkReviewProject(project) const requiredReviewRoles = getRequiredReviewRoles(project) const singleReviewerTest = project.reviewMode === 'single_reviewer_test' const annotationsByImageIndex = useMemo( () => selectedVersion ? getVersionAnnotations(selectedVersion, requiredReviewRoles) : [], [requiredReviewRoles, selectedVersion], ) if (!latestVersion || !selectedVersion) return null return (
{canDelete && ( )}
{expanded && (

V{selectedVersion.version} 作品内容

{!isLatestSelected && 历史版本 · 只读}

提交于 {formatDateTime(selectedVersion.submittedAt)} {selectedVersion.completedAt ? ` · 评审完成于 ${formatDateTime(selectedVersion.completedAt)}` : ''}

{isLatestSelected && canResubmitLatestVersion && ( )}
{project.description && (
作品说明

{project.description}

)} {selectedVersion.submissionNote && (
本次提交说明

{selectedVersion.submissionNote}

)}
0 ? 'xl:grid-cols-[minmax(0,2fr)_minmax(240px,1fr)]' : ''}`}>

作品图片

{(selectedVersion.referenceImageGroups || []).length > 0 && (

参考图片

仅供对照
)}

{singleReviewerTest ? '评审意见' : '双评审意见'}

{requiredReviewRoles.includes('manager') && ( )} {requiredReviewRoles.includes('final_reviewer') && ( )}
)}
) } function ReviewPanel({ label, reviewerName, review, canReview, onReview, }: { label: string reviewerName: string review: WorkReviewReview | null canReview: boolean onReview: () => void }) { return (

{label}

{review?.reviewerName || reviewerName}
{review ? ( ) : ( 待评审 )}
{review ? (
{(review.imageComments || []).map((item) => (
图 {item.imageIndex + 1}{item.sourceName ? ` · ${item.sourceName}` : ''}

{item.comment}

))} {review.generalComment && (
总体意见

{review.generalComment}

)}
提交于 {formatDateTime(review.reviewedAt)}
) : (

{canReview ? '当前版本等待你填写逐图评审建议' : '等待评审人提交'}

{canReview && ( )}
)}
) }