import { createFileRoute } from "@tanstack/react-router"; import { useState } from "react"; import { motion } from "motion/react"; import { toast } from "sonner"; import { trpc } from "@/lib/trpc"; import { useLiveUpdates, type LiveUpdateRule } from "@/hooks"; import { Skeleton } from "@/components/ui/skeleton"; import { Button } from "@/components/ui/button"; import { InboxList } from "@/components/InboxList"; import { InboxDetailPanel } from "@/components/InboxDetailPanel"; import { HQNeedsReviewSection } from "@/components/hq/HQNeedsReviewSection"; import { HQNeedsApprovalSection } from "@/components/hq/HQNeedsApprovalSection"; import { HQResolvingConflictsSection } from "@/components/hq/HQResolvingConflictsSection"; import { HQBlockedSection } from "@/components/hq/HQBlockedSection"; import { HQEmptyState } from "@/components/hq/HQEmptyState"; export const Route = createFileRoute("/hq")({ component: HeadquartersPage, }); const HQ_LIVE_UPDATE_RULES: LiveUpdateRule[] = [ { prefix: "initiative:", invalidate: ["getHeadquartersDashboard"] }, { prefix: "phase:", invalidate: ["getHeadquartersDashboard"] }, { prefix: "agent:", invalidate: ["getHeadquartersDashboard"] }, { prefix: "agent:", invalidate: ["listWaitingAgents", "listMessages"] }, ]; export function HeadquartersPage() { useLiveUpdates(HQ_LIVE_UPDATE_RULES); const query = trpc.getHeadquartersDashboard.useQuery(); const [selectedAgentId, setSelectedAgentId] = useState(null); const utils = trpc.useUtils(); const agentsQuery = trpc.listWaitingAgents.useQuery(); const messagesQuery = trpc.listMessages.useQuery({}); const questionsQuery = trpc.getAgentQuestions.useQuery( { id: selectedAgentId! }, { enabled: !!selectedAgentId } ); const resumeAgentMutation = trpc.resumeAgent.useMutation({ onSuccess: () => { setSelectedAgentId(null); toast.success("Answer submitted"); }, onError: () => { toast.error("Failed to submit answer"); }, }); const dismissQuestionsMutation = trpc.stopAgent.useMutation({ onSuccess: () => { setSelectedAgentId(null); toast.success("Questions dismissed"); }, onError: () => { toast.error("Failed to dismiss questions"); }, }); const respondToMessageMutation = trpc.respondToMessage.useMutation({ onSuccess: () => { setSelectedAgentId(null); toast.success("Response sent"); }, onError: () => { toast.error("Failed to send response"); }, }); const agents = agentsQuery.data ?? []; const messages = messagesQuery.data ?? []; const selectedAgent = selectedAgentId ? agents.find((a) => a.id === selectedAgentId) ?? null : null; const selectedMessage = selectedAgentId ? messages .filter((m) => m.senderId === selectedAgentId) .sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() )[0] ?? null : null; const pendingQuestions = questionsQuery.data ?? null; function handleRefresh() { void utils.listWaitingAgents.invalidate(); void utils.listMessages.invalidate(); } function handleSubmitAnswers(answers: Record) { if (!selectedAgentId) return; resumeAgentMutation.mutate({ id: selectedAgentId, answers }); } function handleDismissQuestions() { if (!selectedAgentId) return; dismissQuestionsMutation.mutate({ id: selectedAgentId }); } function handleDismiss() { if (!selectedMessage) return; respondToMessageMutation.mutate({ id: selectedMessage.id, response: "Acknowledged", }); } const serializedAgents = agents.map((a) => ({ id: a.id, name: a.name, status: a.status, taskId: a.taskId ?? "", updatedAt: String(a.updatedAt), })); const serializedMessages = messages.map((m) => ({ id: m.id, senderId: m.senderId, content: m.content, requiresResponse: m.requiresResponse, status: m.status, createdAt: String(m.createdAt), })); if (query.isLoading) { return (

Headquarters

Items waiting for your attention.

{[1, 2, 3].map((i) => ( ))}
); } if (query.isError) { return (

Failed to load headquarters data.

); } const data = query.data!; const hasAny = data.waitingForInput.length > 0 || data.pendingReviewInitiatives.length > 0 || data.pendingReviewPhases.length > 0 || data.planningInitiatives.length > 0 || data.resolvingConflicts.length > 0 || data.blockedPhases.length > 0; return (

Headquarters

Items waiting for your attention.

{!hasAny ? ( ) : (
{data.waitingForInput.length > 0 && (

Waiting for Input

{/* Left: agent list — hidden on mobile when an agent is selected */}
{/* Right: detail panel */} {selectedAgent ? ( ({ id: q.id, question: q.question, options: q.options, multiSelect: q.multiSelect, })) : null } isLoadingQuestions={questionsQuery.isLoading} questionsError={ questionsQuery.isError ? questionsQuery.error.message : null } onBack={() => setSelectedAgentId(null)} onSubmitAnswers={handleSubmitAnswers} onDismissQuestions={handleDismissQuestions} onDismissMessage={handleDismiss} isSubmitting={resumeAgentMutation.isPending} isDismissingQuestions={dismissQuestionsMutation.isPending} isDismissingMessage={respondToMessageMutation.isPending} submitError={ resumeAgentMutation.isError ? resumeAgentMutation.error.message : null } dismissMessageError={ respondToMessageMutation.isError ? respondToMessageMutation.error.message : null } /> ) : (

No agent selected

Select an agent from the list to answer their questions

)}
)} {(data.pendingReviewInitiatives.length > 0 || data.pendingReviewPhases.length > 0) && ( )} {data.planningInitiatives.length > 0 && ( )} {data.resolvingConflicts.length > 0 && ( )} {data.blockedPhases.length > 0 && ( )}
)}
); }