"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { PlusIcon, SearchIcon, ArrowLeftIcon, SendIcon, PaperclipIcon } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; import { getTickets, getTicket, getTicketTransactions, getQueues, createTicket } from "@/lib/api"; import type { Ticket, Queue, Transaction } from "@/lib/types"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; const STATUS_COLORS: Record = { new: "#8a8f98", open: "#7170ff", in_progress: "#f59e0b", resolved: "#22c55e", closed: "#6b7280", }; const STATUS_LABELS: Record = { new: "New", open: "Open", in_progress: "In progress", resolved: "Resolved", closed: "Closed", }; const FILTERS = [ { key: null, label: "All" }, { key: "open", label: "Open" }, { key: "in_progress", label: "In progress" }, { key: "resolved", label: "Resolved" }, { key: "my", label: "My tickets" }, { key: "unassigned", label: "Unassigned" }, ] as const; type FilterKey = (typeof FILTERS)[number]["key"]; function getInitial(name: string): string { if (!name) return "?"; return name.charAt(0).toUpperCase(); } function getInitialColor(name: string): string { const colors = ["#5e6ad2", "#7170ff", "#828fff", "#f59e0b", "#22c55e"]; let hash = 0; for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); } return colors[Math.abs(hash) % colors.length]; } function TicketRow({ ticket, selected, onClick }: { ticket: Ticket; selected: boolean; onClick: () => void }) { const statusColor = STATUS_COLORS[ticket.status] || STATUS_COLORS.new; const shortId = ticket.id.slice(0, 8); const timeAgo = formatDistanceToNow(new Date(ticket.updated_at), { addSuffix: true }); return ( ); } function SkeletonRow() { return (
); } function TicketDetailPanel({ ticketId, onBack, }: { ticketId: string; onBack: () => void; }) { const [ticket, setTicket] = useState(null); const [transactions, setTransactions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [replyText, setReplyText] = useState(""); useEffect(() => { setLoading(true); setError(null); setTicket(null); setTransactions([]); setReplyText(""); Promise.all([ getTicket(ticketId), getTicketTransactions(ticketId), ]).then(([ticketRes, txRes]) => { if (ticketRes.error) { setError(ticketRes.error); } else { setTicket(ticketRes.data); } if (txRes.data) { setTransactions(txRes.data); } setLoading(false); }); }, [ticketId]); return (
{/* Header */}
{loading && (
{Array.from({ length: 6 }).map((_, i) => (
))}
)} {error && (

{error}

)} {!loading && !error && ticket && ( <> {/* Title */}

{ticket.subject}

{ticket.id.slice(0, 8)}

{/* Conversation */}
{transactions.length === 0 && (

No activity yet

)} {transactions.map((tx) => { const isSystem = tx.transaction_type === "status_change" || tx.transaction_type === "assignment" || tx.transaction_type === "create"; const isAgent = tx.transaction_type === "agent_reply" || tx.transaction_type === "internal_note"; if (isSystem) { const txTimeAgo = formatDistanceToNow( new Date(tx.created_at), { addSuffix: true } ); let message = ""; if (tx.transaction_type === "create") { message = "Ticket created"; } else if (tx.transaction_type === "status_change") { const oldLabel = tx.old_value ? STATUS_LABELS[tx.old_value] || tx.old_value : "?"; const newLabel = tx.new_value ? STATUS_LABELS[tx.new_value] || tx.new_value : "?"; message = `${getInitial(tx.creator_id)} changed status from ${oldLabel} to ${newLabel}`; } else if (tx.transaction_type === "assignment") { message = tx.new_value ? `${getInitial(tx.creator_id)} assigned to ${tx.new_value}` : `${getInitial(tx.creator_id)} unassigned`; } else { message = tx.transaction_type; } return (
{message} ยท {txTimeAgo}
); } const isInternal = tx.transaction_type === "internal_note"; const txTimeAgo = formatDistanceToNow( new Date(tx.created_at), { addSuffix: true } ); return (
{getInitial(tx.creator_id)}
{isInternal && (
Internal note
)}

{typeof tx.data === "object" && tx.data !== null && "body" in (tx.data as Record) ? String( (tx.data as Record).body ) : tx.transaction_type}

{txTimeAgo}

); })}
{/* Reply box */}