"use client"; import { ArrowLeft, Calendar, Globe, Loader2, Mail, Send, Shield, User, } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { adminReplyTicket, assignTicket, updateTicketPriority, updateTicketStatus, } from "@/actions/tickets"; import Link from "@/components/link"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useServerAction } from "@/hooks/use-server-action"; import { TICKET_PRIORITIES, TICKET_STATUSES } from "@/lib/validators/ticket"; interface TicketMessage { id: number; message: string; isStaff: boolean; createdAt: string; user: { id: number; username: string; look: string; rank: number }; } interface TicketInfo { id: number; subject: string; category: string; priority: string; status: string; createdAt: string; closedAt: string | null; creator: { id: number; username: string; look: string; rank: number; mail: string; ipRegister: string; accountCreated: number; }; assignee: { id: number; username: string } | null; } interface StaffMember { id: number; username: string; } const STATUS_COLORS: Record = { open: "bg-[var(--admin-info-subtle)] text-[var(--admin-info)]", in_progress: "bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)]", waiting: "bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)]", closed: "bg-muted text-muted-foreground", }; const PRIORITY_COLORS: Record = { low: "text-muted-foreground", normal: "text-[var(--admin-info)]", high: "text-[var(--admin-warning)]", urgent: "text-[var(--admin-error)]", }; export function AdminTicketDetail({ ticket, messages: initialMessages, staffMembers, currentUserId, canEdit, listHref = "/admin/tickets", userLinkMode = "admin", }: { ticket: TicketInfo; messages: TicketMessage[]; staffMembers: StaffMember[]; currentUserId: number; canEdit: boolean; listHref?: string; userLinkMode?: "admin" | "public"; }) { const [messages] = useState(initialMessages); const [reply, setReply] = useState(""); const { run, isPending } = useServerAction(); const messagesEndRef = useRef(null); function creatorHref() { if (userLinkMode === "public") { return `/u/${encodeURIComponent(ticket.creator.username)}`; } return `/admin/users/show/${ticket.creator.id}`; } // biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); function handleReply() { if (!reply.trim() || isPending) return; run( () => adminReplyTicket({ ticketId: ticket.id, message: reply.trim() }), { successMessage: "Reply sent!", onSuccess: () => setReply(""), }, ); } function handleStatusChange(status: string) { run( () => updateTicketStatus({ ticketId: ticket.id, status: status as (typeof TICKET_STATUSES)[number], }), { successMessage: `Status changed to ${status}`, }, ); } function handleAssign(assigneeId: string) { const id = assigneeId === "unassign" ? null : Number(assigneeId); run(() => assignTicket({ ticketId: ticket.id, assigneeId: id }), { successMessage: id ? "Ticket assigned" : "Ticket unassigned", }); } function handlePriorityChange(priority: string) { run( () => updateTicketPriority({ ticketId: ticket.id, priority: priority as (typeof TICKET_PRIORITIES)[number], }), { successMessage: `Priority changed to ${priority}`, }, ); } const isClosed = ticket.status === "closed"; return (
{/* Header */}
#{ticket.id} {ticket.status.replace("_", " ")} {ticket.priority} {ticket.category}

{ticket.subject}

{/* Messages */}
{messages.map((msg) => (
{msg.isStaff ? ( ) : ( )}
{msg.user.username} {msg.isStaff && " (Staff)"} {new Date(msg.createdAt).toLocaleString()}

{msg.message}

))}
{/* Reply */} {canEdit && !isClosed && (