Your Name 964797d2e9 feat: 完善品牌方和代理商前端功能
品牌方功能:
- 项目看板: 添加截止日期编辑功能
- 项目详情: 添加代理商管理、截止日期编辑、最近任务显示代理商
- 项目创建: 代理商选择支持搜索(名称/ID/公司名)
- 代理商管理: 通过ID邀请、添加备注/分配项目/移除操作
- Brief配置: 新增项目级Brief和规则配置页面
- 系统设置: 完善账户安全(密码/2FA/邮箱/手机/设备管理)、数据导出、退出登录

代理商功能:
- 个人中心: 新增代理商ID展示、公司信息(企业验证)、个人信息编辑
- 账户设置: 密码修改、手机/邮箱绑定、两步验证
- 通知设置: 分类型和渠道的通知开关
- 审核历史: 搜索筛选和统计展示
- 帮助反馈: FAQ分类搜索和客服联系

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 17:40:11 +08:00

292 lines
11 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import {
ArrowLeft,
History,
CheckCircle,
XCircle,
Search,
Filter,
FileText,
Video,
User,
Calendar,
Download
} from 'lucide-react'
// 审核历史记录类型
interface ReviewHistoryItem {
id: string
taskId: string
taskTitle: string
creatorName: string
contentType: 'script' | 'video'
result: 'approved' | 'rejected'
reason?: string
reviewedAt: string
projectName: string
}
// 模拟审核历史数据
const mockHistoryData: ReviewHistoryItem[] = [
{
id: 'h-001',
taskId: 'task-101',
taskTitle: '夏日护肤推广脚本',
creatorName: '小美护肤',
contentType: 'script',
result: 'approved',
reviewedAt: '2026-02-06 14:30',
projectName: 'XX品牌618推广',
},
{
id: 'h-002',
taskId: 'task-102',
taskTitle: '新品口红试色视频',
creatorName: '美妆Lisa',
contentType: 'video',
result: 'rejected',
reason: '背景音乐版权问题',
reviewedAt: '2026-02-06 11:20',
projectName: 'YY口红新品发布',
},
{
id: 'h-003',
taskId: 'task-103',
taskTitle: '健身器材推荐脚本',
creatorName: '健身教练王',
contentType: 'script',
result: 'approved',
reviewedAt: '2026-02-05 16:45',
projectName: 'ZZ运动品牌推广',
},
{
id: 'h-004',
taskId: 'task-104',
taskTitle: '美妆新品测评视频',
creatorName: '达人小红',
contentType: 'video',
result: 'rejected',
reason: '品牌调性不符',
reviewedAt: '2026-02-05 10:15',
projectName: 'XX品牌618推广',
},
{
id: 'h-005',
taskId: 'task-105',
taskTitle: '数码产品开箱脚本',
creatorName: '科技小哥',
contentType: 'script',
result: 'approved',
reviewedAt: '2026-02-04 15:30',
projectName: 'AA数码新品上市',
},
]
export default function AgencyReviewHistoryPage() {
const router = useRouter()
const [searchQuery, setSearchQuery] = useState('')
const [filterResult, setFilterResult] = useState<'all' | 'approved' | 'rejected'>('all')
const [filterType, setFilterType] = useState<'all' | 'script' | 'video'>('all')
// 筛选数据
const filteredHistory = mockHistoryData.filter(item => {
const matchesSearch = searchQuery === '' ||
item.taskTitle.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.creatorName.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.projectName.toLowerCase().includes(searchQuery.toLowerCase())
const matchesResult = filterResult === 'all' || item.result === filterResult
const matchesType = filterType === 'all' || item.contentType === filterType
return matchesSearch && matchesResult && matchesType
})
// 统计
const approvedCount = mockHistoryData.filter(i => i.result === 'approved').length
const rejectedCount = mockHistoryData.filter(i => i.result === 'rejected').length
return (
<div className="space-y-6">
{/* 顶部导航 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => router.back()}
className="p-2 rounded-lg hover:bg-bg-elevated transition-colors"
>
<ArrowLeft size={20} className="text-text-secondary" />
</button>
<div>
<h1 className="text-2xl font-bold text-text-primary"></h1>
<p className="text-sm text-text-secondary mt-0.5"></p>
</div>
</div>
<Button variant="secondary">
<Download size={16} />
</Button>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-3 gap-4">
<div className="p-4 rounded-xl bg-bg-card card-shadow">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-accent-indigo/15 flex items-center justify-center">
<History size={20} className="text-accent-indigo" />
</div>
<div>
<p className="text-2xl font-bold text-text-primary">{mockHistoryData.length}</p>
<p className="text-sm text-text-secondary"></p>
</div>
</div>
</div>
<div className="p-4 rounded-xl bg-bg-card card-shadow">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-accent-green/15 flex items-center justify-center">
<CheckCircle size={20} className="text-accent-green" />
</div>
<div>
<p className="text-2xl font-bold text-accent-green">{approvedCount}</p>
<p className="text-sm text-text-secondary"></p>
</div>
</div>
</div>
<div className="p-4 rounded-xl bg-bg-card card-shadow">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-accent-coral/15 flex items-center justify-center">
<XCircle size={20} className="text-accent-coral" />
</div>
<div>
<p className="text-2xl font-bold text-accent-coral">{rejectedCount}</p>
<p className="text-sm text-text-secondary"></p>
</div>
</div>
</div>
</div>
{/* 搜索和筛选 */}
<div className="flex flex-wrap items-center gap-4">
<div className="relative flex-1 min-w-[240px] max-w-md">
<Search size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary" />
<input
type="text"
placeholder="搜索任务、达人或项目..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 border border-border-subtle rounded-xl bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-text-tertiary">:</span>
<div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg">
{[
{ value: 'all', label: '全部' },
{ value: 'approved', label: '通过' },
{ value: 'rejected', label: '驳回' },
].map((tab) => (
<button
key={tab.value}
type="button"
onClick={() => setFilterResult(tab.value as typeof filterResult)}
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
filterResult === tab.value ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-text-tertiary">:</span>
<div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg">
{[
{ value: 'all', label: '全部' },
{ value: 'script', label: '脚本' },
{ value: 'video', label: '视频' },
].map((tab) => (
<button
key={tab.value}
type="button"
onClick={() => setFilterType(tab.value as typeof filterType)}
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
filterType === tab.value ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
</div>
{/* 历史列表 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<History size={18} className="text-accent-indigo" />
<span className="ml-auto text-sm font-normal text-text-secondary">
{filteredHistory.length}
</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{filteredHistory.length > 0 ? (
filteredHistory.map((item) => (
<div
key={item.id}
className="p-4 rounded-xl bg-bg-elevated hover:bg-bg-elevated/80 transition-colors"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className={`px-2 py-0.5 rounded text-xs font-medium ${
item.result === 'approved'
? 'bg-accent-green/15 text-accent-green'
: 'bg-accent-coral/15 text-accent-coral'
}`}>
{item.result === 'approved' ? '已通过' : '已驳回'}
</span>
<span className="flex items-center gap-1 text-xs text-text-tertiary">
{item.contentType === 'script' ? <FileText size={12} /> : <Video size={12} />}
{item.contentType === 'script' ? '脚本' : '视频'}
</span>
</div>
<h3 className="font-medium text-text-primary mb-1">{item.taskTitle}</h3>
<div className="flex items-center gap-4 text-sm text-text-secondary">
<span className="flex items-center gap-1">
<User size={14} />
{item.creatorName}
</span>
<span>{item.projectName}</span>
</div>
{item.reason && (
<p className="mt-2 text-sm text-accent-coral">: {item.reason}</p>
)}
</div>
<div className="text-right">
<div className="flex items-center gap-1 text-sm text-text-tertiary">
<Calendar size={14} />
{item.reviewedAt}
</div>
</div>
</div>
</div>
))
) : (
<div className="text-center py-12 text-text-tertiary">
<History size={48} className="mx-auto mb-4 opacity-50" />
<p></p>
</div>
)}
</CardContent>
</Card>
</div>
)
}