diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index 10d0709b..a7e4023d 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -38,6 +38,7 @@ export type IssueViewState = { priorities: string[]; assignees: string[]; labels: string[]; + projects: string[]; sortField: "status" | "priority" | "title" | "created" | "updated"; sortDir: "asc" | "desc"; groupBy: "status" | "priority" | "assignee" | "none"; @@ -50,6 +51,7 @@ const defaultViewState: IssueViewState = { priorities: [], assignees: [], labels: [], + projects: [], sortField: "updated", sortDir: "desc", groupBy: "none", @@ -93,6 +95,7 @@ function applyFilters(issues: Issue[], state: IssueViewState): Issue[] { if (state.priorities.length > 0) result = result.filter((i) => state.priorities.includes(i.priority)); if (state.assignees.length > 0) result = result.filter((i) => i.assigneeAgentId != null && state.assignees.includes(i.assigneeAgentId)); if (state.labels.length > 0) result = result.filter((i) => (i.labelIds ?? []).some((id) => state.labels.includes(id))); + if (state.projects.length > 0) result = result.filter((i) => i.projectId != null && state.projects.includes(i.projectId)); return result; } @@ -124,6 +127,7 @@ function countActiveFilters(state: IssueViewState): number { if (state.priorities.length > 0) count++; if (state.assignees.length > 0) count++; if (state.labels.length > 0) count++; + if (state.projects.length > 0) count++; return count; } @@ -134,11 +138,17 @@ interface Agent { name: string; } +interface ProjectOption { + id: string; + name: string; +} + interface IssuesListProps { issues: Issue[]; isLoading?: boolean; error?: Error | null; agents?: Agent[]; + projects?: ProjectOption[]; liveIssueIds?: Set; projectId?: string; viewStateKey: string; @@ -153,6 +163,7 @@ export function IssuesList({ isLoading, error, agents, + projects, liveIssueIds, projectId, viewStateKey, @@ -333,7 +344,7 @@ export function IssuesList({ className="h-3 w-3 ml-1 hidden sm:block" onClick={(e) => { e.stopPropagation(); - updateView({ statuses: [], priorities: [], assignees: [], labels: [] }); + updateView({ statuses: [], priorities: [], assignees: [], labels: [], projects: [] }); }} /> )} @@ -451,6 +462,23 @@ export function IssuesList({ )} + + {projects && projects.length > 0 && ( +
+ Project +
+ {projects.map((project) => ( + + ))} +
+
+ )} diff --git a/ui/src/pages/Issues.tsx b/ui/src/pages/Issues.tsx index fce74c7a..6dd23bf0 100644 --- a/ui/src/pages/Issues.tsx +++ b/ui/src/pages/Issues.tsx @@ -3,6 +3,7 @@ import { useSearchParams } from "@/lib/router"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { issuesApi } from "../api/issues"; import { agentsApi } from "../api/agents"; +import { projectsApi } from "../api/projects"; import { heartbeatsApi } from "../api/heartbeats"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; @@ -48,6 +49,12 @@ export function Issues() { enabled: !!selectedCompanyId, }); + const { data: projects } = useQuery({ + queryKey: queryKeys.projects.list(selectedCompanyId!), + queryFn: () => projectsApi.list(selectedCompanyId!), + enabled: !!selectedCompanyId, + }); + const { data: liveRuns } = useQuery({ queryKey: queryKeys.liveRuns(selectedCompanyId!), queryFn: () => heartbeatsApi.liveRunsForCompany(selectedCompanyId!), @@ -91,6 +98,7 @@ export function Issues() { isLoading={isLoading} error={error as Error | null} agents={agents} + projects={projects} liveIssueIds={liveIssueIds} viewStateKey="paperclip:issues-view" initialAssignees={searchParams.get("assignee") ? [searchParams.get("assignee")!] : undefined}