import { useCallback, useEffect, useState } from 'react' import { X } from 'lucide-react' import { psaContextApi } from '@/api/psaContext' import type { TicketContext } from '@/api/psaContext' import { ticketsApi } from '@/api/tickets' import { integrationsApi } from '@/api/integrations' import { TicketDetailHeader } from './detail/TicketDetailHeader' import { TicketResourceManager } from './detail/TicketResourceManager' import { TicketNotesFeed } from './detail/TicketNotesFeed' import { TicketAddNote } from './detail/TicketAddNote' import { TicketConfigs } from './detail/TicketConfigs' import { TicketRelated } from './detail/TicketRelated' import type { PSATicketSearchResult, PSATicketStatusItem, PsaMemberResponse } from '@/types/integrations' import type { PSAResource } from '@/types/tickets' interface Props { ticket: PSATicketSearchResult onClose: () => void onStatusUpdated?: (ticketId: number, newStatus: string, newStatusId: number) => void onSelectRelated?: (ticketId: number) => void } function Skeleton() { return (
) } export function TicketDetailPanel({ ticket, onClose, onStatusUpdated, onSelectRelated }: Props) { const [context, setContext] = useState(null) const [resources, setResources] = useState([]) const [allMembers, setAllMembers] = useState([]) const [statuses, setStatuses] = useState([]) const [contextLoading, setContextLoading] = useState(true) const [resourcesLoading, setResourcesLoading] = useState(true) // Local status state so the select reflects updates immediately, independent // of the parent list's stale `selectedTicket` snapshot. const [currentStatusId, setCurrentStatusId] = useState(ticket.status_id ?? null) const [currentStatusName, setCurrentStatusName] = useState(ticket.status_name ?? null) const ticketIdNum = Number(ticket.id) const loadResources = useCallback(() => { ticketsApi.listResources(ticketIdNum) .then(setResources) .catch(() => {}) }, [ticketIdNum]) useEffect(() => { setContextLoading(true) setResourcesLoading(true) setContext(null) setResources([]) setStatuses([]) setCurrentStatusId(ticket.status_id ?? null) setCurrentStatusName(ticket.status_name ?? null) Promise.all([ psaContextApi.getTicketContext(ticketIdNum), ticketsApi.listResources(ticketIdNum), integrationsApi.listMembers(), integrationsApi.getTicketStatuses(String(ticket.id)), ]) .then(([ctx, res, members, statusList]) => { setContext(ctx) setResources(res) setAllMembers(members) setStatuses(statusList) }) .catch(() => {}) .finally(() => { setContextLoading(false) setResourcesLoading(false) }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [ticket.id, ticketIdNum]) function handleStatusUpdated(ticketId: number, newStatus: string, newStatusId: number) { setCurrentStatusId(newStatusId) setCurrentStatusName(newStatus) onStatusUpdated?.(ticketId, newStatus, newStatusId) } return (
{/* Panel header */}
Ticket Detail
{/* Scrollable body */}
{/* Header with status selector — optimistic, no loading gate */} {/* Resources */} {resourcesLoading ? ( ) : ( )} {/* Notes */}

Notes

{contextLoading ? ( ) : ( )}
{/* Add note */} { // Re-fetch context to refresh notes psaContextApi.getTicketContext(ticketIdNum) .then(setContext) .catch(() => {}) }} /> {/* Configurations */}

Configurations

{contextLoading ? ( ) : ( )}
{/* Related tickets */}

Related Tickets

{contextLoading ? ( ) : ( onSelectRelated?.(ticketId)} /> )}
) }