Merge main into feat/flowpilot-migration
Brings in PR #141 (PSA ticket management) so FlowPilot can ship on top of a unified main. Two manual conflict resolutions: 1. CLAUDE.md — kept the FlowPilot ai-handoff rewrite (`.ai/`-driven protocol). The pre-rewrite reference content (CW integration notes, lessons archive, env vars table) lives in `docs/connectwise/`, `docs/LESSONS-ARCHIVE.md`, and DEV-ENV.md by design. 2. frontend/src/pages/AssistantChatPage.tsx — both conflict regions were purely additive. Concatenated FlowPilot's Phase 2-9 state hooks (facts, activeFix, preview*, scriptPanelOpen, templatizeQueue) with PSA's spin-off ticket state (linkedTicket, showNewTicket, spinOffHint). Both modal mounts (TemplatizePrompt, ShortcutsHelpOverlay, NewTicketModal) kept. All setters wired by either branch are intact. Verification: - `tsc -b` clean across the merged tree. - Browser smoke-test (Session B fixture): Phase 9 ProposalBanner ("Run AI-drafted PowerShell to recover SSL VPN") renders alongside PSA's new Tickets sidebar icon. Console clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -37,3 +37,4 @@ export { handoffsApi } from './handoffs'
|
||||
export { resolutionsApi } from './resolutions'
|
||||
export { deviceTypesApi } from './deviceTypes'
|
||||
export { networkDiagramsApi } from './networkDiagrams'
|
||||
export { ticketsApi } from './tickets'
|
||||
|
||||
@@ -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 { PSABoard, TicketLinkResponse, PSATicketInfo, PSATicketStatusItem, PsaPreviewResponse, PsaPostResponse, PsaPostLogEntry, PsaMemberResponse, PsaMemberMappingResponse, AutoMatchResult, FlowpilotSettings } from '@/types/integrations'
|
||||
import type { TicketListResponse } from '@/types/tickets'
|
||||
|
||||
export const integrationsApi = {
|
||||
getConnection: () =>
|
||||
@@ -15,20 +16,22 @@ export const integrationsApi = {
|
||||
apiClient.post<PsaConnectionTestResponse>(`/integrations/psa/connections/${id}/test`).then(r => r.data),
|
||||
listBoards: () =>
|
||||
apiClient.get<PSABoard[]>('/integrations/psa/boards').then(r => r.data),
|
||||
searchTickets: (params: { query?: string; board_id?: number; include_closed?: boolean }) =>
|
||||
apiClient.get<PSATicketSearchResult[]>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
||||
searchTickets: (params: { query?: string; board_id?: number; include_closed?: boolean }): Promise<TicketListResponse> =>
|
||||
apiClient.get<TicketListResponse>('/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<PSATicketSearchResult[]>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
||||
}): Promise<TicketListResponse> =>
|
||||
apiClient.get<TicketListResponse>('/integrations/psa/tickets/search', { params }).then(r => r.data),
|
||||
getTicket: (id: string) =>
|
||||
apiClient.get<PSATicketInfo>(`/integrations/psa/tickets/${id}`).then(r => r.data),
|
||||
getTicketStatuses: (ticketId: string) =>
|
||||
apiClient.get<PSATicketStatusItem[]>(`/integrations/psa/tickets/${ticketId}/statuses`).then(r => r.data),
|
||||
getBoardStatuses: (boardId: number | string) =>
|
||||
apiClient.get<PSATicketStatusItem[]>(`/integrations/psa/boards/${boardId}/statuses`).then(r => r.data),
|
||||
listMembers: () =>
|
||||
apiClient.get<PsaMemberResponse[]>('/integrations/psa/members').then(r => r.data),
|
||||
getMemberMappings: () =>
|
||||
|
||||
49
frontend/src/api/tickets.ts
Normal file
49
frontend/src/api/tickets.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
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
|
||||
status_name?: string | 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),
|
||||
}
|
||||
Reference in New Issue
Block a user