'use client' import { useState, useEffect, useCallback } from 'react' import { useRouter, useParams } from 'next/navigation' import Link from 'next/link' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' import { Input } from '@/components/ui/Input' import { Modal } from '@/components/ui/Modal' import { SuccessTag, PendingTag, ErrorTag } from '@/components/ui/Tag' import { useToast } from '@/components/ui/Toast' import { ArrowLeft, Calendar, Users, FileText, Video, Clock, CheckCircle, XCircle, ChevronRight, Plus, Settings, Search, Building2, MoreHorizontal, Trash2, Check, Pencil, 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 { ProjectResponse } from '@/types/project' import type { TaskResponse } from '@/types/task' import type { AgencyDetail } from '@/types/organization' // ==================== Mock 数据 ==================== const mockProject: ProjectResponse = { id: 'proj-001', name: 'XX品牌618推广', brand_id: 'br-001', brand_name: 'XX护肤品牌', description: '618大促活动营销内容审核项目', status: 'active', deadline: '2026-06-18', agencies: [ { id: 'AG789012', name: '星耀传媒' }, { id: 'AG456789', name: '创意无限' }, ], task_count: 20, created_at: '2026-02-01T00:00:00Z', updated_at: '2026-02-06T00:00:00Z', } const mockTasks: TaskResponse[] = [ { id: 'task-001', name: '夏日护肤推广', sequence: 1, stage: 'video_brand_review', project: { id: 'proj-001', name: 'XX品牌618推广' }, agency: { id: 'AG789012', name: '星耀传媒' }, creator: { id: 'cr-001', name: '小美护肤' }, appeal_count: 0, is_appeal: false, created_at: '2026-02-06T14:30:00Z', updated_at: '2026-02-06T14:30:00Z', }, { id: 'task-002', name: '新品口红试色', sequence: 2, stage: 'completed', project: { id: 'proj-001', name: 'XX品牌618推广' }, agency: { id: 'AG789012', name: '星耀传媒' }, creator: { id: 'cr-002', name: '美妆Lisa' }, appeal_count: 0, is_appeal: false, created_at: '2026-02-06T12:15:00Z', updated_at: '2026-02-06T12:15:00Z', }, { id: 'task-003', name: '健身器材推荐', sequence: 3, stage: 'rejected', project: { id: 'proj-001', name: 'XX品牌618推广' }, agency: { id: 'AG456789', name: '创意无限' }, creator: { id: 'cr-003', name: '健身王' }, appeal_count: 0, is_appeal: false, created_at: '2026-02-06T10:00:00Z', updated_at: '2026-02-06T10:00:00Z', }, ] const mockManagedAgencies: AgencyDetail[] = [ { id: 'AG789012', name: '星耀传媒', force_pass_enabled: true, contact_name: '张经理' }, { id: 'AG456789', name: '创意无限', force_pass_enabled: false, contact_name: '李总' }, { id: 'AG123456', name: '美妆达人MCN', force_pass_enabled: false }, { id: 'AG111111', name: '蓝海科技', force_pass_enabled: true }, { id: 'AG222222', name: '云创网络', force_pass_enabled: false }, ] // ==================== 组件 ==================== function StatCard({ title, value, icon: Icon, color }: { title: string; value: number | string; icon: React.ElementType; color: string }) { return (

{title}

