diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 0d3d5b8..de5110c 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -2,12 +2,11 @@ import { useState, useEffect, useCallback } from "react"; import { useRouter, useSearchParams } from "next/navigation"; -import { PlusIcon, SearchIcon, ArrowLeftIcon, SendIcon, PaperclipIcon } from "lucide-react"; +import { PlusIcon, SearchIcon } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; -import { getTickets, getTicket, getTicketTransactions, getQueues, createTicket, updateTicket, previewTicket } from "@/lib/api"; -import type { Ticket, Queue, Transaction, PreviewResult, UpdateResult } from "@/lib/types"; +import { getTickets, getQueues, createTicket } from "@/lib/api"; +import type { Ticket, Queue } from "@/lib/types"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, @@ -16,15 +15,6 @@ import { DialogDescription, DialogFooter, } from "@/components/ui/dialog"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; -import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; const STATUS_COLORS: Record = { @@ -35,16 +25,6 @@ const STATUS_COLORS: Record = { closed: "#6b7280", }; -const STATUS_LABELS: Record = { - new: "New", - open: "Open", - in_progress: "In progress", - resolved: "Resolved", - closed: "Closed", -}; - -const ALL_STATUSES = ["new", "open", "in_progress", "resolved", "closed"]; - const FILTERS = [ { key: null, label: "All" }, { key: "open", label: "Open" }, @@ -56,21 +36,7 @@ const FILTERS = [ 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 }) { +function TicketRow({ ticket, onClick }: { ticket: Ticket; 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 }); @@ -78,38 +44,35 @@ function TicketRow({ ticket, selected, onClick }: { ticket: Ticket; selected: bo return ( @@ -118,581 +81,13 @@ function TicketRow({ ticket, selected, onClick }: { ticket: Ticket; selected: bo function SkeletonRow() { return ( -
-
-
-
-
-
-
-
- ); -} - -function TicketDetailPanel({ - ticketId, - onBack, -}: { - ticketId: string; - onBack: () => void; -}) { - const [ticket, setTicket] = useState(null); - const [transactions, setTransactions] = useState([]); - const [queue, setQueue] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [replyText, setReplyText] = useState(""); - - // Status change - const [pendingStatus, setPendingStatus] = useState(null); - const [preview, setPreview] = useState(null); - const [previewLoading, setPreviewLoading] = useState(false); - const [previewError, setPreviewError] = useState(null); - const [applyLoading, setApplyLoading] = useState(false); - const [scripResults, setScripResults] = useState(null); - - const fetchData = useCallback(async () => { - setLoading(true); - setError(null); - setTicket(null); - setTransactions([]); - setReplyText(""); - - const [ticketRes, txRes, queuesRes] = await Promise.all([ - getTicket(ticketId), - getTicketTransactions(ticketId), - getQueues(), - ]); - - if (ticketRes.error) { - setError(ticketRes.error); - } else { - setTicket(ticketRes.data); - } - if (txRes.data) { - setTransactions(txRes.data); - } - if (queuesRes.data && ticketRes.data) { - const q = queuesRes.data.find((q) => q.id === ticketRes.data!.queue_id); - if (q) setQueue(q); - } - setLoading(false); - }, [ticketId]); - - useEffect(() => { - fetchData(); - }, [fetchData]); - - const handleStatusSelect = (value: string | null) => { - if (!value || value === ticket?.status) return; - setPendingStatus(value); - setPreview(null); - setPreviewError(null); - setScripResults(null); - setPreviewLoading(true); - - previewTicket(ticketId, { status: value }).then(({ data, error }) => { - setPreviewLoading(false); - if (error) { - setPreviewError(error); - setPendingStatus(null); - } else { - setPreview(data); - } - }); - }; - - const handleApplyStatus = async () => { - if (!pendingStatus) return; - setApplyLoading(true); - setPreviewError(null); - - const { data, error } = await updateTicket(ticketId, { status: pendingStatus }); - - setApplyLoading(false); - - if (error) { - setPreviewError(error); - } else if (data) { - setTicket(data.ticket); - setScripResults(data.scrip_results); - setPreview(null); - setPendingStatus(null); - const txRes = await getTicketTransactions(ticketId); - if (txRes.data) setTransactions(txRes.data); - } - }; - - const handleCancelStatus = () => { - setPendingStatus(null); - setPreview(null); - setPreviewError(null); - setScripResults(null); - }; - - return ( -
- {/* Header */} -
- +
+
+
+
+
- - {loading && ( -
-
- {Array.from({ length: 6 }).map((_, i) => ( -
-
-
-
-
-
-
- ))} -
-
- {Array.from({ length: 5 }).map((_, i) => ( -
- ))} -
-
- )} - - {error && ( -
-

{error}

-
- )} - - {!loading && !error && ticket && ( -
- {/* Left: conversation */} -
- {/* Title */} -
-

- {ticket.subject} -

-

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

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

- No activity yet -

-
- )} - {transactions.map((tx) => { - const isSystem = - tx.transaction_type === "StatusChange" || - tx.transaction_type === "SetOwner" || - tx.transaction_type === "Create"; - const isAgent = - tx.transaction_type === "Correspond" || - tx.transaction_type === "Comment"; - - 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 === "StatusChange") { - 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 === "SetOwner") { - 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 === "Comment"; - 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 */} -
-
-