Files
paperclip/ui/src/components/IssueProperties.tsx
Forgotten 3dc3813266 Adopt React Query and live updates across all UI pages
Replace custom useApi/useAgents hooks with @tanstack/react-query.
Add LiveUpdatesProvider for WebSocket-driven cache invalidation.
Add queryKeys module for centralized cache key management. Rework
all pages and dialogs to use React Query mutations and queries.
Improve CompanyContext with query-based data fetching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:24:48 -06:00

103 lines
3.3 KiB
TypeScript

import type { Issue } from "@paperclip/shared";
import { useQuery } from "@tanstack/react-query";
import { agentsApi } from "../api/agents";
import { useCompany } from "../context/CompanyContext";
import { queryKeys } from "../lib/queryKeys";
import { StatusIcon } from "./StatusIcon";
import { PriorityIcon } from "./PriorityIcon";
import { formatDate } from "../lib/utils";
import { timeAgo } from "../lib/timeAgo";
import { Separator } from "@/components/ui/separator";
interface IssuePropertiesProps {
issue: Issue;
onUpdate: (data: Record<string, unknown>) => void;
}
function PropertyRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground">{label}</span>
<div className="flex items-center gap-1.5">{children}</div>
</div>
);
}
function statusLabel(status: string): string {
return status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
}
function priorityLabel(priority: string): string {
return priority.charAt(0).toUpperCase() + priority.slice(1);
}
export function IssueProperties({ issue, onUpdate }: IssuePropertiesProps) {
const { selectedCompanyId } = useCompany();
const { data: agents } = useQuery({
queryKey: queryKeys.agents.list(selectedCompanyId!),
queryFn: () => agentsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const agentName = (id: string | null) => {
if (!id || !agents) return null;
const agent = agents.find((a) => a.id === id);
return agent?.name ?? id.slice(0, 8);
};
return (
<div className="space-y-4">
<div className="space-y-1">
<PropertyRow label="Status">
<StatusIcon
status={issue.status}
onChange={(status) => onUpdate({ status })}
/>
<span className="text-sm">{statusLabel(issue.status)}</span>
</PropertyRow>
<PropertyRow label="Priority">
<PriorityIcon
priority={issue.priority}
onChange={(priority) => onUpdate({ priority })}
/>
<span className="text-sm">{priorityLabel(issue.priority)}</span>
</PropertyRow>
<PropertyRow label="Assignee">
<span className="text-sm">
{issue.assigneeAgentId ? agentName(issue.assigneeAgentId) : "Unassigned"}
</span>
</PropertyRow>
<PropertyRow label="Project">
<span className="text-sm text-muted-foreground">
{issue.projectId ? issue.projectId.slice(0, 8) : "None"}
</span>
</PropertyRow>
</div>
<Separator />
<div className="space-y-1">
{issue.startedAt && (
<PropertyRow label="Started">
<span className="text-sm">{formatDate(issue.startedAt)}</span>
</PropertyRow>
)}
{issue.completedAt && (
<PropertyRow label="Completed">
<span className="text-sm">{formatDate(issue.completedAt)}</span>
</PropertyRow>
)}
<PropertyRow label="Created">
<span className="text-sm">{formatDate(issue.createdAt)}</span>
</PropertyRow>
<PropertyRow label="Updated">
<span className="text-sm">{timeAgo(issue.updatedAt)}</span>
</PropertyRow>
</div>
</div>
);
}