'use client'; import { useEffect, useState, useCallback } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { StatusBadge, StatusDot } from '@/components/status-badge'; import { ProgressRing } from '@/components/progress-ring'; import { useUser } from '@/components/user-context'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { Label } from '@/components/ui/label'; import { calculateProgress, getDaysRemaining, formatDateRange, isKRActive } from '@/lib/kr-utils'; import { REASON_CODES, PERIOD_TYPES } from '@/lib/constants'; import type { Project, User, Objective, KR, Task, TaskStep } from '@/types/database'; import { toast } from 'sonner'; import { ArrowLeft, Plus, Target, Calendar, Users, AlertTriangle, Clock, Pencil, Trash2, Loader2, CheckSquare, User as UserIcon, } from 'lucide-react'; import { TaskEditDialog } from '@/components/task-edit-dialog'; interface ProjectWithRelations extends Project { owner: User | null; pm: User | null; members: { user_id: string; users: User }[]; } interface KRWithRelations extends KR { owner: User | null; } export default function ProjectDetailPage({ projectId }: { projectId: string }) { // Force re-compile const { currentUser, users } = useUser(); const router = useRouter(); const [project, setProject] = useState(null); const [objectives, setObjectives] = useState([]); const [krs, setKRs] = useState([]); const [isLoading, setIsLoading] = useState(true); const [loadError, setLoadError] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [isEditing, setIsEditing] = useState(false); const [isProjectEditDialogOpen, setIsProjectEditDialogOpen] = useState(false); const [isObjectiveDialogOpen, setIsObjectiveDialogOpen] = useState(false); const [editingObjective, setEditingObjective] = useState(null); const [deleteObjectiveId, setDeleteObjectiveId] = useState(null); const [isDeleteObjectiveOpen, setIsDeleteObjectiveOpen] = useState(false); const [isDeletingObjective, setIsDeletingObjective] = useState(false); const [tasks, setTasks] = useState<(Task & { steps: TaskStep[] })[]>([]); const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false); const [editingTask, setEditingTask] = useState<(Task & { steps: TaskStep[] }) | null>(null); // Edit form state const [editForm, setEditForm] = useState({ name: '', description: '', project_type: '', owner_id: '', pm_id: '', member_ids: [] as string[], }); const initEditForm = useCallback(() => { if (project) { setEditForm({ name: project.name, description: project.description || '', project_type: project.project_type || '通用', owner_id: project.owner_id || '', pm_id: project.pm_id || '', member_ids: project.members?.map(m => m.user_id) || [], }); } }, [project]); useEffect(() => { if (isProjectEditDialogOpen) { initEditForm(); } }, [isProjectEditDialogOpen, initEditForm]); const fetchData = useCallback(async () => { try { const [projectRes, objectivesRes, krsRes, tasksRes] = await Promise.all([ fetch(`/api/projects/${projectId}`), fetch(`/api/objectives?project_id=${projectId}`), fetch(`/api/krs?project_id=${projectId}`), fetch(`/api/tasks?project_id=${projectId}`), ]); if (projectRes.status === 403) { setLoadError('你没有权限查看此项目'); setProject(null); return; } if (projectRes.status === 404) { setLoadError('项目不存在'); setProject(null); return; } if (projectRes.ok) { const projectData = await projectRes.json(); setProject(projectData); setLoadError(null); } if (objectivesRes.ok) { const objectivesData = await objectivesRes.json(); setObjectives(objectivesData); } if (krsRes.ok) { const krsData = await krsRes.json(); setKRs(krsData); } if (tasksRes.ok) { const tasksData = await tasksRes.json(); setTasks(tasksData); } } catch (error) { console.error('Failed to fetch project data:', error); setLoadError('加载失败'); } finally { setIsLoading(false); } }, [projectId]); useEffect(() => { fetchData(); }, [fetchData]); const canEdit = currentUser?.role === 'owner' || currentUser?.id === project?.pm_id; const isMember = project?.members?.some(m => m.user_id === currentUser?.id); const canAddKR = canEdit || isMember; const handleDeleteProject = async () => { setIsDeleting(true); try { const res = await fetch(`/api/projects/${projectId}`, { method: 'DELETE' }); if (!res.ok) throw new Error('删除失败'); toast.success('项目已删除'); router.push('/projects'); } catch (error) { toast.error('删除失败'); } finally { setIsDeleting(false); } }; const handleEditProject = async (e: React.FormEvent) => { e.preventDefault(); setIsEditing(true); try { const res = await fetch(`/api/projects/${projectId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editForm), }); if (!res.ok) throw new Error('更新失败'); toast.success('项目已更新'); setIsProjectEditDialogOpen(false); fetchData(); } catch (error) { toast.error('更新失败'); } finally { setIsEditing(false); } }; const requestDeleteObjective = (objectiveId: string) => { setDeleteObjectiveId(objectiveId); setIsDeleteObjectiveOpen(true); }; const handleDeleteObjective = async () => { if (!deleteObjectiveId) return; setIsDeletingObjective(true); try { const res = await fetch(`/api/objectives/${deleteObjectiveId}`, { method: 'DELETE' }); if (!res.ok) throw new Error('删除失败'); toast.success('目标已删除'); fetchData(); } catch (error) { toast.error('删除失败'); } finally { setIsDeletingObjective(false); setIsDeleteObjectiveOpen(false); setDeleteObjectiveId(null); } }; const handleEditObjective = async (e: React.FormEvent) => { e.preventDefault(); if (!editingObjective) return; setIsEditing(true); const formData = new FormData(e.currentTarget); try { const res = await fetch(`/api/objectives/${editingObjective.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: formData.get('title'), description: formData.get('description'), period_start: formData.get('period_start'), period_end: formData.get('period_end'), }), }); if (!res.ok) throw new Error('更新失败'); toast.success('目标已更新'); setIsObjectiveDialogOpen(false); setEditingObjective(null); fetchData(); } catch (error) { toast.error('更新失败'); } finally { setIsEditing(false); } }; const handleTaskStatusChange = async (taskId: string, newStatus: string) => { try { const res = await fetch(`/api/tasks/${taskId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }), }); if (!res.ok) throw new Error('更新失败'); setTasks(tasks.map(t => t.id === taskId ? { ...t, status: newStatus as any } : t)); toast.success('计划状态已更新'); } catch (error) { toast.error('更新失败'); } }; const activeKRs = krs.filter(isKRActive); const completedKRs = activeKRs.filter(kr => calculateProgress(kr) >= 1); if (isLoading) { return (
{[1, 2, 3].map(i => (
))}
); } if (loadError) { return (
{loadError}
); } if (!project) { return (

项目不存在

); } return (
返回项目列表

{project.name}

{project.description && (

{project.description}

)}
{canAddKR && (
{canEdit && ( <> {currentUser?.role === 'owner' && ( <> 编辑项目
setEditForm(prev => ({ ...prev, name: e.target.value }))} required />
setEditForm(prev => ({ ...prev, project_type: e.target.value }))} placeholder="如:研发、市场、运营" />
{users.map(user => (
{ setEditForm(prev => { if (checked) { return { ...prev, member_ids: [...prev.member_ids, user.id] }; } else { return { ...prev, member_ids: prev.member_ids.filter(id => id !== user.id) }; } }); }} />
))}