diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index 79182857..3d57bcd7 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -12,10 +12,11 @@ import { PriorityIcon } from "./PriorityIcon"; import { EmptyState } from "./EmptyState"; import { Identity } from "./Identity"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; import { Checkbox } from "@/components/ui/checkbox"; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible"; -import { CircleDot, Plus, Filter, ArrowUpDown, Layers, Check, X, ChevronRight, List, Columns3, User } from "lucide-react"; +import { CircleDot, Plus, Filter, ArrowUpDown, Layers, Check, X, ChevronRight, List, Columns3, User, Search } from "lucide-react"; import { KanbanBoard } from "./KanbanBoard"; import type { Issue } from "@paperclip/shared"; @@ -93,6 +94,25 @@ function applyFilters(issues: Issue[], state: IssueViewState): Issue[] { return result; } +function applySearch(issues: Issue[], searchQuery: string, agentName: (id: string | null) => string | null): Issue[] { + const query = searchQuery.trim().toLowerCase(); + if (!query) return issues; + + return issues.filter((issue) => { + const fields = [ + issue.identifier ?? "", + issue.title, + issue.description ?? "", + issue.status, + issue.priority, + agentName(issue.assigneeAgentId) ?? "", + ...(issue.labels ?? []).map((label) => label.name), + ]; + + return fields.some((field) => field.toLowerCase().includes(query)); + }); +} + function sortIssues(issues: Issue[], state: IssueViewState): Issue[] { const sorted = [...issues]; const dir = state.sortDir === "asc" ? 1 : -1; @@ -165,6 +185,7 @@ export function IssuesList({ }); const [assigneePickerIssueId, setAssigneePickerIssueId] = useState(null); const [assigneeSearch, setAssigneeSearch] = useState(""); + const [issueSearch, setIssueSearch] = useState(""); const updateView = useCallback((patch: Partial) => { setViewState((prev) => { @@ -180,8 +201,10 @@ export function IssuesList({ }; const filtered = useMemo(() => { - return sortIssues(applyFilters(issues, viewState), viewState); - }, [issues, viewState]); + const filteredByControls = applyFilters(issues, viewState); + const filteredBySearch = applySearch(filteredByControls, issueSearch, agentName); + return sortIssues(filteredBySearch, viewState); + }, [issues, viewState, issueSearch, agents]); const { data: labels } = useQuery({ queryKey: queryKeys.issues.labels(selectedCompanyId!), @@ -237,10 +260,22 @@ export function IssuesList({
{/* Toolbar */}
- +
+ +
+ + setIssueSearch(e.target.value)} + placeholder="Search issues..." + className="h-8 pl-7 text-xs sm:text-sm" + aria-label="Search issues" + /> +
+
{/* View mode toggle */} @@ -264,7 +299,7 @@ export function IssuesList({ {/* Filter */} -
)}
+ {liveIssueIds?.has(issue.id) && ( + + + + + + Live + + )}
- {liveIssueIds?.has(issue.id) && ( - - - - - - Live - - )} {formatDate(issue.createdAt)}