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 <noreply@anthropic.com>
This commit is contained in:
@@ -37,3 +37,4 @@ export { handoffsApi } from './handoffs'
|
|||||||
export { resolutionsApi } from './resolutions'
|
export { resolutionsApi } from './resolutions'
|
||||||
export { deviceTypesApi } from './deviceTypes'
|
export { deviceTypesApi } from './deviceTypes'
|
||||||
export { networkDiagramsApi } from './networkDiagrams'
|
export { networkDiagramsApi } from './networkDiagrams'
|
||||||
|
export { ticketsApi } from './tickets'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { apiClient } from './client'
|
import { apiClient } from './client'
|
||||||
import type { PsaConnectionResponse, PsaConnectionCreate, PsaConnectionUpdate, PsaConnectionTestResponse } from '@/types'
|
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 { PSABoard, TicketLinkResponse, PSATicketSearchResult, PSATicketInfo, PSATicketStatusItem, PsaPreviewResponse, PsaPostResponse, PsaPostLogEntry, PsaMemberResponse, PsaMemberMappingResponse, AutoMatchResult, FlowpilotSettings } from '@/types/integrations'
|
||||||
|
import type { TicketListResponse } from '@/types/tickets'
|
||||||
|
|
||||||
export const integrationsApi = {
|
export const integrationsApi = {
|
||||||
getConnection: () =>
|
getConnection: () =>
|
||||||
@@ -15,16 +16,16 @@ export const integrationsApi = {
|
|||||||
apiClient.post<PsaConnectionTestResponse>(`/integrations/psa/connections/${id}/test`).then(r => r.data),
|
apiClient.post<PsaConnectionTestResponse>(`/integrations/psa/connections/${id}/test`).then(r => r.data),
|
||||||
listBoards: () =>
|
listBoards: () =>
|
||||||
apiClient.get<PSABoard[]>('/integrations/psa/boards').then(r => r.data),
|
apiClient.get<PSABoard[]>('/integrations/psa/boards').then(r => r.data),
|
||||||
searchTickets: (params: { query?: string; board_id?: number; include_closed?: boolean }) =>
|
searchTickets: (params: { query?: string; board_id?: number; include_closed?: boolean }): Promise<TicketListResponse> =>
|
||||||
apiClient.get<PSATicketSearchResult[]>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
apiClient.get<TicketListResponse>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
||||||
searchTicketsQueue: (params: {
|
searchTicketsQueue: (params: {
|
||||||
assigned_to_me?: boolean
|
assigned_to_me?: boolean
|
||||||
unassigned?: boolean
|
unassigned?: boolean
|
||||||
board_ids?: string
|
board_ids?: string
|
||||||
page?: number
|
page?: number
|
||||||
page_size?: number
|
page_size?: number
|
||||||
}) =>
|
}): Promise<TicketListResponse> =>
|
||||||
apiClient.get<PSATicketSearchResult[]>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
apiClient.get<TicketListResponse>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
||||||
getTicket: (id: string) =>
|
getTicket: (id: string) =>
|
||||||
apiClient.get<PSATicketInfo>(`/integrations/psa/tickets/${id}`).then(r => r.data),
|
apiClient.get<PSATicketInfo>(`/integrations/psa/tickets/${id}`).then(r => r.data),
|
||||||
getTicketStatuses: (ticketId: string) =>
|
getTicketStatuses: (ticketId: string) =>
|
||||||
|
|||||||
48
frontend/src/api/tickets.ts
Normal file
48
frontend/src/api/tickets.ts
Normal file
@@ -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<PSAResource[]> =>
|
||||||
|
apiClient.get<PSAResource[]>(`/integrations/psa/tickets/${ticketId}/resources`).then(r => r.data),
|
||||||
|
|
||||||
|
addResource: (ticketId: number, memberId: number): Promise<PSAResource> =>
|
||||||
|
apiClient.post<PSAResource>(`/integrations/psa/tickets/${ticketId}/resources?member_id=${memberId}`).then(r => r.data),
|
||||||
|
|
||||||
|
removeResource: (ticketId: number, memberId: number): Promise<void> =>
|
||||||
|
apiClient.delete(`/integrations/psa/tickets/${ticketId}/resources/${memberId}`).then(() => undefined),
|
||||||
|
|
||||||
|
updateStatus: (ticketId: number, statusId: number): Promise<PSATicketStatusUpdate> =>
|
||||||
|
apiClient.patch<PSATicketStatusUpdate>(`/integrations/psa/tickets/${ticketId}/status?status_id=${statusId}`).then(r => r.data),
|
||||||
|
|
||||||
|
createTicket: (payload: TicketCreationPayload): Promise<PSATicketCreated> =>
|
||||||
|
apiClient.post<PSATicketCreated>('/integrations/psa/tickets', payload).then(r => r.data),
|
||||||
|
|
||||||
|
aiParse: (prompt: string): Promise<AiParseResponse> =>
|
||||||
|
apiClient.post<AiParseResponse>('/integrations/psa/tickets/ai-parse', { prompt }).then(r => r.data),
|
||||||
|
|
||||||
|
listPriorities: (): Promise<PSAPriority[]> =>
|
||||||
|
apiClient.get<PSAPriority[]>('/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<TicketListResponse> =>
|
||||||
|
apiClient.get<TicketListResponse>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
||||||
|
}
|
||||||
@@ -234,11 +234,11 @@ export function TicketQueue() {
|
|||||||
try {
|
try {
|
||||||
const results = await integrationsApi.searchTicketsQueue(params)
|
const results = await integrationsApi.searchTicketsQueue(params)
|
||||||
if (append) {
|
if (append) {
|
||||||
setTickets((prev) => [...prev, ...results])
|
setTickets((prev) => [...prev, ...results.items])
|
||||||
} else {
|
} else {
|
||||||
setTickets(results)
|
setTickets(results.items)
|
||||||
}
|
}
|
||||||
setHasMore(results.length === PAGE_SIZE)
|
setHasMore(results.items.length === PAGE_SIZE)
|
||||||
setError(null)
|
setError(null)
|
||||||
} catch {
|
} catch {
|
||||||
setError('Failed to load tickets. Check your PSA connection.')
|
setError('Failed to load tickets. Check your PSA connection.')
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function TicketPickerModal({ open, onClose, sessionId, onLinked, onSelect
|
|||||||
query: query.trim(),
|
query: query.trim(),
|
||||||
include_closed: closed,
|
include_closed: closed,
|
||||||
})
|
})
|
||||||
setSearchResults(results)
|
setSearchResults(results.items)
|
||||||
setHasSearched(true)
|
setHasSearched(true)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const message =
|
const message =
|
||||||
|
|||||||
Reference in New Issue
Block a user