'use client'; import { use, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { ArrowLeft, Plus, Edit, Trash2, Loader2, Clock, Calendar, User, ListTodo, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { useEpic, useDeleteEpic } from '@/lib/hooks/use-epics'; import { useStories, useDeleteStory } from '@/lib/hooks/use-stories'; import { useProject } from '@/lib/hooks/use-projects'; import { EpicForm } from '@/components/epics/epic-form'; import { StoryForm } from '@/components/projects/story-form'; import { formatDistanceToNow } from 'date-fns'; import { toast } from 'sonner'; import type { Story, WorkItemStatus, WorkItemPriority } from '@/types/project'; interface EpicDetailPageProps { params: Promise<{ id: string }>; } export default function EpicDetailPage({ params }: EpicDetailPageProps) { const { id: epicId } = use(params); const router = useRouter(); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isCreateStoryDialogOpen, setIsCreateStoryDialogOpen] = useState(false); const [editingStory, setEditingStory] = useState(null); const [deletingStoryId, setDeletingStoryId] = useState(null); const [isDeleteEpicDialogOpen, setIsDeleteEpicDialogOpen] = useState(false); const { data: epic, isLoading: epicLoading, error: epicError } = useEpic(epicId); const { data: stories, isLoading: storiesLoading } = useStories(epicId); const { data: project, isLoading: projectLoading } = useProject(epic?.projectId || ''); const deleteEpic = useDeleteEpic(); const deleteStory = useDeleteStory(); const handleDeleteEpic = async () => { try { await deleteEpic.mutateAsync(epicId); toast.success('Epic deleted successfully'); router.push(`/projects/${epic?.projectId}/epics`); } catch (error) { const message = error instanceof Error ? error.message : 'Failed to delete epic'; toast.error(message); } }; const handleDeleteStory = async () => { if (!deletingStoryId) return; try { await deleteStory.mutateAsync(deletingStoryId); setDeletingStoryId(null); } catch (error) { const message = error instanceof Error ? error.message : 'Failed to delete story'; toast.error(message); } }; const getStatusColor = (status: WorkItemStatus) => { switch (status) { case 'Backlog': return 'secondary'; case 'Todo': return 'outline'; case 'InProgress': return 'default'; case 'Done': return 'success' as any; default: return 'secondary'; } }; const getPriorityColor = (priority: WorkItemPriority) => { switch (priority) { case 'Low': return 'bg-blue-100 text-blue-700 hover:bg-blue-100'; case 'Medium': return 'bg-yellow-100 text-yellow-700 hover:bg-yellow-100'; case 'High': return 'bg-orange-100 text-orange-700 hover:bg-orange-100'; case 'Critical': return 'bg-red-100 text-red-700 hover:bg-red-100'; default: return 'secondary'; } }; if (epicLoading || projectLoading) { return (
); } if (epicError || !epic) { return (
Error Loading Epic {epicError instanceof Error ? epicError.message : 'Epic not found'}
); } return (
{/* Breadcrumb */}
Projects / {project && ( <> {project.name} / )} Epics / {epic.name}
{/* Header */}

{epic.name}

{epic.status} {epic.priority}
{/* Epic Details Card */} Epic Details {epic.description ? (

Description

{epic.description}

) : (

No description

)}
{epic.estimatedHours !== undefined && (

Time Estimate

Estimated: {epic.estimatedHours}h {epic.actualHours !== undefined && ( <> / Actual: {epic.actualHours}h )}

)} {epic.assigneeId && (

Assignee

{epic.assigneeId}

)}

Created

{formatDistanceToNow(new Date(epic.createdAt), { addSuffix: true })} {epic.createdBy && <> by {epic.createdBy}}

Last Updated

{formatDistanceToNow(new Date(epic.updatedAt), { addSuffix: true })}

{/* Stories Section */}

Stories

{storiesLoading ? (
{Array.from({ length: 4 }).map((_, i) => ( ))}
) : stories && stories.length > 0 ? (
{stories.map((story) => (
{story.title}
{story.status} {story.priority}
{story.description ? (

{story.description}

) : (

No description

)}
{story.estimatedHours && (
Estimated: {story.estimatedHours}h {story.actualHours && ( / Actual: {story.actualHours}h )}
)}
Created{' '} {formatDistanceToNow(new Date(story.createdAt), { addSuffix: true, })}
))}
) : ( No stories yet Get started by creating your first story to break down this epic )}
{/* Edit Epic Dialog */} Edit Epic Update the epic details setIsEditDialogOpen(false)} onCancel={() => setIsEditDialogOpen(false)} /> {/* Create Story Dialog */} Create New Story Add a new story under {epic.name} setIsCreateStoryDialogOpen(false)} onCancel={() => setIsCreateStoryDialogOpen(false)} /> {/* Edit Story Dialog */} setEditingStory(null)}> Edit Story Update the story details {editingStory && ( setEditingStory(null)} onCancel={() => setEditingStory(null)} /> )} {/* Delete Epic Confirmation Dialog */} Are you sure? This action cannot be undone. This will permanently delete the epic and all its associated stories and tasks. Cancel {deleteEpic.isPending ? ( <> Deleting... ) : ( 'Delete Epic' )} {/* Delete Story Confirmation Dialog */} setDeletingStoryId(null)} > Are you sure? This action cannot be undone. This will permanently delete the story and all its associated tasks. Cancel {deleteStory.isPending ? ( <> Deleting... ) : ( 'Delete Story' )}
); }