Your Name 0ef7650c09 feat: 审核体系全面改造 — 多维度评分 + 卖点优先级 + AI 语义匹配 + 品牌方 AI 状态通知
后端:
- 审核结果拆分为 4 个独立维度 (法规合规/平台规则/品牌安全/Brief匹配度)
- 卖点优先级从 required:bool 改为三级 (core/recommended/reference)
- AI 语义匹配卖点覆盖 + AI 整体 Brief 匹配度分析
- BriefMatchDetail 评分详情 (覆盖率+亮点+问题点)
- min_selling_points 代理商可配置最少卖点数 + Alembic 迁移
- AI 语境复核过滤误报
- Brief AI 解析 + 规则 AI 解析
- AI 未配置/异常时通知品牌方
- 种子数据更新 (新格式审核结果+brief_match_detail)

前端:
- 三端审核页面展示四维度评分卡片
- 卖点编辑改为三级优先级选择器
- BriefMatchDetail 展示 (覆盖率进度条+亮点+问题)
- min_selling_points 配置 UI
- AI 配置页未配置时静默处理
- 文件预览/下载/签名 URL 优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:11:54 +08:00

660 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useEffect, useCallback } from 'react'
import { useRouter, useParams } from 'next/navigation'
import { useToast } from '@/components/ui/Toast'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { Modal, ConfirmModal } from '@/components/ui/Modal'
import { SuccessTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
import { ReviewSteps, getAgencyReviewSteps } from '@/components/ui/ReviewSteps'
import {
ArrowLeft,
FileText,
CheckCircle,
XCircle,
AlertTriangle,
User,
Clock,
Eye,
Shield,
Download,
MessageSquareWarning,
Loader2
} from 'lucide-react'
import { FilePreview, FileInfoCard, FilePreviewModal, type FileInfo } from '@/components/ui/FilePreview'
import { api } from '@/lib/api'
import { USE_MOCK } from '@/contexts/AuthContext'
import { getPlatformInfo } from '@/lib/platforms'
import type { TaskResponse } from '@/types/task'
// 模拟脚本任务数据
const mockScriptTask = {
id: 'script-001',
title: '夏日护肤推广脚本',
creatorName: '小美护肤',
projectName: 'XX品牌618推广',
submittedAt: '2026-02-06 14:30',
aiScore: 88,
status: 'agent_reviewing',
// 文件信息
file: {
id: 'file-001',
fileName: '夏日护肤推广_脚本v2.docx',
fileSize: '245 KB',
fileType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
fileUrl: '/demo/scripts/script-001.docx', // 实际开发时替换为真实URL
uploadedAt: '2026-02-06 14:30',
} as FileInfo,
// 申诉信息
isAppeal: false,
appealReason: '',
// 脚本内容AI解析后的结构化内容用于展示
scriptContent: {
opening: '大家好!今天给大家分享一款超级好用的夏日护肤神器~',
productIntro: '这款XX品牌的防晒霜SPF50+PA++++,真的是夏天出门必备!质地轻薄不油腻,涂上去清清爽爽的。',
demo: '我先在手背上试一下,大家看,延展性特别好,轻轻一抹就推开了,完全不会搓泥。',
closing: '夏天防晒真的很重要姐妹们赶紧冲链接在小黄车1号链接',
},
aiAnalysis: {
violations: [
{ id: 'v1', type: '违禁词', content: '神器', suggestion: '建议替换为"好物"或"必备品"', severity: 'medium' },
],
complianceChecks: [
{ item: '品牌名称正确', passed: true },
{ item: 'SPF标注准确', passed: true },
{ item: '无绝对化用语', passed: false, note: '"超级好用"建议修改' },
{ item: '引导语规范', passed: true },
],
sellingPoints: [
{ point: 'SPF50+ PA++++', covered: true },
{ point: '轻薄质地', covered: true },
{ point: '不油腻', covered: true },
{ point: '延展性好', covered: true },
],
},
}
// 从 TaskResponse 映射到页面视图模型
function mapTaskToViewModel(task: TaskResponse) {
return {
id: task.id,
title: task.name,
creatorName: task.creator?.name || '未知达人',
projectName: task.project?.name || '未知项目',
brandName: task.project?.brand_name || '',
platform: task.project?.platform || '',
submittedAt: task.script_uploaded_at || task.created_at,
aiScore: task.script_ai_score ?? 0,
status: task.stage,
file: {
id: `file-${task.id}`,
fileName: task.script_file_name || '未知文件',
fileSize: '',
fileType: 'application/octet-stream',
fileUrl: task.script_file_url || '',
uploadedAt: task.script_uploaded_at || task.created_at,
} as FileInfo,
isAppeal: task.is_appeal,
appealReason: task.appeal_reason || '',
scriptContent: {
opening: '',
productIntro: '',
demo: '',
closing: '',
},
aiAnalysis: {
violations: (task.script_ai_result?.violations || []).map((v, idx) => ({
id: `v${idx + 1}`,
type: v.type,
content: v.content,
suggestion: v.suggestion,
severity: v.severity,
dimension: v.dimension,
})),
complianceChecks: (task.script_ai_result?.soft_warnings || []).map((w: any) => {
const codeLabels: Record<string, string> = {
missing_selling_points: '卖点缺失',
tone_mismatch: '语气不符',
length_warning: '时长提示',
style_warning: '风格提示',
sensitive_topic: '敏感话题',
audience_mismatch: '受众偏差',
}
const rawLabel = w.type || w.code || '提示'
return {
item: codeLabels[rawLabel] || rawLabel,
passed: false,
note: w.content || w.message || '',
}
}),
dimensions: task.script_ai_result?.dimensions,
sellingPointMatches: task.script_ai_result?.selling_point_matches || [],
sellingPoints: [] as Array<{ point: string; covered: boolean }>,
},
aiSummary: task.script_ai_result?.summary || '',
}
}
type ScriptTaskViewModel = ReturnType<typeof mapTaskToViewModel>
function ReviewProgressBar({ taskStatus }: { taskStatus: string }) {
const steps = getAgencyReviewSteps(taskStatus)
const currentStep = steps.find(s => s.status === 'current')
return (
<Card className="mb-6">
<CardContent className="py-4">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-text-primary"></span>
<span className="text-sm text-accent-indigo font-medium">
{currentStep?.label || '代理商审核'}
</span>
</div>
<ReviewSteps steps={steps} />
</CardContent>
</Card>
)
}
function LoadingSkeleton() {
return (
<div className="space-y-4 animate-pulse">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-bg-elevated rounded-full" />
<div className="flex-1 space-y-2">
<div className="h-6 bg-bg-elevated rounded w-1/3" />
<div className="h-4 bg-bg-elevated rounded w-1/4" />
</div>
</div>
<div className="h-16 bg-bg-elevated rounded-xl" />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-4">
<div className="h-32 bg-bg-elevated rounded-xl" />
<div className="h-64 bg-bg-elevated rounded-xl" />
</div>
<div className="space-y-4">
<div className="h-20 bg-bg-elevated rounded-xl" />
<div className="h-40 bg-bg-elevated rounded-xl" />
<div className="h-40 bg-bg-elevated rounded-xl" />
</div>
</div>
</div>
)
}
export default function AgencyScriptReviewPage() {
const router = useRouter()
const toast = useToast()
const params = useParams()
const taskId = params.id as string
const [loading, setLoading] = useState(!USE_MOCK)
const [submitting, setSubmitting] = useState(false)
const [showApproveModal, setShowApproveModal] = useState(false)
const [showRejectModal, setShowRejectModal] = useState(false)
const [showForcePassModal, setShowForcePassModal] = useState(false)
const [rejectReason, setRejectReason] = useState('')
const [forcePassReason, setForcePassReason] = useState('')
const [viewMode, setViewMode] = useState<'file' | 'parsed'>('file') // 'file' 显示原文件, 'parsed' 显示解析内容
const [showFilePreview, setShowFilePreview] = useState(false)
const [task, setTask] = useState<ScriptTaskViewModel>(mockScriptTask as unknown as ScriptTaskViewModel)
const loadTask = useCallback(async () => {
if (USE_MOCK) return
setLoading(true)
try {
const data = await api.getTask(taskId)
setTask(mapTaskToViewModel(data))
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '加载任务详情失败'
toast.error(message)
} finally {
setLoading(false)
}
}, [taskId, toast])
useEffect(() => {
loadTask()
}, [loadTask])
const handleApprove = async () => {
if (USE_MOCK) {
setShowApproveModal(false)
toast.success('已提交品牌方终审')
router.push('/agency/review')
return
}
setSubmitting(true)
try {
await api.reviewScript(taskId, { action: 'pass' })
setShowApproveModal(false)
toast.success('已提交品牌方终审')
router.push('/agency/review')
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '操作失败'
toast.error(message)
} finally {
setSubmitting(false)
}
}
const handleReject = async () => {
if (!rejectReason.trim()) {
toast.error('请填写驳回原因')
return
}
if (USE_MOCK) {
setShowRejectModal(false)
toast.success('已驳回')
router.push('/agency/review')
return
}
setSubmitting(true)
try {
await api.reviewScript(taskId, { action: 'reject', comment: rejectReason })
setShowRejectModal(false)
toast.success('已驳回')
router.push('/agency/review')
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '操作失败'
toast.error(message)
} finally {
setSubmitting(false)
}
}
const handleForcePass = async () => {
if (!forcePassReason.trim()) {
toast.error('请填写强制通过原因')
return
}
if (USE_MOCK) {
setShowForcePassModal(false)
toast.success('已强制通过并提交品牌方终审')
router.push('/agency/review')
return
}
setSubmitting(true)
try {
await api.reviewScript(taskId, { action: 'force_pass', comment: forcePassReason })
setShowForcePassModal(false)
toast.success('已强制通过并提交品牌方终审')
router.push('/agency/review')
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '操作失败'
toast.error(message)
} finally {
setSubmitting(false)
}
}
if (loading) {
return <LoadingSkeleton />
}
return (
<div className="space-y-4">
{/* 顶部导航 */}
<div className="flex items-center gap-4">
<button type="button" onClick={() => router.back()} className="p-2 hover:bg-bg-elevated rounded-full">
<ArrowLeft size={20} className="text-text-primary" />
</button>
<div className="flex-1">
<div className="flex items-center gap-2">
<h1 className="text-xl font-bold text-text-primary">{task.title}</h1>
{task.isAppeal && (
<span className="flex items-center gap-1 px-2 py-1 text-xs bg-accent-amber/20 text-accent-amber rounded-full font-medium">
<MessageSquareWarning size={12} />
</span>
)}
</div>
<div className="flex items-center gap-4 mt-1 text-sm text-text-secondary">
<span>{task.creatorName}</span>
{task.brandName && <span>{task.brandName}</span>}
{task.platform && <span>{getPlatformInfo(task.platform)?.name || task.platform}</span>}
<span className="flex items-center gap-1">
<Clock size={14} />
{task.submittedAt}
</span>
</div>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg">
<button
type="button"
onClick={() => setViewMode('file')}
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
viewMode === 'file' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
</button>
<button
type="button"
onClick={() => setViewMode('parsed')}
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
viewMode === 'parsed' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
AI解析
</button>
</div>
</div>
</div>
{/* 申诉理由 */}
{task.isAppeal && task.appealReason && (
<div className="p-4 rounded-xl bg-accent-amber/10 border border-accent-amber/30">
<p className="text-sm text-accent-amber font-medium mb-1 flex items-center gap-1">
<MessageSquareWarning size={14} />
</p>
<p className="text-text-secondary">{task.appealReason}</p>
</div>
)}
{/* 审核流程进度条 */}
<ReviewProgressBar taskStatus={task.status} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 左侧:脚本内容 */}
<div className="lg:col-span-2 space-y-4">
{/* 文件信息卡片 */}
<FileInfoCard
file={task.file}
onPreview={() => setShowFilePreview(true)}
/>
{viewMode === 'file' ? (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText size={18} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent>
<FilePreview file={task.file} />
</CardContent>
</Card>
) : (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText size={18} className="text-accent-indigo" />
AI
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{task.aiSummary ? (
<div className="p-4 bg-bg-elevated rounded-lg">
<div className="text-xs text-accent-indigo font-medium mb-2">AI </div>
<p className="text-text-primary">{task.aiSummary}</p>
</div>
) : (
<p className="text-sm text-text-tertiary text-center py-4"> AI </p>
)}
{task.aiAnalysis.violations.length > 0 && (
<div className="p-4 bg-bg-elevated rounded-lg">
<div className="text-xs text-accent-coral font-medium mb-2"> ({task.aiAnalysis.violations.length})</div>
<div className="space-y-2">
{task.aiAnalysis.violations.map((v) => (
<div key={v.id} className="text-sm">
<span className="text-accent-coral font-medium">[{v.type}]</span>
<span className="text-text-primary ml-1">{v.content}</span>
<p className="text-xs text-accent-indigo mt-0.5">{v.suggestion}</p>
</div>
))}
</div>
</div>
)}
{task.aiAnalysis.sellingPointMatches.length > 0 && (
<div className="p-4 bg-bg-elevated rounded-lg">
<div className="text-xs text-accent-green font-medium mb-2"></div>
<div className="space-y-1">
{task.aiAnalysis.sellingPointMatches.map((sp: { content: string; priority: string; matched: boolean; evidence?: string }, idx: number) => (
<div key={idx} className="flex items-center gap-2 text-sm">
{sp.matched ? <CheckCircle size={14} className="text-accent-green flex-shrink-0" /> : <XCircle size={14} className="text-accent-coral flex-shrink-0" />}
<span className="text-text-primary">{sp.content}</span>
<span className={`text-xs px-1.5 py-0.5 rounded ${
sp.priority === 'core' ? 'bg-accent-coral/20 text-accent-coral' :
sp.priority === 'recommended' ? 'bg-accent-amber/20 text-accent-amber' :
'bg-bg-page text-text-tertiary'
}`}>{sp.priority === 'core' ? '核心' : sp.priority === 'recommended' ? '推荐' : '参考'}</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
)}
</div>
{/* 右侧AI 分析面板 */}
<div className="space-y-4">
{/* AI 评分 */}
<Card>
<CardContent className="py-4">
<div className="flex items-center justify-between">
<span className="text-sm text-text-secondary">AI </span>
<span className={`text-3xl font-bold ${task.aiScore >= 85 ? 'text-accent-green' : task.aiScore >= 70 ? 'text-yellow-400' : 'text-accent-coral'}`}>
{task.aiScore}
</span>
</div>
</CardContent>
</Card>
{/* 维度评分 */}
{task.aiAnalysis.dimensions && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<Shield size={16} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{(['legal', 'platform', 'brand_safety', 'brief_match'] as const).map(key => {
const dim = (task.aiAnalysis.dimensions as unknown as Record<string, { score: number; passed: boolean; issue_count: number }>)?.[key]
if (!dim) return null
const label = { legal: '法规合规', platform: '平台规则', brand_safety: '品牌安全', brief_match: 'Brief 匹配' }[key]
return (
<div key={key} className={`flex items-center justify-between p-2 rounded-lg ${dim.passed ? 'bg-accent-green/5' : 'bg-accent-coral/5'}`}>
<span className="text-sm text-text-primary">{label}</span>
<div className="flex items-center gap-2">
<span className={`text-sm font-bold ${dim.passed ? 'text-accent-green' : 'text-accent-coral'}`}>{dim.score}</span>
{dim.passed ? <CheckCircle size={14} className="text-accent-green" /> : <XCircle size={14} className="text-accent-coral" />}
</div>
</div>
)
})}
</CardContent>
</Card>
)}
{/* 违规检测 */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<AlertTriangle size={16} className="text-orange-500" />
({task.aiAnalysis.violations.length})
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{task.aiAnalysis.violations.map((v) => (
<div key={v.id} className="p-3 bg-orange-500/10 rounded-lg border border-orange-500/30">
<div className="flex items-center gap-2 mb-1">
<WarningTag>{v.type}</WarningTag>
{v.dimension && <span className="text-xs text-text-tertiary">{{ legal: '法规合规', platform: '平台规则', brand_safety: '品牌安全', brief_match: 'Brief 匹配' }[v.dimension as string]}</span>}
</div>
<p className="text-sm text-text-primary">{v.content}</p>
<p className="text-xs text-accent-indigo mt-1">{v.suggestion}</p>
</div>
))}
{task.aiAnalysis.violations.length === 0 && (
<p className="text-sm text-text-tertiary text-center py-4"></p>
)}
</CardContent>
</Card>
{/* 舆情提示 */}
{task.aiAnalysis.complianceChecks.length > 0 && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<AlertTriangle size={16} className="text-orange-500" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{task.aiAnalysis.complianceChecks.map((check, idx) => (
<div key={idx} className="p-3 bg-orange-500/10 rounded-lg border border-orange-500/30">
<div className="flex items-center gap-2 mb-1">
<WarningTag>{check.item}</WarningTag>
</div>
{check.note && (
<p className="text-sm text-text-secondary">{check.note}</p>
)}
<p className="text-xs text-text-tertiary mt-1"></p>
</div>
))}
</CardContent>
</Card>
)}
{/* 卖点匹配 */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<CheckCircle size={16} className="text-accent-green" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{task.aiAnalysis.sellingPointMatches && task.aiAnalysis.sellingPointMatches.length > 0 ? (
task.aiAnalysis.sellingPointMatches.map((sp: { content: string; priority: string; matched: boolean; evidence?: string }, idx: number) => (
<div key={idx} className="flex items-start gap-2 p-2 rounded-lg bg-bg-elevated">
{sp.matched ? <CheckCircle size={16} className="text-accent-green flex-shrink-0 mt-0.5" /> : <XCircle size={16} className="text-accent-coral flex-shrink-0 mt-0.5" />}
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="text-sm text-text-primary">{sp.content}</span>
<span className={`px-1.5 py-0.5 text-xs rounded ${
sp.priority === 'core' ? 'bg-accent-coral/20 text-accent-coral' :
sp.priority === 'recommended' ? 'bg-accent-amber/20 text-accent-amber' :
'bg-bg-page text-text-tertiary'
}`}>{sp.priority === 'core' ? '核心' : sp.priority === 'recommended' ? '推荐' : '参考'}</span>
</div>
{sp.evidence && <p className="text-xs text-text-tertiary mt-0.5">{sp.evidence}</p>}
</div>
</div>
))
) : (
<p className="text-sm text-text-tertiary text-center py-4"></p>
)}
</CardContent>
</Card>
</div>
</div>
{/* 底部决策栏 */}
<Card className="sticky bottom-4 shadow-lg">
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div className="text-sm text-text-secondary">
{task.brandName && <span>{task.brandName} · </span>}
{task.projectName}
{task.platform && <span> · {getPlatformInfo(task.platform)?.name || task.platform}</span>}
</div>
<div className="flex gap-3">
<Button variant="danger" onClick={() => setShowRejectModal(true)} disabled={submitting}>
{submitting ? <Loader2 size={16} className="animate-spin mr-1" /> : null}
</Button>
<Button variant="secondary" onClick={() => setShowForcePassModal(true)} disabled={submitting}>
{submitting ? <Loader2 size={16} className="animate-spin mr-1" /> : null}
</Button>
<Button variant="success" onClick={() => setShowApproveModal(true)} disabled={submitting}>
{submitting ? <Loader2 size={16} className="animate-spin mr-1" /> : null}
</Button>
</div>
</div>
</CardContent>
</Card>
{/* 通过确认弹窗 */}
<ConfirmModal
isOpen={showApproveModal}
onClose={() => setShowApproveModal(false)}
onConfirm={handleApprove}
title="确认通过"
message="确定要通过此脚本的审核吗?通过后将提交给品牌方进行终审。"
confirmText="确认通过"
/>
{/* 驳回弹窗 */}
<Modal isOpen={showRejectModal} onClose={() => setShowRejectModal(false)} title="驳回审核">
<div className="space-y-4">
<p className="text-text-secondary text-sm"></p>
<div>
<label className="block text-sm font-medium text-text-primary mb-1"></label>
<textarea
className="w-full h-24 p-3 border border-border-subtle rounded-lg resize-none bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
placeholder="请详细说明驳回原因..."
value={rejectReason}
onChange={(e) => setRejectReason(e.target.value)}
/>
</div>
<div className="flex gap-3 justify-end">
<Button variant="ghost" onClick={() => setShowRejectModal(false)} disabled={submitting}></Button>
<Button variant="danger" onClick={handleReject} disabled={submitting}>
{submitting ? <Loader2 size={16} className="animate-spin mr-1" /> : null}
</Button>
</div>
</div>
</Modal>
{/* 强制通过弹窗 */}
<Modal isOpen={showForcePassModal} onClose={() => setShowForcePassModal(false)} title="强制通过">
<div className="space-y-4">
<div className="p-3 bg-yellow-500/10 rounded-lg border border-yellow-500/30">
<p className="text-sm text-yellow-400">
<AlertTriangle size={14} className="inline mr-1" />
</p>
</div>
<div>
<label className="block text-sm font-medium text-text-primary mb-1"></label>
<textarea
className="w-full h-24 p-3 border border-border-subtle rounded-lg resize-none bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
placeholder="例如:内容符合品牌调性,建议通过"
value={forcePassReason}
onChange={(e) => setForcePassReason(e.target.value)}
/>
</div>
<div className="flex gap-3 justify-end">
<Button variant="ghost" onClick={() => setShowForcePassModal(false)} disabled={submitting}></Button>
<Button onClick={handleForcePass} disabled={submitting}>
{submitting ? <Loader2 size={16} className="animate-spin mr-1" /> : null}
</Button>
</div>
</div>
</Modal>
{/* 文件预览弹窗 */}
<FilePreviewModal
file={task.file}
isOpen={showFilePreview}
onClose={() => setShowFilePreview(false)}
/>
</div>
)
}