Self-hosted US Congress monitoring platform with AI policy briefs, bill/member/topic follows, ntfy + RSS + email notifications, alignment scoring, collections, and draft-letter generator. Authored by: Jack Levy
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import { clsx, type ClassValue } from "clsx";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
export function formatDate(date?: string | null): string {
|
|
if (!date) return "—";
|
|
return new Date(date).toLocaleDateString("en-US", {
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
});
|
|
}
|
|
|
|
export function billLabel(billType: string, billNumber: number): string {
|
|
const labels: Record<string, string> = {
|
|
hr: "H.R.",
|
|
s: "S.",
|
|
hjres: "H.J.Res.",
|
|
sjres: "S.J.Res.",
|
|
hconres: "H.Con.Res.",
|
|
sconres: "S.Con.Res.",
|
|
hres: "H.Res.",
|
|
sres: "S.Res.",
|
|
};
|
|
return `${labels[billType?.toLowerCase()] ?? billType?.toUpperCase()} ${billNumber}`;
|
|
}
|
|
|
|
export function partyColor(party?: string): string {
|
|
if (!party) return "text-muted-foreground";
|
|
const p = party.toLowerCase();
|
|
if (p.includes("democrat") || p === "d") return "text-blue-500";
|
|
if (p.includes("republican") || p === "r") return "text-red-500";
|
|
return "text-yellow-500";
|
|
}
|
|
|
|
export function partyBadgeColor(party?: string): string {
|
|
if (!party) return "bg-muted text-muted-foreground";
|
|
const p = party.toLowerCase();
|
|
if (p.includes("democrat") || p === "d") return "bg-blue-600 text-white";
|
|
if (p.includes("republican") || p === "r") return "bg-red-600 text-white";
|
|
return "bg-slate-500 text-white";
|
|
}
|
|
|
|
export function congressLabel(congress: number): string {
|
|
const lastTwo = congress % 100;
|
|
if (lastTwo >= 11 && lastTwo <= 13) return `${congress}th Congress`;
|
|
const suffixes: Record<number, string> = { 1: "st", 2: "nd", 3: "rd" };
|
|
return `${congress}${suffixes[congress % 10] ?? "th"} Congress`;
|
|
}
|
|
|
|
export function chamberBadgeColor(chamber?: string): string {
|
|
if (!chamber) return "bg-muted text-muted-foreground";
|
|
const c = chamber.toLowerCase();
|
|
if (c === "senate") return "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400";
|
|
if (c.startsWith("house")) return "bg-slate-100 text-slate-600 dark:bg-slate-700/50 dark:text-slate-300";
|
|
return "bg-muted text-muted-foreground";
|
|
}
|
|
|
|
export function trendColor(score?: number): string {
|
|
if (!score) return "text-muted-foreground";
|
|
if (score >= 70) return "text-red-500";
|
|
if (score >= 40) return "text-yellow-500";
|
|
return "text-green-500";
|
|
}
|