'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 { 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 } from '@/types/database'; import { toast } from 'sonner'; import { ArrowLeft, Plus, Target, Calendar, Users, AlertTriangle, Clock, Pencil, Trash2, Loader2 } from 'lucide-react'; 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 } = useUser(); const router = useRouter(); const [project, setProject] = useState(null); const [objectives, setObjectives] = useState([]); const [krs, setKRs] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isDeleting, setIsDeleting] = useState(false); const [isEditing, setIsEditing] = useState(false); const [isObjectiveDialogOpen, setIsObjectiveDialogOpen] = useState(false); const [editingObjective, setEditingObjective] = useState(null); const fetchData = useCallback(async () => { try { const [projectRes, objectivesRes, krsRes] = await Promise.all([ fetch(`/api/projects/${projectId}`), fetch(`/api/objectives?project_id=${projectId}`), fetch(`/api/krs?project_id=${projectId}`), ]); if (projectRes.ok) { const projectData = await projectRes.json(); setProject(projectData); } if (objectivesRes.ok) { const objectivesData = await objectivesRes.json(); setObjectives(objectivesData); } if (krsRes.ok) { const krsData = await krsRes.json(); setKRs(krsData); } } catch (error) { console.error('Failed to fetch project data:', error); } finally { setIsLoading(false); } }, [projectId]); useEffect(() => { fetchData(); }, [fetchData]); const canEdit = currentUser?.role === 'owner' || currentUser?.id === project?.pm_id; 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); const formData = new FormData(e.currentTarget); try { const res = await fetch(`/api/projects/${projectId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: formData.get('name'), description: formData.get('description'), }), }); if (!res.ok) throw new Error('更新失败'); toast.success('项目已更新'); fetchData(); } catch (error) { toast.error('更新失败'); } finally { setIsEditing(false); } }; const handleDeleteObjective = async (objectiveId: string) => { if (!confirm('确定要删除此目标吗?关联的 KR 也会被解除关联。')) return; try { const res = await fetch(`/api/objectives/${objectiveId}`, { method: 'DELETE' }); if (!res.ok) throw new Error('删除失败'); toast.success('目标已删除'); fetchData(); } catch (error) { toast.error('删除失败'); } }; 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 activeKRs = krs.filter(isKRActive); const completedKRs = activeKRs.filter(kr => calculateProgress(kr) >= 1); if (isLoading) { return (
{[1, 2, 3].map(i => (
))}
); } if (!project) { return (

项目不存在

); } return (
返回项目列表

{project.name}

{project.description && (

{project.description}

)}
{canEdit && (
{currentUser?.role === 'owner' && ( <> 编辑项目