Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

BillSquare.tsx7.11 kB
/** * BillSquare Component * Displays a single bill as a rounded square in the GANTT visualization */ 'use client'; import React from 'react'; import { useLocale } from 'next-intl'; import { Link } from '@/i18n/navigation'; import type { BillGanttData } from '@/lib/billGanttUtils'; import { ShareButton } from '@/components/ShareButton'; import { BookmarkButton } from '@/components/bookmarks/BookmarkButton'; import { PartyLogo } from '@/components/PartyLogo'; interface BillPosition { column: number; // Stage column position (%) row: number; // Row within column (0-2) col: number; // Horizontal offset within stage (for multiple bills) } interface BillSquareProps { bill: BillGanttData; gridPosition: BillPosition; swimlane: 'house' | 'committee' | 'senate'; } export function BillSquare({ bill, gridPosition, swimlane }: BillSquareProps) { const locale = useLocale(); const title = (locale === 'fr' ? bill.title_fr : bill.title) || bill.title || ''; const status = (locale === 'fr' ? bill.status_fr : bill.status) || bill.status || ''; const billType = (locale === 'fr' ? bill.bill_type_fr : bill.bill_type) || bill.bill_type || ''; const [isHovered, setIsHovered] = React.useState(false); // Color based on bill type const getBillColors = () => { // Government Bill - Green if (bill.is_government_bill && !billType.toLowerCase().includes('senate')) { return { bg: 'bg-green-500 hover:bg-green-600', border: 'border-green-400', }; } // Private Member's Bill - Gold/Amber if (billType === "Private Member's Bill" || billType === "Projet de loi d'initiative parlementaire") { return { bg: 'bg-amber-500 hover:bg-amber-600', border: 'border-amber-400', }; } // Senate Government Bill - Red if (billType === 'Senate Government Bill' || billType === 'Projet de loi du gouvernement du Sénat') { return { bg: 'bg-red-500 hover:bg-red-600', border: 'border-red-400', }; } // Senate Public Bill - Orange if (billType === 'Senate Public Bill' || billType === 'Projet de loi public du Sénat') { return { bg: 'bg-orange-500 hover:bg-orange-600', border: 'border-orange-400', }; } // Default fallback - Blue return { bg: 'bg-blue-500 hover:bg-blue-600', border: 'border-blue-400', }; }; const { bg: bgColor, border: borderColor } = getBillColors(); // Calculate position based on grid const SQUARE_SIZE = 32; // 32px square const SPACING = 5; // 5px spacing between squares const TOTAL_SIZE = SQUARE_SIZE + SPACING; // 37px total per square // Horizontal position: column position + offset for multiple bills in same stage const leftPosition = `calc(${gridPosition.column}% + ${gridPosition.col * TOTAL_SIZE}px)`; // Vertical position: offset from top, with 5px spacing between rows // Start at top edge, then offset by row const topPosition = gridPosition.row * TOTAL_SIZE; // 0px from top, then 37px per row return ( <div className="absolute group" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} style={{ left: leftPosition, top: `${topPosition}px`, transform: 'translateX(-50%)', zIndex: isHovered ? 10000 : 1, }} > {/* Compact square (always visible) */} <div className={` w-8 h-8 rounded-md ${bgColor} border ${borderColor} flex items-center justify-center shadow-md transition-all duration-200 cursor-pointer relative `} > <span className="text-white font-semibold text-[10px] text-center leading-tight"> {bill.number} </span> </div> {/* Expanded view on hover - positioned outside container */} <div className=" absolute left-1/2 top-0 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 " style={{ pointerEvents: isHovered ? 'auto' : 'none' }} > <div className={` w-64 rounded-md ${bgColor} border-2 ${borderColor} p-4 shadow-2xl flex flex-col gap-3 relative `} > {/* Action buttons in top-right corner */} <div className="absolute top-2 right-2 flex gap-2"> <BookmarkButton bookmarkData={{ itemType: 'bill', itemId: `${bill.session}-${bill.number}`, title: `Bill ${bill.number}`, subtitle: String(title), url: `/${locale}/bills/${bill.session}/${bill.number}`, metadata: { session: bill.session, bill_type: billType, status: status, sponsor: bill.sponsor?.name, party: bill.sponsor?.party, is_government_bill: bill.is_government_bill, }, }} size="sm" /> <ShareButton url={`/${locale}/bills/${bill.session}/${bill.number}`} title={`Bill ${bill.number} - ${String(title)}`} description={String(status)} size="sm" /> </div> <div className="text-sm font-semibold text-white line-clamp-3 pr-20"> {bill.number}: {String(title)} </div> {bill.sponsor && ( <div className="text-xs text-white/90 pr-12"> {String(bill.sponsor.name)} ({String(bill.sponsor.party)}) </div> )} <div className="text-xs text-white/80 pr-12">{String(status)}</div> {/* Activity indicators */} <div className="flex gap-2 text-xs text-white/90 pr-12"> {bill.hansardDebatesAggregate && bill.hansardDebatesAggregate.count > 0 && ( <span>💬 {bill.hansardDebatesAggregate.count}</span> )} {bill.votesAggregate && bill.votesAggregate.count > 0 && ( <span>🗳️ {bill.votesAggregate.count}</span> )} {bill.referredTo && bill.referredTo.length > 0 && ( <span>👥 Committee</span> )} </div> {/* Sponsor Party Logo - Left aligned */} {bill.sponsor?.party && ( <div className="flex items-center"> <PartyLogo party={bill.sponsor.party} size="sm" linkTo={undefined} /> </div> )} {/* Button to bill page */} <Link href={`/bills/${bill.session}/${bill.number}` as any} className="mt-2 px-3 py-2 bg-white/20 hover:bg-white/30 text-white text-xs font-semibold rounded-md transition-colors text-center" > View Details → </Link> </div> </div> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server