Files
fusion/packages/dashboard/app/components/TaskCardBadge.tsx
gsxdsm 7899345cfa feat(KB-064): add batch GitHub badge status fetching
- Add batch status API types (BatchStatusRequest, BatchStatusResponse, etc.)

- Add batch GitHub client wrappers for efficient PR/issue status fetching

- Create useBatchBadgeFetch hook for coordinated dashboard fetching

- Update Board component to batch fetch badge statuses on mount

- Update TaskCard to merge batch, WebSocket, and task data with freshness comparison

- Add batch status REST endpoint at POST /api/tasks/batch/status
2026-03-30 16:41:24 -07:00

70 lines
1.9 KiB
TypeScript

import { memo, useEffect } from "react";
import type { IssueInfo, PrInfo } from "@kb/core";
import { useBadgeWebSocket } from "../hooks/useBadgeWebSocket";
import { GitHubBadge } from "./GitHubBadge";
export function pickPreferredBadge<T extends { lastCheckedAt?: string }>(
liveValue: T | null | undefined,
liveTimestamp: string | undefined,
taskValue: T | undefined,
taskTimestamp: string | undefined,
): T | undefined {
if (liveValue === undefined || !liveTimestamp) {
return taskValue;
}
if (!taskTimestamp || liveTimestamp >= taskTimestamp) {
return liveValue ?? undefined;
}
return taskValue;
}
interface TaskCardBadgeProps {
taskId: string;
prInfo?: PrInfo;
issueInfo?: IssueInfo;
updatedAt: string;
isInViewport: boolean;
}
function TaskCardBadgeComponent({ taskId, prInfo, issueInfo, updatedAt, isInViewport }: TaskCardBadgeProps) {
const { badgeUpdates, subscribeToBadge, unsubscribeFromBadge } = useBadgeWebSocket();
const hasGitHubBadge = Boolean(prInfo || issueInfo);
useEffect(() => {
if (!hasGitHubBadge || !isInViewport) {
unsubscribeFromBadge(taskId);
return;
}
subscribeToBadge(taskId);
return () => {
unsubscribeFromBadge(taskId);
};
}, [hasGitHubBadge, isInViewport, subscribeToBadge, taskId, unsubscribeFromBadge]);
const liveBadgeData = badgeUpdates.get(taskId);
const livePrInfo = pickPreferredBadge<PrInfo>(
liveBadgeData?.prInfo,
liveBadgeData?.timestamp,
prInfo,
prInfo?.lastCheckedAt ?? updatedAt,
);
const liveIssueInfo = pickPreferredBadge<IssueInfo>(
liveBadgeData?.issueInfo,
liveBadgeData?.timestamp,
issueInfo,
issueInfo?.lastCheckedAt ?? updatedAt,
);
if (!livePrInfo && !liveIssueInfo) {
return null;
}
return <GitHubBadge prInfo={livePrInfo} issueInfo={liveIssueInfo} />;
}
export const TaskCardBadge = memo(TaskCardBadgeComponent);
TaskCardBadge.displayName = "TaskCardBadge";