From 9d88c8456c039e66a65195c24a6808d65eba724c Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Thu, 16 Apr 2026 03:05:13 +0000 Subject: [PATCH] feat(tickets): add tickets API client, update integrations API for paginated search, fix callers - Create frontend/src/api/tickets.ts with ticketsApi (resources, status, create, ai-parse, priorities, search) - Update integrationsApi.searchTickets and searchTicketsQueue return types from PSATicketSearchResult[] to TicketListResponse - Fix TicketQueue.tsx to use results.items (append/set) and results.items.length for pagination check - Fix TicketPickerModal.tsx to use results.items when setting search results - Export ticketsApi from api/index.ts Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/api/index.ts | 1 + frontend/src/api/integrations.ts | 9 ++-- frontend/src/api/tickets.ts | 48 +++++++++++++++++++ .../src/components/dashboard/TicketQueue.tsx | 6 +-- .../components/session/TicketPickerModal.tsx | 2 +- 5 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 frontend/src/api/tickets.ts diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 9ec66c9b..50440df8 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -37,3 +37,4 @@ export { handoffsApi } from './handoffs' export { resolutionsApi } from './resolutions' export { deviceTypesApi } from './deviceTypes' export { networkDiagramsApi } from './networkDiagrams' +export { ticketsApi } from './tickets' diff --git a/frontend/src/api/integrations.ts b/frontend/src/api/integrations.ts index b60113d0..709a0bc6 100644 --- a/frontend/src/api/integrations.ts +++ b/frontend/src/api/integrations.ts @@ -1,6 +1,7 @@ import { apiClient } from './client' import type { PsaConnectionResponse, PsaConnectionCreate, PsaConnectionUpdate, PsaConnectionTestResponse } from '@/types' import type { PSABoard, TicketLinkResponse, PSATicketSearchResult, PSATicketInfo, PSATicketStatusItem, PsaPreviewResponse, PsaPostResponse, PsaPostLogEntry, PsaMemberResponse, PsaMemberMappingResponse, AutoMatchResult, FlowpilotSettings } from '@/types/integrations' +import type { TicketListResponse } from '@/types/tickets' export const integrationsApi = { getConnection: () => @@ -15,16 +16,16 @@ export const integrationsApi = { apiClient.post(`/integrations/psa/connections/${id}/test`).then(r => r.data), listBoards: () => apiClient.get('/integrations/psa/boards').then(r => r.data), - searchTickets: (params: { query?: string; board_id?: number; include_closed?: boolean }) => - apiClient.get('/integrations/psa/tickets/search', { params }).then(r => r.data), + searchTickets: (params: { query?: string; board_id?: number; include_closed?: boolean }): Promise => + apiClient.get('/integrations/psa/tickets/search', { params }).then(r => r.data), searchTicketsQueue: (params: { assigned_to_me?: boolean unassigned?: boolean board_ids?: string page?: number page_size?: number - }) => - apiClient.get('/integrations/psa/tickets/search', { params }).then(r => r.data), + }): Promise => + apiClient.get('/integrations/psa/tickets/search', { params }).then(r => r.data), getTicket: (id: string) => apiClient.get(`/integrations/psa/tickets/${id}`).then(r => r.data), getTicketStatuses: (ticketId: string) => diff --git a/frontend/src/api/tickets.ts b/frontend/src/api/tickets.ts new file mode 100644 index 00000000..fb049cb1 --- /dev/null +++ b/frontend/src/api/tickets.ts @@ -0,0 +1,48 @@ +import { apiClient } from './client' +import type { + PSAResource, + PSATicketCreated, + PSATicketStatusUpdate, + TicketCreationPayload, + AiParseResponse, + TicketListResponse, + PSAPriority, +} from '@/types/tickets' + +export const ticketsApi = { + listResources: (ticketId: number): Promise => + apiClient.get(`/integrations/psa/tickets/${ticketId}/resources`).then(r => r.data), + + addResource: (ticketId: number, memberId: number): Promise => + apiClient.post(`/integrations/psa/tickets/${ticketId}/resources?member_id=${memberId}`).then(r => r.data), + + removeResource: (ticketId: number, memberId: number): Promise => + apiClient.delete(`/integrations/psa/tickets/${ticketId}/resources/${memberId}`).then(() => undefined), + + updateStatus: (ticketId: number, statusId: number): Promise => + apiClient.patch(`/integrations/psa/tickets/${ticketId}/status?status_id=${statusId}`).then(r => r.data), + + createTicket: (payload: TicketCreationPayload): Promise => + apiClient.post('/integrations/psa/tickets', payload).then(r => r.data), + + aiParse: (prompt: string): Promise => + apiClient.post('/integrations/psa/tickets/ai-parse', { prompt }).then(r => r.data), + + listPriorities: (): Promise => + apiClient.get('/integrations/psa/priorities').then(r => r.data), + + searchTickets: (params: { + query?: string + board_id?: number | null + status_id?: number | null + include_closed?: boolean + assigned_to_me?: boolean + unassigned?: boolean + board_ids?: string + priority?: string | null + company_id?: number | null + page?: number + page_size?: number + }): Promise => + apiClient.get('/integrations/psa/tickets/search', { params }).then(r => r.data), +} diff --git a/frontend/src/components/dashboard/TicketQueue.tsx b/frontend/src/components/dashboard/TicketQueue.tsx index da667479..829537be 100644 --- a/frontend/src/components/dashboard/TicketQueue.tsx +++ b/frontend/src/components/dashboard/TicketQueue.tsx @@ -234,11 +234,11 @@ export function TicketQueue() { try { const results = await integrationsApi.searchTicketsQueue(params) if (append) { - setTickets((prev) => [...prev, ...results]) + setTickets((prev) => [...prev, ...results.items]) } else { - setTickets(results) + setTickets(results.items) } - setHasMore(results.length === PAGE_SIZE) + setHasMore(results.items.length === PAGE_SIZE) setError(null) } catch { setError('Failed to load tickets. Check your PSA connection.') diff --git a/frontend/src/components/session/TicketPickerModal.tsx b/frontend/src/components/session/TicketPickerModal.tsx index b7bf711d..6ae47001 100644 --- a/frontend/src/components/session/TicketPickerModal.tsx +++ b/frontend/src/components/session/TicketPickerModal.tsx @@ -56,7 +56,7 @@ export function TicketPickerModal({ open, onClose, sessionId, onLinked, onSelect query: query.trim(), include_closed: closed, }) - setSearchResults(results) + setSearchResults(results.items) setHasSearched(true) } catch (err: unknown) { const message =