// ReportPrimitives.jsx — Reporting-specific UI vocabulary. // All inline styles + tokens from ../../colors_and_type.css. // Mirrors the live Reporting page (frontend/src/components/pages/ReportingPage.js) // after the Knowledge-Base alignment pass. const { useState: useRPTState } = React; /* ───────────────────────────────────────────────────────────────── COLOR ROLE MAP (Reporting) ────────────────────────────────────────────────────────────────── Sky-blue (#0EA5E9) → primary surface accent (panel borders, tab pill active, donut highlight, table header text, neutral secondary buttons) Green (#10B981) → page identity (header glow + primary Sync button) Amber (#F59E0B) → filter-active indicator, anomaly callout Red (#EF4444) → error / overdue Slate stack → muted text + dividers (#475569 → #334155) ──────────────────────────────────────────────────────────────── */ const COLORS = { sky: '#0EA5E9', skySoft: '#7DD3FC', green: '#10B981', amber: '#F59E0B', red: '#EF4444', redSoft: '#FCA5A5', }; /* ── Page header ───────────────────────────────────────────────── Big mono uppercase title in green w/ glow + count subtitle. Right side: neutral icon-tinted secondaries + tinted-fill primary. Lifted from the existing Knowledge Base page header pattern. */ function PageHeader({ title = 'Reporting', meta, children }) { return (

{title}

