"use client";
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { ListChecks, ChevronDown, ChevronUp } from "lucide-react";
import { billsAPI, followsAPI } from "@/lib/api";
import { cn, formatDate, partyBadgeColor } from "@/lib/utils";
import type { BillVote, MemberVotePosition } from "@/lib/types";
interface VotePanelProps {
billId: string;
alwaysRender?: boolean;
}
export function VotePanel({ billId, alwaysRender = false }: VotePanelProps) {
const [expanded, setExpanded] = useState(true);
const { data: votes, isLoading } = useQuery({
queryKey: ["votes", billId],
queryFn: () => billsAPI.getVotes(billId),
staleTime: 5 * 60 * 1000,
});
const { data: follows } = useQuery({
queryKey: ["follows"],
queryFn: () => followsAPI.list(),
retry: false,
throwOnError: false,
});
const followedMemberIds = new Set(
(follows || [])
.filter((f) => f.follow_type === "member")
.map((f) => f.follow_value)
);
if (isLoading || !votes || votes.length === 0) {
if (!alwaysRender) return null;
return (
{isLoading ? "Checking for roll-call votes…" : "No roll-call votes have been recorded for this bill."}
);
}
return (
{expanded && (
{votes.map((vote) => (
))}
)}
);
}
function VoteRow({
vote,
followedMemberIds,
}: {
vote: BillVote;
followedMemberIds: Set;
}) {
const [showPositions, setShowPositions] = useState(false);
const total = (vote.yeas ?? 0) + (vote.nays ?? 0) + (vote.not_voting ?? 0);
const yeaPct = total > 0 ? ((vote.yeas ?? 0) / total) * 100 : 0;
const nayPct = total > 0 ? ((vote.nays ?? 0) / total) * 100 : 0;
const resultLower = (vote.result ?? "").toLowerCase();
const passed =
resultLower.includes("pass") ||
resultLower.includes("agreed") ||
resultLower.includes("adopted") ||
resultLower.includes("enacted");
const followedPositions: MemberVotePosition[] = vote.positions.filter(
(p) => p.bioguide_id && followedMemberIds.has(p.bioguide_id)
);
return (
{/* Header row */}
{vote.result && (
{vote.result}
)}
{vote.chamber} Roll #{vote.roll_number}
{vote.vote_date && ` · ${formatDate(vote.vote_date)}`}
{vote.question && (
{vote.question}
)}
{vote.source_url && (
Source ↗
)}
{/* Yea / Nay bar */}
{total > 0 && (
{vote.yeas ?? "—"} Yea
{vote.nays ?? "—"} Nay
{(vote.not_voting ?? 0) > 0 && (
{vote.not_voting} Not Voting
)}
)}
{/* Followed member positions */}
{followedPositions.length > 0 && (
{showPositions && (
{followedPositions.map((p, i) => (
{vote.chamber === "Senate" ? "Sen." : "Rep."}
{p.member_name}
{p.party && (
{p.party}
)}
{p.state && {p.state}}
{p.position}
))}
)}
)}
);
}