From 0a318fb9d6d88d8b7e05474f155e2f9058d990ef Mon Sep 17 00:00:00 2001 From: lorentz Date: Mon, 23 Mar 2026 10:14:12 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20ticket=20modal=20=E2=80=94=20picklist=20?= =?UTF-8?q?labels=20for=20status/priority,=20compact=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ticket [id] route: resolve statusLabel/priorityLabel via getTicketStatusPicklist() and getTicketPriorityPicklist() with module-level cache (fetched once per deploy) - Modal: remove subtitle, move ticket# and title into header, compact single-row metadata (status badge + priority badge + assigned resource), dates in 2-col grid, smaller buttons --- app/api/tickets/[id]/route.ts | 26 ++- components/quotes/ticket-detail-modal.tsx | 183 ++++++++-------------- 2 files changed, 89 insertions(+), 120 deletions(-) diff --git a/app/api/tickets/[id]/route.ts b/app/api/tickets/[id]/route.ts index 2edcc5d..552ed52 100644 --- a/app/api/tickets/[id]/route.ts +++ b/app/api/tickets/[id]/route.ts @@ -1,6 +1,20 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAutotaskClient } from '@/lib/services/autotask-factory'; +let _statusPicklist: Record | null = null; +let _priorityPicklist: Record | null = null; + +async function getPicklists() { + const client = getAutotaskClient(); + if (!_statusPicklist) { + try { _statusPicklist = await client.getTicketStatusPicklist(); } catch { _statusPicklist = {}; } + } + if (!_priorityPicklist) { + try { _priorityPicklist = await client.getTicketPriorityPicklist(); } catch { _priorityPicklist = {}; } + } + return { statusPicklist: _statusPicklist!, priorityPicklist: _priorityPicklist! }; +} + export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } @@ -9,8 +23,10 @@ export async function GET( const { id } = await params; const client = getAutotaskClient(); - // Get ticket by ID - const ticket = await client.getEntityById('Tickets', parseInt(id)); + const [ticket, { statusPicklist, priorityPicklist }] = await Promise.all([ + client.getEntityById('Tickets', parseInt(id)), + getPicklists(), + ]); if (!ticket) { return NextResponse.json( @@ -19,9 +35,9 @@ export async function GET( ); } - // Get assigned resource name if available - let assignedResourceName = null; const ticketData = ticket as any; + + let assignedResourceName = null; if (ticketData.assignedResourceID) { try { const resource = await client.getEntityById('Resources', ticketData.assignedResourceID) as any; @@ -35,6 +51,8 @@ export async function GET( ticket: { ...ticket, assignedResourceName, + statusLabel: statusPicklist[ticketData.status] ?? null, + priorityLabel: priorityPicklist[ticketData.priority] ?? null, }, }); } catch (error) { diff --git a/components/quotes/ticket-detail-modal.tsx b/components/quotes/ticket-detail-modal.tsx index a66eee3..164b249 100644 --- a/components/quotes/ticket-detail-modal.tsx +++ b/components/quotes/ticket-detail-modal.tsx @@ -4,7 +4,6 @@ import { useState, useEffect } from 'react'; import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; @@ -13,7 +12,6 @@ import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; import { Ticket as TicketIcon, - Building2, User, Calendar, Clock, @@ -77,25 +75,24 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe onOpenChange(newOpen); }; - const getStatusBadge = (status: number) => { - const statusMap: Record = { - 1: { label: "New", variant: "default" }, - 5: { label: "Complete", variant: "secondary" }, - 8: { label: "In Progress", variant: "default" }, - }; - const config = statusMap[status] || { label: `Status ${status}`, variant: "outline" }; - return {config.label}; + const getStatusBadge = (status: number, label?: string | null) => { + const resolved = label || `Status ${status}`; + const lower = resolved.toLowerCase(); + const variant = + lower.includes('complete') || lower.includes('closed') || lower.includes('resolved') ? 'secondary' : + lower.includes('progress') || lower.includes('waiting') ? 'default' : + lower.includes('new') ? 'default' : 'outline'; + return {resolved}; }; - const getPriorityBadge = (priority: number) => { - const priorityMap: Record = { - 1: { label: "Critical", variant: "destructive" }, - 2: { label: "High", variant: "destructive" }, - 3: { label: "Medium", variant: "default" }, - 4: { label: "Low", variant: "secondary" }, - }; - const config = priorityMap[priority] || { label: `Priority ${priority}`, variant: "outline" as any }; - return {config.label}; + const getPriorityBadge = (priority: number, label?: string | null) => { + const resolved = label || `Priority ${priority}`; + const lower = resolved.toLowerCase(); + const variant: "destructive" | "default" | "secondary" | "outline" = + lower.includes('critical') || lower.includes('high') ? 'destructive' : + lower.includes('medium') ? 'default' : + lower.includes('low') ? 'secondary' : 'outline'; + return {resolved}; }; const formatDate = (dateString?: string) => { @@ -111,102 +108,69 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe return ( - - - - - Ticket Details + + + + + {ticketNumber} + {ticket && {ticket.title}} - - Viewing ticket information from Autotask PSA - {loading && ( -
- +
+
)} {error && ( -
- +
+ {error}
)} {ticket && !loading && ( -
- {/* Header Info */} -
-
-

Ticket Number

-

- {ticket.ticketNumber} -

-
-
-

Status

-
{getStatusBadge(ticket.status)}
-
+
+ {/* Metadata row */} +
+ {getStatusBadge(ticket.status, ticket.statusLabel)} + {getPriorityBadge(ticket.priority, ticket.priorityLabel)} + {ticket.assignedResourceName && ( + + + {ticket.assignedResourceName} + + )}
- {/* Title */} -
-

{ticket.title}

-
- - {/* Key Details */} -
-
-
-

- - Assigned To -

-

{ticket.assignedResourceName || 'Unassigned'}

-
- -
-

Priority

-
{getPriorityBadge(ticket.priority)}
-
- -
-

- - Created Date -

-

{formatDate(ticket.createDate)}

-
+ {/* Dates row */} +
+
+ + Created + {formatDate(ticket.createDate)}
- -
-
-

- - Due Date -

-

{formatDate(ticket.dueDateTime)}

+ {ticket.dueDateTime && ( +
+ + Due + {formatDate(ticket.dueDateTime)}
- - {ticket.resolvedDateTime && ( -
-

- - Resolved Date -

-

{formatDate(ticket.resolvedDateTime)}

-
- )} - - {ticket.purchaseOrderNumber && ( -
-

PO Number

-

{ticket.purchaseOrderNumber}

-
- )} -
+ )} + {ticket.resolvedDateTime && ( +
+ + Resolved + {formatDate(ticket.resolvedDateTime)} +
+ )} + {ticket.purchaseOrderNumber && ( +
+ PO + {ticket.purchaseOrderNumber} +
+ )}
@@ -214,7 +178,7 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe {/* Description */} {ticket.description && (
-

Description

+

Description

{ticket.description}
@@ -224,33 +188,20 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe {/* Resolution */} {ticket.resolution && (
-

Resolution

+

Resolution

{ticket.resolution}
)} - {/* Quote Number */} - {ticket.userDefinedFields && ( - <> - {ticket.userDefinedFields.find((f: any) => f.name === 'Quote Number' && f.value) && ( -
-

Quote Number

-

- {ticket.userDefinedFields.find((f: any) => f.name === 'Quote Number')?.value} -

-
- )} - - )} - {/* Actions */} -
-