Backend:
- Add fetch_bill_actions task with pagination and idempotent upsert
- Add fetch_actions_for_active_bills nightly batch (4 AM UTC beat schedule)
- Wire fetch_bill_actions into new-bill creation and _update_bill_if_changed
- Add backfill_brief_citations task: detects pre-citation briefs by JSONB
type check, deletes them, re-queues LLM processing against stored text
(LLM calls only — zero Congress.gov or GovInfo calls)
- Add admin endpoints: POST /bills/{id}/reprocess, /backfill-citations,
/trigger-fetch-actions; add uncited_briefs count to /stats
Frontend:
- New BriefPanel component: wraps AIBriefCard, adds amber "What Changed"
badge for amendment briefs and collapsible version history with
inline brief expansion
- Swap AIBriefCard for BriefPanel on bill detail page
- Admin panel: Backfill Citations + Fetch Bill Actions buttons; amber
warning in stats when uncited briefs remain
- Add feature roadmap document with phased plan through Phase 5
Co-Authored-By: Jack Levy
94 lines
3.7 KiB
TypeScript
94 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { use } from "react";
|
|
import Link from "next/link";
|
|
import { ArrowLeft, ExternalLink, User } from "lucide-react";
|
|
import { useBill, useBillTrend } from "@/lib/hooks/useBills";
|
|
import { BriefPanel } from "@/components/bills/BriefPanel";
|
|
import { ActionTimeline } from "@/components/bills/ActionTimeline";
|
|
import { TrendChart } from "@/components/bills/TrendChart";
|
|
import { NewsPanel } from "@/components/bills/NewsPanel";
|
|
import { FollowButton } from "@/components/shared/FollowButton";
|
|
import { billLabel, congressLabel, formatDate, partyBadgeColor, cn } from "@/lib/utils";
|
|
|
|
export default function BillDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = use(params);
|
|
const billId = decodeURIComponent(id);
|
|
|
|
const { data: bill, isLoading } = useBill(billId);
|
|
const { data: trendData } = useBillTrend(billId, 30);
|
|
|
|
if (isLoading) {
|
|
return <div className="text-center py-20 text-muted-foreground">Loading bill...</div>;
|
|
}
|
|
|
|
if (!bill) {
|
|
return (
|
|
<div className="text-center py-20">
|
|
<p className="text-muted-foreground">Bill not found.</p>
|
|
<Link href="/bills" className="text-sm text-primary mt-2 inline-block">← Back to bills</Link>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const label = billLabel(bill.bill_type, bill.bill_number);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<Link href="/bills" className="text-muted-foreground hover:text-foreground transition-colors">
|
|
<ArrowLeft className="w-4 h-4" />
|
|
</Link>
|
|
<span className="font-mono text-sm font-semibold text-muted-foreground bg-muted px-2 py-0.5 rounded">
|
|
{label}
|
|
</span>
|
|
<span className="text-sm text-muted-foreground">{bill.chamber}</span>
|
|
<span className="text-sm text-muted-foreground">{congressLabel(bill.congress_number)}</span>
|
|
</div>
|
|
<h1 className="text-xl font-bold leading-snug">
|
|
{bill.short_title || bill.title || "Untitled Bill"}
|
|
</h1>
|
|
{bill.sponsor && (
|
|
<div className="flex items-center gap-2 mt-2 text-sm text-muted-foreground">
|
|
<User className="w-3.5 h-3.5" />
|
|
<Link href={`/members/${bill.sponsor.bioguide_id}`} className="hover:text-foreground transition-colors">
|
|
{bill.sponsor.name}
|
|
</Link>
|
|
{bill.sponsor.party && (
|
|
<span className={cn("px-1.5 py-0.5 rounded text-xs font-medium", partyBadgeColor(bill.sponsor.party))}>
|
|
{bill.sponsor.party}
|
|
</span>
|
|
)}
|
|
{bill.sponsor.state && <span>{bill.sponsor.state}</span>}
|
|
</div>
|
|
)}
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
Introduced: {formatDate(bill.introduced_date)}
|
|
{bill.congress_url && (
|
|
<a href={bill.congress_url} target="_blank" rel="noopener noreferrer" className="ml-3 hover:text-primary transition-colors">
|
|
congress.gov <ExternalLink className="w-3 h-3 inline" />
|
|
</a>
|
|
)}
|
|
</p>
|
|
</div>
|
|
<FollowButton type="bill" value={bill.bill_id} />
|
|
</div>
|
|
|
|
{/* Content grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-6">
|
|
<div className="md:col-span-2 space-y-6">
|
|
<BriefPanel briefs={bill.briefs} />
|
|
<ActionTimeline actions={bill.actions} />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<TrendChart data={trendData} />
|
|
<NewsPanel articles={bill.news_articles} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|