diff --git a/backend/app/api/endpoints/integrations.py b/backend/app/api/endpoints/integrations.py index 53abd104..78abf7a2 100644 --- a/backend/app/api/endpoints/integrations.py +++ b/backend/app/api/endpoints/integrations.py @@ -376,8 +376,9 @@ async def list_boards( provider = await get_provider_for_account(current_user.account_id, db) boards = await provider.list_boards() return [PSABoardResponse(id=b.id, name=b.name) for b in boards] - except PSAError: + except PSAError as e: # Boards are optional UI chrome — degrade gracefully rather than surfacing a toast + logger.warning("list_boards failed: %s", e) return [] @@ -630,7 +631,9 @@ async def list_ticket_resources( try: return await ticket_svc.list_resources(current_user.account_id, ticket_id, db) except PSAError as e: - raise HTTPException(status_code=502, detail=str(e)) + # Resources are optional display data — degrade gracefully rather than surfacing a toast + logger.warning("list_resources(%s) failed: %s", ticket_id, e) + return [] @router.post("/tickets/{ticket_id}/resources", response_model=PSAResourceSchema, status_code=201) @@ -679,7 +682,8 @@ async def list_priorities( provider = await get_provider_for_account(current_user.account_id, db) raw = await provider.list_priorities() return [PSAPrioritySchema(id=p["id"], name=p["name"]) for p in raw if p.get("id")] - except PSAError: + except PSAError as e: + logger.warning("list_priorities failed: %s", e) return [] diff --git a/frontend/src/components/dashboard/TicketQueue.tsx b/frontend/src/components/dashboard/TicketQueue.tsx index 5437dedf..6826b70d 100644 --- a/frontend/src/components/dashboard/TicketQueue.tsx +++ b/frontend/src/components/dashboard/TicketQueue.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { useNavigate, Link } from 'react-router-dom' -import { Ticket, ChevronDown, Check, Loader2, AlertCircle } from 'lucide-react' +import { Ticket, ChevronDown, Check, AlertCircle } from 'lucide-react' import { integrationsApi } from '@/api/integrations' import type { PSABoard, PSATicketSearchResult } from '@/types/integrations' import { cn } from '@/lib/utils' @@ -193,10 +193,7 @@ export function TicketQueue() { const [selectedBoardIds, setSelectedBoardIds] = useState([]) const [activeTab, setActiveTab] = useState('mine') const [tickets, setTickets] = useState([]) - const [page, setPage] = useState(1) - const [hasMore, setHasMore] = useState(false) const [loading, setLoading] = useState(false) - const [loadingMore, setLoadingMore] = useState(false) const [error, setError] = useState(null) // Check connection on mount @@ -227,9 +224,9 @@ export function TicketQueue() { }, [hasConnection]) const fetchTickets = useCallback( - async (tab: Tab, boardIds: number[], pageNum: number, append: boolean) => { + async (tab: Tab, boardIds: number[]) => { const params: Parameters[0] = { - page: pageNum, + page: 1, page_size: PAGE_SIZE, } if (tab === 'mine') { @@ -243,12 +240,7 @@ export function TicketQueue() { try { const results = await integrationsApi.searchTicketsQueue(params) - if (append) { - setTickets((prev) => [...prev, ...results.items]) - } else { - setTickets(results.items) - } - setHasMore(results.items.length === PAGE_SIZE) + setTickets(results.items) setError(null) } catch { setError('Failed to load tickets. Check your PSA connection.') @@ -261,21 +253,11 @@ export function TicketQueue() { useEffect(() => { if (!hasConnection) return if (activeTab === 'mine' && hasMemberMapping !== true) return - setPage(1) setTickets([]) - setHasMore(false) setLoading(true) - fetchTickets(activeTab, selectedBoardIds, 1, false).finally(() => setLoading(false)) + fetchTickets(activeTab, selectedBoardIds).finally(() => setLoading(false)) }, [activeTab, selectedBoardIds, hasConnection, hasMemberMapping, fetchTickets]) - const handleLoadMore = async () => { - const nextPage = page + 1 - setPage(nextPage) - setLoadingMore(true) - await fetchTickets(activeTab, selectedBoardIds, nextPage, true) - setLoadingMore(false) - } - const handleStartSession = (ticket: PSATicketSearchResult) => { navigate('/pilot', { state: { @@ -368,7 +350,7 @@ export function TicketQueue() { ))} @@ -404,28 +386,6 @@ export function TicketQueue() { )} - {/* Load more */} - {!error && !loading && hasMore && ( -
- -
- )} ) diff --git a/frontend/src/components/tickets/TicketFilterBar.tsx b/frontend/src/components/tickets/TicketFilterBar.tsx index bb92ca1d..6f882ea1 100644 --- a/frontend/src/components/tickets/TicketFilterBar.tsx +++ b/frontend/src/components/tickets/TicketFilterBar.tsx @@ -1,5 +1,6 @@ // frontend/src/components/tickets/TicketFilterBar.tsx -import { Search, X } from 'lucide-react' +import { useState } from 'react' +import { Search, X, User } from 'lucide-react' import { cn } from '@/lib/utils' import type { TicketFilters, PSAPriority } from '@/types/tickets' import type { PSABoard, PSATicketStatusItem } from '@/types/integrations' @@ -27,6 +28,24 @@ export function TicketFilterBar({ const hasNext = page * pageSize < total const hasPrev = page > 1 + // Member search state — text filter over the member list + const [memberSearch, setMemberSearch] = useState('') + const [memberDropdownOpen, setMemberDropdownOpen] = useState(false) + + const currentMemberName = typeof filters.assigned === 'number' + ? (members.find(m => m.id === filters.assigned)?.name ?? `Member ${filters.assigned}`) + : null + + const filteredMembers = members.filter(m => + m.name.toLowerCase().includes(memberSearch.toLowerCase()) + ) + + function handleMemberSelect(memberId: number | 'all' | 'me' | 'unassigned') { + onChange({ assigned: memberId }) + setMemberDropdownOpen(false) + setMemberSearch('') + } + return (
{/* Filter row */} @@ -42,22 +61,60 @@ export function TicketFilterBar({ />
- {/* Assignment */} - + {/* Assignment — searchable member picker */} +
+ + {memberDropdownOpen && ( + <> +
setMemberDropdownOpen(false)} /> +
+
+ setMemberSearch(e.target.value)} + /> +
+
+ {!memberSearch && ( + <> + + + + {members.length > 0 &&
} + + )} + {filteredMembers.map(m => ( + + ))} + {memberSearch && filteredMembers.length === 0 && ( +

No members found

+ )} +
+
+ + )} +
{/* Board */}