import React, { useMemo, useState } from 'react'; import Chart from 'react-apexcharts'; export default function FeatureStatusDonut() { const [activeCount] = useState(tpsaAdmin.feature_status.active); const [inactiveCount] = useState(tpsaAdmin.feature_status.inactive); const total = activeCount + inactiveCount; const [focusIndex, setFocusIndex] = useState(null); const [hoverIndex, setHoverIndex] = useState(null); const [centerMode, setCenterMode] = useState('total'); const series = useMemo( () => [activeCount, inactiveCount], [activeCount, inactiveCount] ); const labels = useMemo(() => ['Active', 'Inactive'], []); const displayIndex = hoverIndex ?? focusIndex; const centerTitle = useMemo(() => { if (displayIndex === 0) return 'Active'; if (displayIndex === 1) return 'Inactive'; return centerMode === 'total' ? 'Total' : 'Active Rate'; }, [displayIndex, centerMode]); const centerValue = useMemo(() => { if (displayIndex === 0) return activeCount; if (displayIndex === 1) return inactiveCount; if (centerMode === 'total') return total; return `${Math.round((activeCount / total) * 100)}%`; }, [displayIndex, activeCount, inactiveCount, total, centerMode]); const centerSub = useMemo(() => { if (displayIndex !== null) return 'Click slice to focus'; return centerMode === 'total' ? 'Features in plugin' : 'Active / Total'; }, [displayIndex, centerMode]); const options = useMemo( () => ({ chart: { type: 'donut', height: 320, toolbar: { show: false }, events: { dataPointMouseEnter: (_e, _ctx, cfg) => setHoverIndex(cfg.dataPointIndex), dataPointMouseLeave: () => setHoverIndex(null), dataPointSelection: (_e, _ctx, cfg) => { const idx = cfg.dataPointIndex; setFocusIndex((p) => (p === idx ? null : idx)); }, legendClick: (_ctx, seriesIndex) => { setFocusIndex((p) => (p === seriesIndex ? null : seriesIndex)); return false; }, }, }, labels, colors: ['#22c55e', '#64748b'], stroke: { width: 5, colors: ['#ffffff'] }, plotOptions: { pie: { donut: { size: '68%' } }, }, // The legend is deliberately off. It duplicated the Active/Inactive/Total // pills already shown in the footer, and — because it sat on the right — // it pushed the donut off-centre while the centre label stayed centred on // the container, so the text overflowed the ring. legend: { show: false }, tooltip: { y: { formatter: (val) => val }, }, dataLabels: { enabled: false }, fill: { opacity: focusIndex === null ? 1 : 0.35 }, // Shrink the donut on narrow screens instead of letting it overflow. responsive: [ { breakpoint: 1200, options: { chart: { height: 280 } }, }, { breakpoint: 782, options: { chart: { height: 240 } }, }, { breakpoint: 480, options: { chart: { height: 200 }, plotOptions: { pie: { donut: { size: '62%' } } }, stroke: { width: 3 }, }, }, ], }), [labels, focusIndex] ); return (
Feature Status
Active vs Inactive modules
{centerTitle}
{centerValue}
{centerSub}
Active: {activeCount} Inactive: {inactiveCount} Total: {total}
); }