fix: pre-landing review fixes — company_id filter and CW condition injection
- Apply company_id filter in CW search_tickets conditions (was silently ignored) - Sanitize query string to strip single quotes before CW condition interpolation - Add psaError state to TicketsPage for permissions error surfacing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,9 @@ class ConnectWiseProvider(PSAProvider):
|
||||
|
||||
conditions: list[str] = []
|
||||
if query:
|
||||
conditions.append(f"summary contains '{query}'")
|
||||
# Sanitize: strip single quotes to prevent CW condition injection
|
||||
safe_query = query.replace("'", "")
|
||||
conditions.append(f"summary contains '{safe_query}'")
|
||||
if filters.get("board_id"):
|
||||
conditions.append(f"board/id = {filters['board_id']}")
|
||||
if filters.get("status_id"):
|
||||
@@ -89,6 +91,8 @@ class ConnectWiseProvider(PSAProvider):
|
||||
if board_ids:
|
||||
board_list = ", ".join(str(bid) for bid in board_ids)
|
||||
conditions.append(f"board/id in ({board_list})")
|
||||
if filters.get("company_id"):
|
||||
conditions.append(f"company/id = {int(filters['company_id'])}")
|
||||
|
||||
condition_str = " and ".join(conditions) if conditions else ""
|
||||
if condition_str:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Plus, Ticket } from 'lucide-react'
|
||||
import { Plus, Ticket, AlertTriangle } from 'lucide-react'
|
||||
import axios from 'axios'
|
||||
import { TicketFilterBar } from '@/components/tickets/TicketFilterBar'
|
||||
import { TicketListRow } from '@/components/tickets/TicketListRow'
|
||||
import { TicketDetailPanel } from '@/components/tickets/TicketDetailPanel'
|
||||
@@ -37,6 +38,7 @@ export default function TicketsPage() {
|
||||
const [tickets, setTickets] = useState<PSATicketSearchResult[]>([])
|
||||
const [total, setTotal] = useState(0)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [psaError, setPsaError] = useState<string | null>(null)
|
||||
const [boards, setBoards] = useState<PSABoard[]>([])
|
||||
const [statuses, setStatuses] = useState<PSATicketStatusItem[]>([])
|
||||
const [priorities, setPriorities] = useState<PSAPriority[]>([])
|
||||
@@ -66,6 +68,7 @@ export default function TicketsPage() {
|
||||
// Fetch tickets on filter/page change
|
||||
const fetchTickets = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setPsaError(null)
|
||||
try {
|
||||
const result = await ticketsApi.searchTickets({
|
||||
query: filters.search || undefined,
|
||||
@@ -90,9 +93,20 @@ export default function TicketsPage() {
|
||||
})
|
||||
return seen.size > 0 ? Array.from(seen, ([id, name]) => ({ id, name })) : prev
|
||||
})
|
||||
} catch {
|
||||
} catch (err: unknown) {
|
||||
setTickets([])
|
||||
setTotal(0)
|
||||
if (axios.isAxiosError(err)) {
|
||||
const status = err.response?.status
|
||||
const detail = (err.response?.data as { detail?: string })?.detail ?? ''
|
||||
if (status === 502 && detail.toLowerCase().includes('permission')) {
|
||||
setPsaError('ConnectWise returned a permissions error. Check that the API member\'s security role has Service Tickets → Inquire → ALL and System → Table Setup → Inquire → ALL.')
|
||||
} else if (status === 502) {
|
||||
setPsaError('ConnectWise is unavailable or returned an error. Check your integration settings.')
|
||||
} else {
|
||||
setPsaError('Failed to load tickets.')
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -165,7 +179,13 @@ export default function TicketsPage() {
|
||||
Loading tickets…
|
||||
</div>
|
||||
)}
|
||||
{!loading && tickets.length === 0 && (
|
||||
{!loading && psaError && (
|
||||
<div className="mx-6 mt-6 flex items-start gap-3 px-4 py-3 rounded-lg bg-danger-dim border border-danger/30 text-sm text-danger">
|
||||
<AlertTriangle className="w-4 h-4 mt-0.5 shrink-0" />
|
||||
<span>{psaError}</span>
|
||||
</div>
|
||||
)}
|
||||
{!loading && !psaError && tickets.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-muted-foreground text-sm gap-2">
|
||||
<Ticket className="w-8 h-8 opacity-30" />
|
||||
No tickets match your filters
|
||||
|
||||
Reference in New Issue
Block a user