"use client"; import { use, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ArrowLeft, Edit, Trash2, Loader2, Clock, Calendar, User, Layers, CheckCircle2, Tag, Target, } 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useStory, useUpdateStory, useDeleteStory, useChangeStoryStatus, } from "@/lib/hooks/use-stories"; import { useEpic } from "@/lib/hooks/use-epics"; import { useProject } from "@/lib/hooks/use-projects"; import { StoryForm } from "@/components/projects/story-form"; import { TaskList } from "@/components/tasks/task-list"; import { formatDistanceToNow } from "date-fns"; import { toast } from "sonner"; import type { WorkItemStatus, WorkItemPriority } from "@/types/project"; interface StoryDetailPageProps { params: Promise<{ id: string }>; } export default function StoryDetailPage({ params }: StoryDetailPageProps) { const { id: storyId } = use(params); const router = useRouter(); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const { data: story, isLoading: storyLoading, error: storyError } = useStory(storyId); const { data: epic, isLoading: epicLoading } = useEpic(story?.epicId || ""); const { data: project, isLoading: projectLoading } = useProject(story?.projectId || ""); const updateStory = useUpdateStory(); const deleteStory = useDeleteStory(); const changeStatus = useChangeStoryStatus(); const handleDeleteStory = async () => { try { await deleteStory.mutateAsync(storyId); toast.success("Story deleted successfully"); // Navigate back to epic detail page router.push(`/epics/${story?.epicId}`); } catch (error) { const message = error instanceof Error ? error.message : "Failed to delete story"; toast.error(message); } }; const handleStatusChange = async (status: WorkItemStatus) => { if (!story) return; try { await changeStatus.mutateAsync({ id: storyId, status }); } catch (error) { const message = error instanceof Error ? error.message : "Failed to update status"; toast.error(message); } }; const handlePriorityChange = async (priority: WorkItemPriority) => { if (!story) return; try { await updateStory.mutateAsync({ id: storyId, data: { priority }, }); } catch (error) { const message = error instanceof Error ? error.message : "Failed to update priority"; toast.error(message); } }; const getStatusColor = (status: WorkItemStatus) => { switch (status) { case "Backlog": return "secondary"; case "Todo": return "outline"; case "InProgress": return "default"; case "Done": return "default"; 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"; } }; // Loading state if (storyLoading || epicLoading || projectLoading) { return (
); } // Error state if (storyError || !story) { return (
Error Loading Story {storyError instanceof Error ? storyError.message : "Story not found"}
); } return (
{/* Breadcrumb Navigation */}
Projects / {project && ( <> {project.name} / )} Epics / {epic && ( <> {epic.name} / )} Stories / {story.title}
{/* Header */}

{story.title}

{story.status} {story.priority}
{/* Two-column layout */}
{/* Main Content Area (2/3 width) */}
{/* Story Details Card */} Story Details {story.description ? (

Description

{story.description}

) : (

No description

)}
{/* Tasks Section - Sprint 4 Story 2 */}
{/* Metadata Sidebar (1/3 width) */}
{/* Status */} Status {/* Priority */} Priority {/* Story Points - Sprint 4 Story 3 */} {story.storyPoints && ( Story Points
{story.storyPoints}
)} {/* Assignee */} {story.assigneeId && ( Assignee
{story.assigneeId}
)} {/* Tags - Sprint 4 Story 3 */} {story.tags && story.tags.length > 0 && ( Tags
{story.tags.map((tag) => ( {tag} ))}
)} {/* Time Tracking */} {(story.estimatedHours !== undefined || story.actualHours !== undefined) && ( Time Tracking {story.estimatedHours !== undefined && (
Estimated: {story.estimatedHours}h
)} {story.actualHours !== undefined && (
Actual: {story.actualHours}h
)}
)} {/* Acceptance Criteria - Sprint 4 Story 3 */} {story.acceptanceCriteria && story.acceptanceCriteria.length > 0 && ( Acceptance Criteria {story.acceptanceCriteria.map((criterion, index) => (
{criterion}
))}
)} {/* Dates */} Dates

Created

{formatDistanceToNow(new Date(story.createdAt), { addSuffix: true })}

Updated

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

{/* Parent Epic Card */} {epic && ( Parent Epic
{epic.name}
{epic.status} {epic.priority}
)}
{/* Edit Story Dialog */} Edit Story Update the story details setIsEditDialogOpen(false)} onCancel={() => setIsEditDialogOpen(false)} /> {/* Delete Story Confirmation Dialog */} 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" )}
); }