nexus/ui/src/pages/Issues.tsx
Mikkel Georgsen e4ddb2453a feat(06-02): replace Select a company empty states + CLI Paperclip strings
- 14 UI pages: all Select a company empty states use VOCAB.company.toLowerCase()
- AgentConfigForm: 3 error throws use VOCAB.company
- AgentDetail: additional Select a company upload error replaced
- CLI run.ts: Starting/Could not locate/failed to start messages use VOCAB.appName
- CLI deployment-auth-check: repairHint uses VOCAB.appName
- CLI agent-jwt-secret-check: repairHint uses VOCAB.appName
- CLI allowed-hostname: restart message uses VOCAB.appName
- Added VOCAB import to all files missing it
2026-04-02 15:08:37 +00:00

118 lines
4.2 KiB
TypeScript

import { useEffect, useMemo, useCallback } from "react";
import { VOCAB } from "@paperclipai/branding";
import { useLocation, 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";
import { queryKeys } from "../lib/queryKeys";
import { createIssueDetailLocationState } from "../lib/issueDetailBreadcrumb";
import { EmptyState } from "../components/EmptyState";
import { IssuesList } from "../components/IssuesList";
import { CircleDot } from "lucide-react";
export function Issues() {
const { selectedCompanyId } = useCompany();
const { setBreadcrumbs } = useBreadcrumbs();
const location = useLocation();
const [searchParams] = useSearchParams();
const queryClient = useQueryClient();
const initialSearch = searchParams.get("q") ?? "";
const participantAgentId = searchParams.get("participantAgentId") ?? undefined;
const handleSearchChange = useCallback((search: string) => {
const trimmedSearch = search.trim();
const currentSearch = new URLSearchParams(window.location.search).get("q") ?? "";
if (currentSearch === trimmedSearch) return;
const url = new URL(window.location.href);
if (trimmedSearch) {
url.searchParams.set("q", trimmedSearch);
} else {
url.searchParams.delete("q");
}
const nextUrl = `${url.pathname}${url.search}${url.hash}`;
window.history.replaceState(window.history.state, "", nextUrl);
}, []);
const { data: agents } = useQuery({
queryKey: queryKeys.agents.list(selectedCompanyId!),
queryFn: () => agentsApi.list(selectedCompanyId!),
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!),
enabled: !!selectedCompanyId,
refetchInterval: 5000,
});
const liveIssueIds = useMemo(() => {
const ids = new Set<string>();
for (const run of liveRuns ?? []) {
if (run.issueId) ids.add(run.issueId);
}
return ids;
}, [liveRuns]);
const issueLinkState = useMemo(
() =>
createIssueDetailLocationState(
"Issues",
`${location.pathname}${location.search}${location.hash}`,
"issues",
),
[location.pathname, location.search, location.hash],
);
useEffect(() => {
setBreadcrumbs([{ label: "Issues" }]);
}, [setBreadcrumbs]);
const { data: issues, isLoading, error } = useQuery({
queryKey: [...queryKeys.issues.list(selectedCompanyId!), "participant-agent", participantAgentId ?? "__all__"],
queryFn: () => issuesApi.list(selectedCompanyId!, { participantAgentId }),
enabled: !!selectedCompanyId,
});
const updateIssue = useMutation({
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
issuesApi.update(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(selectedCompanyId!) });
},
});
if (!selectedCompanyId) {
return <EmptyState icon={CircleDot} message={`Select a ${VOCAB.company.toLowerCase()} to view issues.`} />;
}
return (
<IssuesList
issues={issues ?? []}
isLoading={isLoading}
error={error as Error | null}
agents={agents}
projects={projects}
liveIssueIds={liveIssueIds}
viewStateKey="paperclip:issues-view"
issueLinkState={issueLinkState}
initialAssignees={searchParams.get("assignee") ? [searchParams.get("assignee")!] : undefined}
initialSearch={initialSearch}
onSearchChange={handleSearchChange}
onUpdateIssue={(id, data) => updateIssue.mutate({ id, data })}
searchFilters={participantAgentId ? { participantAgentId } : undefined}
/>
);
}