"use client" /** * Ticket Detail Page */ import { useEffect, useState } from "react" import { useParams } from "next/navigation" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Textarea } from "@/components/ui/textarea" import { useAuth } from "@/contexts/auth-context" import { getApiUrl } from "@/lib/api-config" import Link from "next/link" import { ArrowLeft, Send, Loader2, Clock, User } from "lucide-react" interface Ticket { id: number ticket_number: string type: string status: string priority: string title: string description: string reporter_email: string reporter_name: string is_public: boolean upvotes: number created_at: string updated_at: string resolved_at: string | null } interface Comment { id: number author_name: string content: string is_internal: boolean created_at: string } const STATUS_STYLES: Record = { open: { label: "Open", variant: "default" }, in_progress: { label: "In Progress", variant: "secondary" }, resolved: { label: "Resolved", variant: "outline" }, closed: { label: "Closed", variant: "outline" }, } const PRIORITY_COLORS: Record = { low: "bg-gray-500", medium: "bg-yellow-500", high: "bg-orange-500", critical: "bg-red-500", } export default function TicketDetailPage() { const params = useParams() const ticketNumber = params.id as string const { user } = useAuth() const [ticket, setTicket] = useState(null) const [comments, setComments] = useState([]) const [loading, setLoading] = useState(true) const [newComment, setNewComment] = useState("") const [submitting, setSubmitting] = useState(false) const [error, setError] = useState("") useEffect(() => { if (ticketNumber) { fetchTicket() fetchComments() } }, [ticketNumber]) const fetchTicket = async () => { try { const token = localStorage.getItem("token") const headers: Record = {} if (token) headers["Authorization"] = `Bearer ${token}` const res = await fetch(`${getApiUrl()}/tickets/${ticketNumber}`, { headers }) if (res.ok) { setTicket(await res.json()) } else if (res.status === 404) { setError("Ticket not found") } else if (res.status === 403) { setError("You don't have access to this ticket") } } catch (e) { setError("Failed to load ticket") } finally { setLoading(false) } } const fetchComments = async () => { try { const token = localStorage.getItem("token") const headers: Record = {} if (token) headers["Authorization"] = `Bearer ${token}` const res = await fetch(`${getApiUrl()}/tickets/${ticketNumber}/comments`, { headers }) if (res.ok) { setComments(await res.json()) } } catch (e) { console.error("Failed to load comments", e) } } const handleSubmitComment = async (e: React.FormEvent) => { e.preventDefault() if (!newComment.trim()) return setSubmitting(true) try { const token = localStorage.getItem("token") const res = await fetch(`${getApiUrl()}/tickets/${ticketNumber}/comments`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ content: newComment }), }) if (res.ok) { const comment = await res.json() setComments([...comments, comment]) setNewComment("") } } catch (e) { console.error("Failed to add comment", e) } finally { setSubmitting(false) } } if (loading) { return (
) } if (error || !ticket) { return (

{error || "Ticket not found"}

) } return (
{/* Header */}
{ticket.ticket_number} {STATUS_STYLES[ticket.status]?.label || ticket.status}
{/* Ticket Content */} {ticket.title}
{ticket.reporter_name || "Anonymous"} {new Date(ticket.created_at).toLocaleString()}
{ticket.description && (

{ticket.description}

)}
{/* Comments */}

Comments ({comments.length})

{comments.length === 0 ? (

No comments yet

) : ( comments.map((comment) => (
{comment.author_name} {new Date(comment.created_at).toLocaleString()}

{comment.content}

)) )}
{/* Add Comment Form */} {user && (