import Link from 'next/link';
import type { ReactNode } from 'react';
import { cn } from '@/lib/format';
export const ADMIN_NAV = [
{ href: '/admin', label: 'Overview' },
{ href: '/admin/connectors', label: 'Connectors' },
{ href: '/admin/coverage', label: 'Coverage' },
{ href: '/admin/data-quality', label: 'Data quality' },
{ href: '/admin/costs', label: 'Costs' },
{ href: '/admin/taxonomy', label: 'Taxonomy' },
{ href: '/admin/audit', label: 'Audit log' },
{ href: '/admin/events', label: 'Events' },
];
export function AdminShell({ children, title, subtitle, actions, current }: { children: ReactNode; title: string; subtitle?: ReactNode; actions?: ReactNode; current: string }) {
return (
{title}
{subtitle ?
{subtitle}
: null}
{actions}
{children}
);
}
export function StatusPill({ status }: { status: string | null | undefined }) {
const s = status ?? 'unknown';
const tone: Record = { maintenance: 'bg-index-bg text-index', completed: 'bg-gain-bg text-gain', healthy: 'bg-gain-bg text-gain', active: 'bg-gain-bg text-gain', success: 'bg-gain-bg text-gain', valid: 'bg-gain-bg text-gain', degraded: 'bg-alert-bg text-alert', partial: 'bg-alert-bg text-alert', paused: 'bg-alert-bg text-alert', flagged: 'bg-alert-bg text-alert', pending: 'bg-alert-bg text-alert', running: 'bg-index-bg text-index', failing: 'bg-loss-bg text-loss', failed: 'bg-loss-bg text-loss', excluded: 'bg-loss-bg text-loss', disabled: 'bg-inset text-muted', unknown: 'bg-inset text-muted' };
return {s};
}
export function ActionButton({ label, tone = 'neutral', small = true }: { label: string; tone?: 'neutral' | 'danger' | 'primary'; small?: boolean }) {
return (
);
}
export function Kpi({ label, value, sub, tone }: { label: string; value: ReactNode; sub?: ReactNode; tone?: 'gain' | 'loss' | 'alert' }) {
return (
{label}
{value}
{sub ?
{sub}
: null}
);
}
/** Single-series bar chart (one hue, hover tooltips via , no legend needed). */
export function Bars({ points, format, className }: { points: Array<{ label: string; value: number }>; format?: (v: number) => string; className?: string }) {
if (!points.length) return No data in this window.
;
const max = Math.max(...points.map((p) => p.value), 0) || 1;
const w = 600;
const h = 120;
const gap = 2;
const bw = Math.max(2, (w - gap * points.length) / points.length);
const fmt = format ?? ((v: number) => String(Math.round(v * 100) / 100));
return (
);
}
export function fmtTs(v: unknown): string {
if (!v) return '—';
const d = v instanceof Date ? v : new Date(String(v));
if (Number.isNaN(d.getTime())) return String(v);
return d.toISOString().replace('T', ' ').slice(0, 16) + 'Z';
}
export function n(v: unknown, digits = 0): string {
const x = Number(v);
return Number.isFinite(x) ? x.toLocaleString('en-US', { maximumFractionDigits: digits }) : '—';
}