import "./GroupTaskModal.css"; import { useCallback, useEffect, useMemo, useState } from "react"; import { CheckCircle2, CircleDashed, ExternalLink, Loader2, X } from "lucide-react"; import { apiGetBranchGroup, apiPromoteBranchGroup, type BranchGroupSummary } from "../api"; import { subscribeSse } from "../sse-bus"; interface GroupTaskModalProps { isOpen: boolean; onClose: () => void; groupId: string | null; projectId?: string; onOpenMemberTask: (taskId: string) => void; } export function GroupTaskModal({ isOpen, onClose, groupId, projectId, onOpenMemberTask }: GroupTaskModalProps) { const [group, setGroup] = useState(null); const [loading, setLoading] = useState(false); const [promoting, setPromoting] = useState(false); const [error, setError] = useState(null); const loadGroup = useCallback(async () => { if (!groupId) return; setLoading(true); try { const response = await apiGetBranchGroup(groupId, projectId); setGroup(response.group); setError(null); } catch (loadError) { setError(loadError instanceof Error ? loadError.message : "Failed to load branch group"); } finally { setLoading(false); } }, [groupId, projectId]); useEffect(() => { if (!isOpen || !groupId) return; void loadGroup(); }, [groupId, isOpen, loadGroup]); useEffect(() => { if (!isOpen || !groupId) return; const query = projectId ? `?projectId=${encodeURIComponent(projectId)}` : ""; return subscribeSse(`/api/events${query}`, { events: { "task:updated": (event) => { try { const payload = JSON.parse(event.data) as { projectId?: string }; if (projectId && payload.projectId && payload.projectId !== projectId) { return; } } catch { // no-op } void loadGroup(); }, }, onReconnect: () => { void loadGroup(); }, }); }, [groupId, isOpen, loadGroup, projectId]); const completionText = useMemo(() => { if (!group) return ""; return `${group.completion.landed} of ${group.completion.total} members finished`; }, [group]); const completionPercent = useMemo(() => { if (!group || group.completion.total <= 0) return 0; return (group.completion.landed / group.completion.total) * 100; }, [group]); const onPromote = useCallback(async () => { if (!groupId) return; setPromoting(true); try { await apiPromoteBranchGroup(groupId, projectId); await loadGroup(); } finally { setPromoting(false); } }, [groupId, loadGroup, projectId]); if (!isOpen || !groupId) return null; return (
event.stopPropagation()}>

Branch Group {groupId}

{loading && (
Loading branch group…
)} {!loading && error &&
{error}
} {!loading && !error && !group &&
Branch group unavailable
} {!loading && !error && group && ( <>
Shared branch {group.branchName}
Status {group.status}
{completionText}

Members

    {group.members.map((member) => (
  • {member.taskId} · {member.title} {member.column} {member.landed ? : }
  • ))}
{group.prUrl && (
PR #{group.prNumber ?? "—"} ({group.prState})
)} {group.completion.complete && (
{group.autoMerge ? ( Auto-merge enabled ) : ( )}
)} )}
); }