{value}

) } function TaskStatusTag({ stage }: { stage: string }) { if (stage === 'completed') return 已通过 if (stage === 'rejected') return 已驳回 if (stage.includes('review')) return 审核中 return 进行中 } function DetailSkeleton() { return (
{[1, 2, 3, 4].map(i =>
)}
) } export default function ProjectDetailPage() { const router = useRouter() const params = useParams() const toast = useToast() const projectId = params.id as string const { subscribe } = useSSE() const [project, setProject] = useState(null) const [recentTasks, setRecentTasks] = useState([]) const [managedAgencies, setManagedAgencies] = useState([]) const [loading, setLoading] = useState(true) // UI states const [showAddModal, setShowAddModal] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [selectedAgencies, setSelectedAgencies] = useState([]) const [activeAgencyMenu, setActiveAgencyMenu] = useState(null) const [showDeleteModal, setShowDeleteModal] = useState(false) const [agencyToDelete, setAgencyToDelete] = useState<{ id: string; name: string } | null>(null) const [showDeadlineModal, setShowDeadlineModal] = useState(false) const [newDeadline, setNewDeadline] = useState('') const [submitting, setSubmitting] = useState(false) const loadData = useCallback(async () => { if (USE_MOCK) { setProject(mockProject) setRecentTasks(mockTasks) setManagedAgencies(mockManagedAgencies) setLoading(false) return } try { const [projectData, tasksData, agenciesData] = await Promise.all([ api.getProject(projectId), api.listTasks(1, 10), api.listBrandAgencies(), ]) setProject(projectData) setRecentTasks(tasksData.items.filter(t => t.project.id === projectId).slice(0, 5)) setManagedAgencies(agenciesData.items) } catch (err) { console.error('Failed to load project:', err) toast.error('加载项目详情失败') } finally { setLoading(false) } }, [projectId, toast]) useEffect(() => { loadData() }, [loadData]) useEffect(() => { const unsub = subscribe('task_updated', () => loadData()) return unsub }, [subscribe, loadData]) if (loading || !project) return const availableAgencies = managedAgencies.filter( agency => !project.agencies.some(a => a.id === agency.id) ) const filteredAgencies = availableAgencies.filter(agency => searchQuery === '' || agency.name.toLowerCase().includes(searchQuery.toLowerCase()) || agency.id.toLowerCase().includes(searchQuery.toLowerCase()) ) const toggleSelectAgency = (agencyId: string) => { setSelectedAgencies(prev => prev.includes(agencyId) ? prev.filter(id => id !== agencyId) : [...prev, agencyId] ) } const handleAddAgencies = async () => { setSubmitting(true) try { if (!USE_MOCK) { await api.assignAgencies(projectId, selectedAgencies) } const newAgencies = managedAgencies .filter(a => selectedAgencies.includes(a.id)) .map(a => ({ id: a.id, name: a.name })) setProject({ ...project, agencies: [...project.agencies, ...newAgencies] }) toast.success('代理商已添加') } catch (err) { console.error('Failed to add agencies:', err) toast.error('添加失败') } finally { setSubmitting(false) setShowAddModal(false) setSelectedAgencies([]) setSearchQuery('') } } const handleRemoveAgency = async () => { if (!agencyToDelete) return setSubmitting(true) try { if (!USE_MOCK) { await api.removeAgencyFromProject(projectId, agencyToDelete.id) } setProject({ ...project, agencies: project.agencies.filter(a => a.id !== agencyToDelete.id) }) toast.success('代理商已移除') } catch (err) { console.error('Failed to remove agency:', err) toast.error('移除失败') } finally { setSubmitting(false) setShowDeleteModal(false) setAgencyToDelete(null) } } const handleSaveDeadline = async () => { if (!newDeadline) return setSubmitting(true) try { if (!USE_MOCK) { await api.updateProject(projectId, { deadline: newDeadline }) } setProject({ ...project, deadline: newDeadline }) toast.success('截止日期已更新') } catch (err) { console.error('Failed to update deadline:', err) toast.error('更新失败') } finally { setSubmitting(false) setShowDeadlineModal(false) } } return (
{/* 顶部导航 */}

{project.name}

{project.description && (

{project.description}

)}
{project.status === 'active' ? '进行中' : project.status === 'completed' ? '已完成' : '已归档'}
{/* 项目信息 */}
截止日期: {project.deadline ? new Date(project.deadline).toLocaleDateString('zh-CN') : '未设置'} 创建时间: {new Date(project.created_at).toLocaleDateString('zh-CN')}
{/* Brief和规则配置 */}

Brief和规则配置

配置项目Brief、审核规则、AI检测项等

{/* 统计卡片 */}
{/* 最近任务 */} 最近提交 {recentTasks.length > 0 ? (
{recentTasks.map((task) => ( ))}
任务 达人 代理商 状态 操作
{task.name} {task.creator.name} {task.agency.name}
) : (
暂无任务
)}
{/* 代理商列表 */} 参与代理商 ({project.agencies.length}) {project.agencies.map((agency) => (

{agency.name}

{agency.id}

{activeAgencyMenu === agency.id && (
)}
))}
{/* 添加代理商弹窗 */} { setShowAddModal(false); setSearchQuery(''); setSelectedAgencies([]) }} title="添加代理商" size="lg" >
setSearchQuery(e.target.value)} placeholder="搜索代理商名称或ID..." className="pl-10" />
{filteredAgencies.length > 0 ? ( filteredAgencies.map((agency) => { const isSelected = selectedAgencies.includes(agency.id) return ( ) }) ) : (
{availableAgencies.length === 0 ? ( <>

所有代理商都已添加到此项目

) : ( <>

未找到匹配的代理商

)}
)}
{selectedAgencies.length > 0 && (
已选择 {selectedAgencies.length} 个代理商
)}
{/* 删除确认弹窗 */} { setShowDeleteModal(false); setAgencyToDelete(null) }} title="移除代理商">

确定要将 {agencyToDelete?.name} 从此项目中移除吗?

移除后,该代理商下的达人将无法继续参与此项目的任务。

{/* 编辑截止日期弹窗 */} setShowDeadlineModal(false)} title="修改截止日期">
setNewDeadline(e.target.value)} className="w-full pl-12 pr-4 py-3 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" />
) }