WIP: Dashboard redesign — design system overhaul and component updates
Frontend redesign in progress: updated styles, layout, and components across all pages to align with new design system. Includes Jira API compliance specs, property tests, and load test script.
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
// 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 (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16 }}>
|
||||
<div>
|
||||
<h2 style={{
|
||||
fontFamily: 'var(--font-mono)', fontSize: 24, fontWeight: 700,
|
||||
color: COLORS.green, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
textShadow: '0 0 16px rgba(16,185,129,0.25)',
|
||||
margin: '0 0 4px 0',
|
||||
}}>
|
||||
{title}
|
||||
</h2>
|
||||
{meta && (
|
||||
<div style={{ fontSize: 12, color: 'var(--fg-muted)', fontFamily: 'var(--font-mono)' }}>
|
||||
{meta}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, flexShrink: 0, alignItems: 'center' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
background: hover && !disabled ? v.bgHover : v.bgRest,
|
||||
border: `1px solid ${hover && !disabled && variant === 'neutral' ? 'rgba(14,165,233,0.55)' : v.bd}`,
|
||||
color: disabled ? 'var(--fg-disabled)' : v.fg,
|
||||
padding: '8px 14px', borderRadius: 6,
|
||||
fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600,
|
||||
textTransform: 'uppercase', letterSpacing: '0.05em',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
transition: 'all 150ms cubic-bezier(0.4,0,0.2,1)',
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{icon}{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<div
|
||||
onMouseEnter={() => 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 && (
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
|
||||
color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
paddingBottom: 8,
|
||||
borderBottom: '1px solid rgba(255,255,255,0.04)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
}}>
|
||||
<span>{label}</span>
|
||||
{labelExtra}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Pill tab (Ivanti / Atlas) ───────────────────────────────── */
|
||||
function PillTab({ active, color = COLORS.sky, onClick, children }) {
|
||||
const [hover, setHover] = useRPTState(false);
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
||||
style={{
|
||||
padding: '6px 12px',
|
||||
fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
|
||||
textTransform: 'uppercase', letterSpacing: '0.05em',
|
||||
cursor: 'pointer', borderRadius: 4,
|
||||
border: `1px solid ${active ? color : (hover ? 'rgba(255,255,255,0.10)' : 'transparent')}`,
|
||||
background: active ? `${color}26` : 'transparent',
|
||||
color: active ? color : (hover ? '#94A3B8' : 'var(--fg-muted)'),
|
||||
transition: 'all 120ms',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Filter chip (active filter pin in the toolbar) ──────────── */
|
||||
function FilterChip({ color = COLORS.amber, onClear, children }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClear}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
padding: '6px 12px',
|
||||
background: `${color}14`,
|
||||
border: `1px solid ${color}4D`,
|
||||
borderRadius: 6,
|
||||
color, cursor: 'pointer',
|
||||
fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600,
|
||||
letterSpacing: '0.05em',
|
||||
}}
|
||||
>
|
||||
<RptIcon.Filter size={11} />
|
||||
{children}
|
||||
<span style={{ marginLeft: 2, opacity: 0.7 }}>×</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'flex-start', gap: 8,
|
||||
padding: '10px 14px', background: t.bg, border: `1px solid ${t.bd}`,
|
||||
borderRadius: 8,
|
||||
}}>
|
||||
<RptIcon.AlertCircle size={15} style={{ color: t.icon, flexShrink: 0, marginTop: 1 }} />
|
||||
<span style={{ fontSize: 12, color: t.fg, fontFamily: 'var(--font-mono)' }}>{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Toolbar label (small mono uppercase, used inside findings panel) ── */
|
||||
function ToolbarLabel({ children, accent = COLORS.sky, count }) {
|
||||
return (
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 700,
|
||||
color: accent, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
}}>
|
||||
{children}
|
||||
{count != null && (
|
||||
<span style={{ marginLeft: 10, color: '#334155', fontWeight: 400 }}>
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<span style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
|
||||
color: v.text, letterSpacing: '0.04em',
|
||||
}}>
|
||||
<span style={{
|
||||
width: 7, height: 7, borderRadius: '50%', background: v.c,
|
||||
boxShadow: `0 0 6px ${v.c}99`,
|
||||
}} />
|
||||
{level}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<span style={{
|
||||
padding: '2px 8px', borderRadius: 999,
|
||||
background: v.bg, color: v.c,
|
||||
fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 10,
|
||||
letterSpacing: '0.05em',
|
||||
}}>
|
||||
{status.replace('_', ' ')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<span style={{
|
||||
padding: '2px 8px', borderRadius: 4,
|
||||
background: v.bg, color: v.c, border: `1px solid ${v.c}55`,
|
||||
fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 10,
|
||||
letterSpacing: '0.05em',
|
||||
}}>
|
||||
{state}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 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 (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<svg width={size} height={size}>
|
||||
{arcs.map((a, i) => (
|
||||
<path key={i} d={a.d} fill={a.color} stroke="rgba(15,23,42,0.95)" strokeWidth="1" />
|
||||
))}
|
||||
</svg>
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0,
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
||||
pointerEvents: 'none',
|
||||
}}>
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-mono)', fontSize: 22, fontWeight: 700,
|
||||
color: 'var(--fg-1)', lineHeight: 1,
|
||||
}}>{centerValue}</div>
|
||||
{centerLabel && (
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-mono)', fontSize: 9, fontWeight: 600,
|
||||
color: 'var(--fg-disabled)', textTransform: 'uppercase', letterSpacing: '0.12em',
|
||||
marginTop: 4,
|
||||
}}>
|
||||
{centerLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Legend */}
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px 10px', justifyContent: 'center', maxWidth: size + 32 }}>
|
||||
{segments.map((s) => (
|
||||
<div key={s.label} style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 4,
|
||||
fontFamily: 'var(--font-mono)', fontSize: 9.5, color: 'var(--fg-muted)',
|
||||
letterSpacing: '0.04em',
|
||||
}}>
|
||||
<span style={{ width: 8, height: 8, borderRadius: 2, background: s.color, flexShrink: 0 }} />
|
||||
<span>{s.label} <span style={{ color: 'var(--fg-disabled)' }}>{s.value}</span></span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Inline lucide icons (Reporting subset) ──────────────────── */
|
||||
const _ic = (path) => ({ size = 14, strokeWidth = 1.75, ...rest }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"
|
||||
style={{ flexShrink: 0 }} {...rest}>{path}</svg>
|
||||
);
|
||||
const RptIcon = {
|
||||
Refresh: _ic(<><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></>),
|
||||
PieChart: _ic(<><path d="M21.21 15.89A10 10 0 1 1 8 2.83"/><path d="M22 12A10 10 0 0 0 12 2v10z"/></>),
|
||||
Filter: _ic(<><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></>),
|
||||
Download: _ic(<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>),
|
||||
ChevronD: _ic(<><polyline points="6 9 12 15 18 9"/></>),
|
||||
ChevronUp: _ic(<><polyline points="18 15 12 9 6 15"/></>),
|
||||
ChevronUpDn:_ic(<><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></>),
|
||||
ListTodo: _ic(<><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></>),
|
||||
Settings: _ic(<><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h0a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>),
|
||||
Eye: _ic(<><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></>),
|
||||
EyeOff: _ic(<><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" y1="2" x2="22" y2="22"/></>),
|
||||
AlertCircle:_ic(<><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></>),
|
||||
AlertTri: _ic(<><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></>),
|
||||
Atlas: _ic(<><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10"/><path d="M12 2a15.3 15.3 0 0 0-4 10 15.3 15.3 0 0 0 4 10"/></>),
|
||||
Search: _ic(<><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></>),
|
||||
Square: _ic(<><rect x="3" y="3" width="18" height="18" rx="2"/></>),
|
||||
CheckSq: _ic(<><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></>),
|
||||
Loader: _ic(<><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/></>),
|
||||
TrendUp: _ic(<><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></>),
|
||||
};
|
||||
|
||||
window.RPT = {
|
||||
COLORS,
|
||||
PageHeader, RptButton, KbCard, PillTab, FilterChip, StatusBanner,
|
||||
ToolbarLabel, SeverityDot, SlaPill, WorkflowBadge, DonutSample,
|
||||
RptIcon,
|
||||
};
|
||||
Reference in New Issue
Block a user