UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

1 lines 12.2 kB
{"version":3,"sources":["../src/components/ProficiencyChart/ProficiencyChart.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\nimport type {\n ProficiencyCounters,\n ProficiencyChartProps,\n SliceData,\n PieChartProps,\n} from './interfaces';\n\nexport type { ProficiencyCounters, ProficiencyChartProps } from './interfaces';\n\n/**\n * Build slices from counters\n */\nconst buildSlices = (\n counters: ProficiencyCounters,\n total: number\n): SliceData[] => {\n if (total === 0) return [];\n\n return [\n {\n key: 'attentionPoint',\n label: 'Ponto de atenção',\n value: counters.attentionPoint,\n percentage: (counters.attentionPoint / total) * 100,\n colorClass: 'bg-error-600',\n fillColor: '#dc2626', // error-600\n },\n {\n key: 'belowAverage',\n label: 'Abaixo da média',\n value: counters.belowAverage,\n percentage: (counters.belowAverage / total) * 100,\n colorClass: 'bg-warning-400',\n fillColor: '#fbbf24', // warning-400\n },\n {\n key: 'aboveAverage',\n label: 'Acima da média',\n value: counters.aboveAverage,\n percentage: (counters.aboveAverage / total) * 100,\n colorClass: 'bg-success-400',\n fillColor: '#4ade80', // success-400\n },\n {\n key: 'highlight',\n label: 'Destaque da turma',\n value: counters.highlight,\n percentage: (counters.highlight / total) * 100,\n colorClass: 'bg-success-700',\n fillColor: '#15803d', // success-700\n },\n ];\n};\n\n/** SVG viewBox size */\nconst CHART_SIZE = 180;\n\n/**\n * Simple pie chart sub-component\n */\nconst PieChart = ({\n slices,\n hoveredSlice,\n onSliceHover,\n}: Readonly<PieChartProps>) => {\n const total = slices.reduce((sum, s) => sum + s.value, 0);\n if (total === 0) {\n return (\n <div className=\"flex items-center justify-center w-[180px] h-[180px] bg-background-50 rounded-full\">\n <Text size=\"sm\" className=\"text-text-500\">\n Sem dados\n </Text>\n </div>\n );\n }\n\n const cx = CHART_SIZE / 2;\n const cy = CHART_SIZE / 2;\n const radius = CHART_SIZE / 2 - 5;\n\n let cumulativePercent = 0;\n\n const paths = slices.map((slice) => {\n const percent = slice.percentage;\n const startPercent = cumulativePercent;\n cumulativePercent += percent;\n\n if (percent === 0) return null;\n\n const startAngle = (startPercent / 100) * 360 - 90;\n const endAngle = ((startPercent + percent) / 100) * 360 - 90;\n\n const startRad = (startAngle * Math.PI) / 180;\n const endRad = (endAngle * Math.PI) / 180;\n\n const x1 = cx + radius * Math.cos(startRad);\n const y1 = cy + radius * Math.sin(startRad);\n const x2 = cx + radius * Math.cos(endRad);\n const y2 = cy + radius * Math.sin(endRad);\n\n const largeArc = percent > 50 ? 1 : 0;\n\n // Handle full circle case\n const d =\n percent >= 100\n ? `M ${cx - radius} ${cy} A ${radius} ${radius} 0 1 1 ${cx + radius} ${cy} A ${radius} ${radius} 0 1 1 ${cx - radius} ${cy}`\n : `M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;\n\n // Calculate label position (center of slice)\n const midAngle = ((startPercent + percent / 2) / 100) * 360 - 90;\n const midRad = (midAngle * Math.PI) / 180;\n const labelRadius = radius * 0.65;\n const labelX = cx + labelRadius * Math.cos(midRad);\n const labelY = cy + labelRadius * Math.sin(midRad);\n\n const isOtherHovered = hoveredSlice && hoveredSlice !== slice.key;\n\n return (\n <g key={slice.key}>\n <path\n d={d}\n className=\"cursor-pointer transition-opacity\"\n style={{ fill: slice.fillColor, opacity: isOtherHovered ? 0.3 : 1 }}\n onMouseEnter={() => onSliceHover(slice.key)}\n onMouseLeave={() => onSliceHover(null)}\n />\n {/* Label inside slice (only if percentage > 8%) */}\n {percent >= 8 && (\n <text\n x={labelX}\n y={labelY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-white text-xs font-semibold pointer-events-none\"\n style={{\n textShadow: '0 1px 2px rgba(0,0,0,0.3)',\n opacity: isOtherHovered ? 0.3 : 1,\n }}\n >\n {slice.percentage.toFixed(0)}%\n </text>\n )}\n </g>\n );\n });\n\n return (\n <svg\n viewBox={`0 0 ${CHART_SIZE} ${CHART_SIZE}`}\n className=\"w-[180px] h-[180px]\"\n >\n {paths}\n </svg>\n );\n};\n\n/**\n * ProficiencyChart - displays a pie chart showing the distribution of student proficiency levels.\n *\n * @example\n * ```tsx\n * <ProficiencyChart\n * counters={{\n * highlight: 10,\n * aboveAverage: 25,\n * belowAverage: 15,\n * attentionPoint: 5,\n * }}\n * totalStudents={55}\n * />\n * ```\n */\nexport const ProficiencyChart = ({\n counters,\n totalStudents,\n className,\n ...props\n}: Readonly<ProficiencyChartProps>) => {\n const [hoveredSlice, setHoveredSlice] = useState<string | null>(null);\n\n const total = totalStudents ?? 0;\n\n const slices = useMemo(() => {\n if (!counters) return [];\n return buildSlices(counters, total);\n }, [counters, total]);\n\n if (!counters || total === 0) {\n return (\n <div\n className={cn(\n 'flex items-center justify-center h-[200px] bg-background border border-border-50 rounded-xl',\n className\n )}\n {...props}\n >\n <Text size=\"sm\" className=\"text-text-500\">\n Sem dados de proficiência disponíveis\n </Text>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'w-full bg-background border border-border-50 rounded-xl p-5',\n className\n )}\n {...props}\n >\n <Text as=\"h3\" size=\"md\" weight=\"semibold\" className=\"text-text-950 mb-4\">\n Proficiência por quantidade de estudante\n </Text>\n\n <div className=\"flex items-center justify-between gap-6\">\n {/* Legend */}\n <div className=\"flex flex-col gap-3\">\n {slices.map((slice) => (\n <div\n key={slice.key}\n className={cn(\n 'flex items-center gap-3 cursor-pointer transition-opacity',\n hoveredSlice && hoveredSlice !== slice.key && 'opacity-50'\n )}\n onMouseEnter={() => setHoveredSlice(slice.key)}\n onMouseLeave={() => setHoveredSlice(null)}\n >\n <span\n className={cn(\n 'w-3 h-3 rounded-full shrink-0',\n slice.colorClass\n )}\n />\n <div className=\"flex flex-col\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-950\">\n {slice.label}\n </Text>\n <Text size=\"xs\" className=\"text-text-500\">\n {slice.value.toLocaleString('pt-BR')}{' '}\n {slice.value === 1 ? 'aluno' : 'alunos'} (\n {slice.percentage.toFixed(1)}%)\n </Text>\n </div>\n </div>\n ))}\n\n {/* Total */}\n <div className=\"pt-2 border-t border-border-100\">\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-950\">\n Total: {total.toLocaleString('pt-BR')}{' '}\n {total === 1 ? 'aluno' : 'alunos'}\n </Text>\n </div>\n </div>\n\n {/* Pie Chart */}\n <div className=\"shrink-0\">\n <PieChart\n slices={slices}\n hoveredSlice={hoveredSlice}\n onSliceHover={setHoveredSlice}\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ProficiencyChart;\n"],"mappings":"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","names":[]}