import * as React from "react"; import Link from "next/link"; import { ChevronLeft, ChevronRight, Search } from "lucide-react"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Skeleton, TableSkeleton } from "@/components/ui/skeleton"; import { formatDate, timeAgo } from "@/lib/format"; import { RANGES, rangeLabel, type Range } from "@/lib/queries/admin"; /** Dense table cell for numbers: right-aligned, tabular, mono. */ export function Num({ children, className, muted }: { children: React.ReactNode; className?: string; muted?: boolean }) { return {children}; } export const numCell = "text-right tabular font-mono text-[12.5px] whitespace-nowrap"; export const monoCell = "font-mono text-[12.5px] whitespace-nowrap"; export function Mono({ children, className, title }: { children: React.ReactNode; className?: string; title?: string }) { return ( {children} ); } /** Upstream provider name. Admin-only by construction: this component only lives in components/admin. */ export function ProviderBadge({ id, className }: { id: string | null | undefined; className?: string }) { if (!id) return —; return ( {id} ); } export function NetworkBadge({ network }: { network: string | null | undefined }) { if (!network) return —; return {network}; } /** Single-plan platform: `unlimited` is the only valid value; anything else is a legacy row not yet migrated. */ export function PlanBadge({ plan }: { plan: string }) { const v = plan === "unlimited" ? "solid" : "outline"; return ( {plan} ); } export function BoolBadge({ value, yes = "yes", no = "no", invert }: { value: boolean; yes?: string; no?: string; invert?: boolean }) { const good = invert ? !value : value; return ( {value ? yes : no} ); } export function DateCell({ value, relative = true }: { value: Date | string | null | undefined; relative?: boolean }) { if (!value) return —; return ( ); } export function Panel({ title, description, right, children, className, bodyClassName, flush }: { title?: React.ReactNode; description?: React.ReactNode; right?: React.ReactNode; children: React.ReactNode; className?: string; bodyClassName?: string; flush?: boolean }) { return (
{title ? (

{title}

{description ?

{description}

: null}
{right ?
{right}
: null}
) : null}
{children}
); } /** Two-column key/value grid for detail pages. */ export function KV({ items, cols = 2, className }: { items: Array<{ label: React.ReactNode; value: React.ReactNode; mono?: boolean; span?: boolean }>; cols?: 1 | 2 | 3; className?: string }) { return (
{items.map((it, i) => (
{it.label}
{it.value ?? —}
))}
); } /** Range selector (24h / 7d / 30d) rendered as links; preserves other query params. */ export function RangeTabs({ current, basePath, params }: { current: Range; basePath: string; params?: Record }) { return (
{RANGES.map((r) => { const sp = new URLSearchParams(); for (const [k, v] of Object.entries(params ?? {})) if (v) sp.set(k, v); sp.set("range", r); const active = r === current; return ( {r} ); })}
); } export function Pagination({ page, pages, total, basePath, params, pageSize }: { page: number; pages: number; total: number; basePath: string; params?: Record; pageSize: number }) { const href = (p: number) => { const sp = new URLSearchParams(); for (const [k, v] of Object.entries(params ?? {})) if (v) sp.set(k, v); if (p > 1) sp.set("page", String(p)); const q = sp.toString(); return q ? `${basePath}?${q}` : basePath; }; const from = total === 0 ? 0 : (page - 1) * pageSize + 1; const to = Math.min(total, page * pageSize); return (
{from.toLocaleString()}–{to.toLocaleString()} of {total.toLocaleString()}
{page} / {pages}
); } /** Plain GET search form — no client JS needed. Extra hidden params are preserved. */ export function SearchForm({ action, q, placeholder, hidden, children }: { action: string; q?: string; placeholder?: string; hidden?: Record; children?: React.ReactNode }) { return (
{Object.entries(hidden ?? {}).map(([k, v]) => (v ? : null))}
{children} {q ? ( ) : null}
); } export function MarginText({ value, pct }: { value: number; pct: number | null }) { const neg = value < 0; return ( {neg ? "−" : ""}${Math.abs(value).toFixed(value !== 0 && Math.abs(value) < 0.01 ? 4 : 2)} {pct !== null ? ({pct.toFixed(1)}%) : null} ); } export function AdminLoading({ stats = 4, rows = 8 }: { stats?: number; rows?: number }) { return (
{stats ? (
{Array.from({ length: stats }).map((_, i) => (
))}
) : null}
); }