fix(tickets): status filter dropdown and CW resource assignment
- Status filter: aggregate statuses across all boards (deduped by name)
when no board is selected. Backend accepts status_name and filters by
status/name so the same status matches across boards.
- Resource assignment: CW has no /service/tickets/{id}/members endpoint —
assignees live in the ticket's comma-separated `resources` string field.
Rewrote list/add/remove to read/PATCH that field via member identifier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ export const ticketsApi = {
|
||||
query?: string
|
||||
board_id?: number | null
|
||||
status_id?: number | null
|
||||
status_name?: string | null
|
||||
include_closed?: boolean
|
||||
assigned_to_me?: boolean
|
||||
unassigned?: boolean
|
||||
|
||||
@@ -55,25 +55,49 @@ export default function TicketsPage() {
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
// Load statuses when board changes
|
||||
// Load statuses when board changes. If no board is selected, aggregate statuses
|
||||
// across all boards (deduped by name) so the filter is useful before the user
|
||||
// picks a board.
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
if (filters.board_id) {
|
||||
integrationsApi.getBoardStatuses(filters.board_id)
|
||||
.then(setStatuses).catch(() => {})
|
||||
.then(s => { if (!cancelled) setStatuses(s) })
|
||||
.catch(() => { if (!cancelled) setStatuses([]) })
|
||||
} else if (boards.length > 0) {
|
||||
Promise.all(boards.map(b =>
|
||||
integrationsApi.getBoardStatuses(b.id).catch(() => [] as PSATicketStatusItem[])
|
||||
))
|
||||
.then(lists => {
|
||||
if (cancelled) return
|
||||
const byName = new Map<string, PSATicketStatusItem>()
|
||||
lists.flat().forEach(s => {
|
||||
if (!byName.has(s.name)) byName.set(s.name, s)
|
||||
})
|
||||
setStatuses(Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name)))
|
||||
})
|
||||
.catch(() => { if (!cancelled) setStatuses([]) })
|
||||
} else {
|
||||
setStatuses([])
|
||||
}
|
||||
}, [filters.board_id])
|
||||
return () => { cancelled = true }
|
||||
}, [filters.board_id, boards])
|
||||
|
||||
// Fetch tickets on filter/page change
|
||||
const fetchTickets = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setPsaError(null)
|
||||
try {
|
||||
// When no board is selected, statuses are aggregated across boards — filter by
|
||||
// name instead of id so we match the same status across every board.
|
||||
const selectedStatusName = filters.status_id
|
||||
? statuses.find(s => s.id === filters.status_id)?.name
|
||||
: undefined
|
||||
const result = await ticketsApi.searchTickets({
|
||||
query: filters.search || undefined,
|
||||
board_id: filters.board_id ?? undefined,
|
||||
status_id: filters.status_id ?? undefined,
|
||||
status_id: filters.board_id && filters.status_id ? filters.status_id : undefined,
|
||||
status_name: !filters.board_id && selectedStatusName ? selectedStatusName : undefined,
|
||||
include_closed: filters.include_closed,
|
||||
assigned_to_me: filters.assigned === 'me',
|
||||
unassigned: filters.assigned === 'unassigned',
|
||||
@@ -111,7 +135,7 @@ export default function TicketsPage() {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [filters.search, filters.board_id, filters.status_id, filters.include_closed,
|
||||
filters.assigned, filters.priority, filters.company_id, page])
|
||||
filters.assigned, filters.priority, filters.company_id, page, statuses])
|
||||
|
||||
useEffect(() => { fetchTickets() }, [fetchTickets])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user