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 (
);
}
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}
);
}