{meta && (
{meta}
)}
{children}
); } /* ── Buttons ───────────────────────────────────────────────────── THREE variants documented for Reporting: • primary — green tinted-fill (lone primary action: Sync) • neutral — sky outlined transparent (Atlas, refresh, etc.) • subtle — sky tinted-fill (Export, Queue, Column manager) */ function RptButton({ variant = 'neutral', icon, children, disabled, ...rest }) { const [hover, setHover] = useRPTState(false); const v = { primary: { bgRest: 'rgba(16,185,129,0.18)', bgHover: 'rgba(16,185,129,0.26)', bd: COLORS.green, fg: COLORS.green, }, neutral: { bgRest: 'transparent', bgHover: 'rgba(14,165,233,0.06)', bd: 'rgba(14,165,233,0.25)', fg: COLORS.sky, }, subtle: { bgRest: 'rgba(14,165,233,0.08)', bgHover: 'rgba(14,165,233,0.16)', bd: 'rgba(14,165,233,0.35)', fg: COLORS.sky, }, danger: { bgRest: 'rgba(239,68,68,0.08)', bgHover: 'rgba(239,68,68,0.16)', bd: 'rgba(239,68,68,0.30)', fg: COLORS.red, }, }[variant]; return ( ); } /* ── KB-style card (sky) — used for donuts + findings panel ──── */ function KbCard({ children, padding = 16, label, labelExtra, hover = true, style }) { const [h, setH] = useRPTState(false); return (
hover && setH(true)} onMouseLeave={() => setH(false)} style={{ background: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)', border: `1.5px solid ${h ? 'rgba(14,165,233,0.35)' : 'rgba(14,165,233,0.12)'}`, borderRadius: 8, padding, display: 'flex', flexDirection: 'column', gap: 10, transition: 'border-color 150ms cubic-bezier(0.4,0,0.2,1)', ...style, }}> {label && (
{label} {labelExtra}
)} {children}
); } /* ── Pill tab (Ivanti / Atlas) ───────────────────────────────── */ function PillTab({ active, color = COLORS.sky, onClick, children }) { const [hover, setHover] = useRPTState(false); return ( ); } /* ── Filter chip (active filter pin in the toolbar) ──────────── */ function FilterChip({ color = COLORS.amber, onClear, children }) { return ( ); } /* ── Status banner (error / Atlas error / sync error) ────────── */ function StatusBanner({ tone = 'error', children }) { const tones = { error: { bg: 'rgba(239,68,68,0.08)', bd: 'rgba(239,68,68,0.25)', fg: COLORS.redSoft, icon: COLORS.red }, warn: { bg: 'rgba(245,158,11,0.10)', bd: 'rgba(245,158,11,0.28)', fg: '#FCD34D', icon: COLORS.amber }, info: { bg: 'rgba(14,165,233,0.08)', bd: 'rgba(14,165,233,0.25)', fg: COLORS.skySoft, icon: COLORS.sky }, }; const t = tones[tone]; return (
{children}
); } /* ── Toolbar label (small mono uppercase, used inside findings panel) ── */ function ToolbarLabel({ children, accent = COLORS.sky, count }) { return (
{children} {count != null && ( {count} )}
); } /* ── Severity dot (used in table rows) ───────────────────────── */ function SeverityDot({ level }) { const map = { Critical: { c: COLORS.red, text: '#FCA5A5' }, High: { c: COLORS.amber, text: '#FCD34D' }, Medium: { c: COLORS.sky, text: '#7DD3FC' }, Low: { c: COLORS.green, text: '#6EE7B7' }, Info: { c: '#94A3B8', text: '#CBD5E1' }, }; const v = map[level] || map.Info; return ( {level} ); } /* ── SLA pill (table cell) ───────────────────────────────────── */ function SlaPill({ status }) { const map = { OVERDUE: { c: COLORS.red, bg: 'rgba(239,68,68,0.16)' }, AT_RISK: { c: COLORS.amber, bg: 'rgba(245,158,11,0.16)' }, WITHIN_SLA: { c: COLORS.green, bg: 'rgba(16,185,129,0.16)' }, }; const v = map[status] || map.WITHIN_SLA; return ( {status.replace('_', ' ')} ); } /* ── Workflow badge (table cell) ─────────────────────────────── */ function WorkflowBadge({ state }) { const map = { OPEN: { c: COLORS.sky, bg: 'rgba(14,165,233,0.14)' }, FP: { c: COLORS.amber, bg: 'rgba(245,158,11,0.14)' }, EXC: { c: '#A78BFA', bg: 'rgba(167,139,250,0.14)' }, REMEDIATED:{ c: COLORS.green, bg: 'rgba(16,185,129,0.14)' }, ARCHIVED: { c: '#94A3B8', bg: 'rgba(148,163,184,0.14)' }, }; const v = map[state] || { c: 'var(--fg-muted)', bg: 'rgba(148,163,184,0.10)' }; return ( {state} ); } /* ── Donut placeholder — semantic stand-in for the real recharts donut ── */ function DonutSample({ size = 130, segments, centerLabel, centerValue }) { // segments: [{ label, value, color }] const total = segments.reduce((s, x) => s + x.value, 0); const cx = size / 2, cy = size / 2; const outerR = size / 2 - 4, innerR = outerR - 16; let angle = -90; const arcs = segments.map((seg) => { const sweep = (seg.value / total) * 360; const a0 = (angle * Math.PI) / 180; const a1 = ((angle + sweep) * Math.PI) / 180; const large = sweep > 180 ? 1 : 0; const x0 = cx + outerR * Math.cos(a0), y0 = cy + outerR * Math.sin(a0); const x1 = cx + outerR * Math.cos(a1), y1 = cy + outerR * Math.sin(a1); const xi1 = cx + innerR * Math.cos(a1), yi1 = cy + innerR * Math.sin(a1); const xi0 = cx + innerR * Math.cos(a0), yi0 = cy + innerR * Math.sin(a0); const d = `M ${x0} ${y0} A ${outerR} ${outerR} 0 ${large} 1 ${x1} ${y1} L ${xi1} ${yi1} A ${innerR} ${innerR} 0 ${large} 0 ${xi0} ${yi0} Z`; angle += sweep; return { d, color: seg.color }; }); return (
{arcs.map((a, i) => ( ))}
{centerValue}
{centerLabel && (
{centerLabel}
)}
{/* Legend */}
{segments.map((s) => (
{s.label} {s.value}
))}
); } /* ── Inline lucide icons (Reporting subset) ──────────────────── */ const _ic = (path) => ({ size = 14, strokeWidth = 1.75, ...rest }) => ( {path} ); const RptIcon = { Refresh: _ic(<>), PieChart: _ic(<>), Filter: _ic(<>), Download: _ic(<>), ChevronD: _ic(<>), ChevronUp: _ic(<>), ChevronUpDn:_ic(<>), ListTodo: _ic(<>), Settings: _ic(<>), Eye: _ic(<>), EyeOff: _ic(<>), AlertCircle:_ic(<>), AlertTri: _ic(<>), Atlas: _ic(<>), Search: _ic(<>), Square: _ic(<>), CheckSq: _ic(<>), Loader: _ic(<>), TrendUp: _ic(<>), }; window.RPT = { COLORS, PageHeader, RptButton, KbCard, PillTab, FilterChip, StatusBanner, ToolbarLabel, SeverityDot, SlaPill, WorkflowBadge, DonutSample, RptIcon, };