'use client' import { useState, useEffect, useCallback } from 'react' import Link from 'next/link' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' import { AlertTriangle, Clock, CheckCircle, ChevronRight, FileVideo, MessageSquare, TrendingUp, Loader2 } from 'lucide-react' import { getPlatformInfo } from '@/lib/platforms' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' import { useSSE } from '@/contexts/SSEContext' import type { AgencyDashboard as AgencyDashboardType } from '@/types/dashboard' import type { TaskResponse } from '@/types/task' import type { ProjectResponse } from '@/types/project' // ==================== Mock 数据 ==================== const mockStats: AgencyDashboardType = { pending_review: { script: 8, video: 4 }, pending_appeal: 3, today_passed: { script: 18, video: 10 }, in_progress: { script: 25, video: 20 }, total_creators: 15, total_tasks: 80, } const mockPendingTasks: TaskResponse[] = [ { id: 'task-001', name: '夏日护肤推广', sequence: 1, stage: 'script_agency_review', project: { id: 'proj-001', name: 'XX品牌618推广', brand_name: 'XX品牌' }, agency: { id: 'ag-001', name: '优创代理' }, creator: { id: 'cr-001', name: '小美护肤' }, script_ai_score: 85, appeal_count: 0, is_appeal: false, created_at: '2026-02-04T14:30:00Z', updated_at: '2026-02-04T14:30:00Z', }, { id: 'task-002', name: '新品口红试色', sequence: 2, stage: 'video_agency_review', project: { id: 'proj-001', name: 'XX品牌618推广', brand_name: 'XX品牌' }, agency: { id: 'ag-001', name: '优创代理' }, creator: { id: 'cr-002', name: '美妆达人Lisa' }, video_ai_score: 72, appeal_count: 0, is_appeal: true, created_at: '2026-02-04T13:45:00Z', updated_at: '2026-02-04T13:45:00Z', }, { id: 'task-003', name: '健身器材开箱', sequence: 3, stage: 'script_agency_review', project: { id: 'proj-002', name: 'XX运动品牌', brand_name: 'XX运动' }, agency: { id: 'ag-001', name: '优创代理' }, creator: { id: 'cr-003', name: '健身教练王' }, script_ai_score: 68, appeal_count: 0, is_appeal: false, created_at: '2026-02-04T14:50:00Z', updated_at: '2026-02-04T14:50:00Z', }, ] const mockProjects: ProjectResponse[] = [ { id: 'proj-001', name: 'XX品牌618推广', brand_id: 'br-001', brand_name: 'XX品牌', status: 'active', deadline: '2026-06-18', agencies: [], task_count: 20, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-02-01T00:00:00Z', }, { id: 'proj-002', name: '新品口红系列', brand_id: 'br-001', brand_name: 'XX品牌', status: 'active', deadline: '2026-03-15', agencies: [], task_count: 12, created_at: '2026-01-15T00:00:00Z', updated_at: '2026-02-01T00:00:00Z', }, { id: 'proj-003', name: '护肤品秋季活动', brand_id: 'br-002', brand_name: 'YY品牌', status: 'active', deadline: '2026-09-01', agencies: [], task_count: 15, created_at: '2026-02-01T00:00:00Z', updated_at: '2026-02-05T00:00:00Z', }, ] // ==================== 组件 ==================== function UrgentLevelIcon({ level }: { level: string }) { if (level === 'high') return if (level === 'medium') return return } function getTaskUrgencyLevel(task: TaskResponse): string { const aiScore = task.stage.startsWith('script') ? task.script_ai_score : task.video_ai_score if (aiScore != null && aiScore < 60) return 'high' if (task.is_appeal) return 'medium' return 'low' } function getTaskUrgencyTitle(task: TaskResponse): string { return `${task.project.name} · ${task.name}` } function getPlatformLabel(platform?: string | null): string { const map: Record = { douyin: '抖音', xiaohongshu: '小红书', bilibili: 'B站', kuaishou: '快手' } return platform ? (map[platform] || platform) : '' } function getTaskTimeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime() const hours = Math.floor(diff / 3600000) if (hours < 1) return `${Math.floor(diff / 60000)}分钟前` if (hours < 24) return `${hours}小时前` return `${Math.floor(hours / 24)}天前` } function DashboardSkeleton() { return (
{[1, 2, 3, 4].map(i => (
))}
) } export default function AgencyDashboard() { const [stats, setStats] = useState(null) const [pendingTasks, setPendingTasks] = useState([]) const [projects, setProjects] = useState([]) const [loading, setLoading] = useState(true) const { subscribe } = useSSE() const loadData = useCallback(async () => { if (USE_MOCK) { setStats(mockStats) setPendingTasks(mockPendingTasks) setProjects(mockProjects) setLoading(false) return } try { const [dashboardData, tasksData, projectsData] = await Promise.all([ api.getAgencyDashboard(), api.listPendingReviews(1, 5), api.listProjects(1, 3), ]) setStats(dashboardData) // listPendingReviews returns TaskSummary, but we need TaskResponse for the table // Fall back to listTasks for the pending table const fullTasks = await api.listTasks(1, 5, 'script_agency_review') setPendingTasks(fullTasks.items) setProjects(projectsData.items) } catch (err) { console.error('Failed to load agency dashboard:', err) // fallback to empty setStats({ pending_review: { script: 0, video: 0 }, pending_appeal: 0, today_passed: { script: 0, video: 0 }, in_progress: { script: 0, video: 0 }, total_creators: 0, total_tasks: 0 }) setPendingTasks([]) setProjects([]) } finally { setLoading(false) } }, []) useEffect(() => { loadData() }, [loadData]) useEffect(() => { const unsub1 = subscribe('task_updated', () => loadData()) const unsub2 = subscribe('new_task', () => loadData()) const unsub3 = subscribe('review_completed', () => loadData()) return () => { unsub1(); unsub2(); unsub3() } }, [subscribe, loadData]) if (loading || !stats) return // Build urgent todos from pending tasks (top 3) const urgentTodos = pendingTasks.slice(0, 3).map(task => { const type = task.stage.includes('video') ? '视频' : '脚本' const platformLabel = getPlatformLabel(task.project.platform) const brandLabel = task.project.brand_name || '' const desc = [task.creator.name, brandLabel, platformLabel, type].filter(Boolean).join(' · ') return { id: task.id, title: getTaskUrgencyTitle(task), description: desc, time: getTaskTimeAgo(task.updated_at), level: getTaskUrgencyLevel(task), } }) return (
{/* 页面标题 */}

代理商工作台

更新时间:{new Date().toLocaleString('zh-CN')}
{/* 统计卡片 */}
待审核
{stats.pending_review.script + stats.pending_review.video}
脚本 {stats.pending_review.script} 视频 {stats.pending_review.video}
待仲裁
{stats.pending_appeal}
今日通过
{stats.today_passed.script + stats.today_passed.video}
脚本 {stats.today_passed.script} 视频 {stats.today_passed.video}
进行中
{stats.in_progress.script + stats.in_progress.video}
脚本 {stats.in_progress.script} 视频 {stats.in_progress.video}
{/* 紧急待办 */} 紧急待办 {urgentTodos.length > 0 ? urgentTodos.map((todo) => (
{todo.title}
{todo.description}
{todo.time}
)) : (
暂无紧急待办
)}
{/* 项目概览 */} 项目概览
{projects.length > 0 ? projects.map((project) => (
{project.name} {project.brand_name && ( ({project.brand_name}) )} {project.platform && ( {getPlatformLabel(project.platform)} )}
{project.task_count} 个任务
状态: {project.status === 'active' ? '进行中' : project.status === 'completed' ? '已完成' : '已归档'} {project.deadline && 截止: {new Date(project.deadline).toLocaleDateString('zh-CN')}}
)) : (
暂无项目
)}
{/* 待审核列表 */} 待审核任务
{pendingTasks.length > 0 ? pendingTasks.map((task) => { const isVideo = task.stage.includes('video') const aiScore = isVideo ? task.video_ai_score : task.script_ai_score return ( ) }) : ( )}
任务 类型 达人 品牌 平台 AI评分 提交时间 操作
{task.project.name} · {task.name}
{task.is_appeal && ( 申诉 )}
{isVideo ? '视频' : '脚本'} {task.creator.name} {task.project.brand_name || '-'} {getPlatformLabel(task.project.platform) || '-'} {aiScore != null ? ( = 80 ? 'text-accent-green' : aiScore >= 60 ? 'text-yellow-400' : 'text-accent-coral' }`}> {aiScore}分 ) : ( - )} {new Date(task.created_at).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
暂无待审核任务
) }