UNPKG

analytica-frontend-lib

Version:

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

496 lines (494 loc) 15.6 kB
import { Tooltip } from "./chunk-QBKJAKGY.mjs"; import { bgClassToCssVar, describeArc, polarToCartesian } from "./chunk-REYQHCVW.mjs"; import { Text_default } from "./chunk-IMCIR6TJ.mjs"; import { cn } from "./chunk-53ICLDGS.mjs"; // src/components/TimeChart/TimeChart.tsx import { useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; var TIME_CHART_CATEGORY_KEY = /* @__PURE__ */ ((TIME_CHART_CATEGORY_KEY2) => { TIME_CHART_CATEGORY_KEY2["ACTIVITIES"] = "activities"; TIME_CHART_CATEGORY_KEY2["CONTENT"] = "content"; TIME_CHART_CATEGORY_KEY2["SIMULATIONS"] = "simulations"; TIME_CHART_CATEGORY_KEY2["QUESTIONNAIRES"] = "questionnaires"; TIME_CHART_CATEGORY_KEY2["RECOMMENDED_LESSONS"] = "recommendedLessons"; return TIME_CHART_CATEGORY_KEY2; })(TIME_CHART_CATEGORY_KEY || {}); var STUDENT_CATEGORIES = [ { key: "activities" /* ACTIVITIES */, label: "Atividades", colorClass: "bg-success-700" }, { key: "simulations" /* SIMULATIONS */, label: "Simulados", colorClass: "bg-warning-300" }, { key: "questionnaires" /* QUESTIONNAIRES */, label: "Question\xE1rios", colorClass: "bg-indicator-info" }, { key: "content" /* CONTENT */, label: "Videoaulas", colorClass: "bg-success-300" }, { key: "recommendedLessons" /* RECOMMENDED_LESSONS */, label: "Aulas recomendadas", colorClass: "bg-indicator-positive" } ]; var DEFAULT_CATEGORIES = [ { key: "activities" /* ACTIVITIES */, label: "Atividades", colorClass: "bg-success-700" }, { key: "recommendedLessons" /* RECOMMENDED_LESSONS */, label: "Aulas recomendadas", colorClass: "bg-indicator-positive" } ]; var calculateHourTicks = (maxHours) => { if (maxHours <= 0) return [0]; const niceMax = Math.ceil(maxHours / 4) * 4; const step = niceMax / 4; return [niceMax, step * 3, step * 2, step, 0]; }; var getDayValue = (day, key) => Number(day[key]) || 0; var LegendItem = ({ color, label }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-row items-center gap-2", children: [ /* @__PURE__ */ jsx("div", { className: cn("w-2 h-2 rounded-full", color) }), /* @__PURE__ */ jsx(Text_default, { size: "sm", weight: "medium", className: "text-text-600", children: label }) ] }); var ChartCard = ({ title, categories, children, className, contentGapClassName = "gap-4", ...props }) => /* @__PURE__ */ jsxs( "div", { className: cn( "flex flex-col p-5 bg-background border border-border-50 rounded-xl", contentGapClassName, className ), ...props, children: [ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [ /* @__PURE__ */ jsx( Text_default, { as: "h3", size: "lg", weight: "bold", className: "text-text-950 tracking-[0.2px]", children: title } ), /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-wrap justify-between gap-x-6 gap-y-2", children: categories.map((cat) => /* @__PURE__ */ jsx(LegendItem, { color: cat.colorClass, label: cat.label }, cat.key)) }) ] }), children ] } ); var YAxis = ({ ticks, chartHeight, unitSuffix }) => /* @__PURE__ */ jsx( "div", { className: "flex flex-col justify-between items-end pr-3", style: { height: chartHeight }, "aria-hidden": "true", children: ticks.map((tick, index) => /* @__PURE__ */ jsx( Text_default, { size: "md", weight: "normal", className: "text-text-700", children: tick === 0 ? "0" : `${tick}${unitSuffix}` }, `${tick}-${index}` )) } ); var GridLines = ({ ticks, chartHeight }) => /* @__PURE__ */ jsx( "div", { className: "absolute inset-0 flex flex-col justify-between pointer-events-none", style: { height: chartHeight }, children: ticks.map((tick, index) => /* @__PURE__ */ jsx( "div", { className: "w-full border-t border-dashed border-border-200" }, `grid-${tick}-${index}` )) } ); var StackedBar = ({ day, categories, maxValue, chartHeight, unitSuffix }) => { const nonZeroCategories = categories.filter( (cat) => getDayValue(day, cat.key) > 0 ); const tooltipContent = nonZeroCategories.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: nonZeroCategories.map((cat) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsx( "div", { className: "w-2 h-2 rounded-full shrink-0", style: { background: bgClassToCssVar(cat.colorClass) } } ), /* @__PURE__ */ jsxs(Text_default, { as: "span", size: "xs", weight: "medium", color: "text-white", children: [ cat.label, ": ", getDayValue(day, cat.key), unitSuffix ] }) ] }, cat.key)) }) : null; return /* @__PURE__ */ jsx( Tooltip, { content: tooltipContent ?? "", disabled: !tooltipContent, position: "top", className: "flex-1", contentClassName: "whitespace-normal", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 w-full cursor-pointer group/bar", children: [ /* @__PURE__ */ jsxs( "div", { className: "w-full flex flex-col-reverse items-center justify-start relative", style: { height: chartHeight }, children: [ categories.map((cat) => { const value = getDayValue(day, cat.key); const segmentHeight = maxValue === 0 ? 0 : value / maxValue * chartHeight; if (segmentHeight === 0) return null; const isFirst = cat === nonZeroCategories[0]; const isLast = cat === nonZeroCategories[nonZeroCategories.length - 1]; return /* @__PURE__ */ jsx( "div", { "data-testid": `bar-segment-${day.label}-${cat.key}`, className: cn( "w-9", cat.colorClass, isFirst && "rounded-b", isLast && "rounded-t", isFirst && isLast && "rounded" ), style: { height: `${segmentHeight}px` }, "aria-label": `${cat.label}: ${value}${unitSuffix}` }, cat.key ); }), nonZeroCategories.length > 0 && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-white/50 opacity-0 group-hover/bar:opacity-100 transition-opacity duration-200 rounded-md pointer-events-none z-20" }) ] } ), /* @__PURE__ */ jsx( Text_default, { size: "md", weight: "normal", className: "text-text-700 text-center", "data-testid": `day-label-${day.label}`, children: day.label } ) ] }) } ); }; var PieChart = ({ categories, totals, size }) => { const [hoveredSlice, setHoveredSlice] = useState(null); const grandTotal = categories.reduce( // istanbul ignore next -- defensive: parent always provides all keys (sum, cat) => sum + (totals[cat.key] ?? 0), 0 ); if (grandTotal === 0) { const radius2 = size * 0.4; const center2 = size / 2; return /* @__PURE__ */ jsx( "svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", "data-testid": "pie-chart", children: /* @__PURE__ */ jsx( "circle", { cx: center2, cy: center2, r: radius2, className: "fill-background-300" } ) } ); } const radius = size * 0.4; const center = size / 2; let cumulativeAngle = 0; const slices = categories.map((cat) => { const value = totals[cat.key] ?? 0; const percentage = value / grandTotal * 100; const angle = percentage / 100 * 360; const startAngle = cumulativeAngle; const endAngle = cumulativeAngle + angle; const midAngle = startAngle + angle / 2; cumulativeAngle = endAngle; return { cat, percentage, startAngle, endAngle, midAngle }; }).filter((s) => s.percentage > 0); const hoveredData = hoveredSlice ? slices.find((s) => s.cat.key === hoveredSlice) : null; const tooltipPos = hoveredData ? polarToCartesian(center, center, radius * 0.6, hoveredData.midAngle) : { x: 0, y: 0 }; return /* @__PURE__ */ jsxs("div", { className: "relative inline-block", children: [ /* @__PURE__ */ jsx( "svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", "data-testid": "pie-chart", onMouseLeave: () => setHoveredSlice(null), children: slices.map((slice) => { const path = slice.percentage >= 99.99 ? void 0 : describeArc( center, center, radius, slice.startAngle, slice.endAngle ); const labelPos = polarToCartesian( center, center, radius * 0.6, slice.midAngle ); return /* @__PURE__ */ jsxs( "g", { "data-testid": `pie-slice-${slice.cat.key}`, onMouseEnter: () => setHoveredSlice(slice.cat.key), className: "cursor-pointer", children: [ slice.percentage >= 99.99 ? /* @__PURE__ */ jsx( "circle", { cx: center, cy: center, r: radius, style: { fill: bgClassToCssVar(slice.cat.colorClass) } } ) : /* @__PURE__ */ jsx( "path", { d: path, style: { fill: bgClassToCssVar(slice.cat.colorClass) } } ), hoveredSlice === slice.cat.key && (slice.percentage >= 99.99 ? /* @__PURE__ */ jsx( "circle", { cx: center, cy: center, r: radius, fill: "white", opacity: 0.5, style: { pointerEvents: "none" } } ) : /* @__PURE__ */ jsx( "path", { d: path, fill: "white", opacity: 0.5, style: { pointerEvents: "none" } } )), slice.percentage >= 5 && /* @__PURE__ */ jsx( "text", { x: labelPos.x, y: labelPos.y, textAnchor: "middle", dominantBaseline: "central", className: "fill-white text-xs font-bold", style: { fontSize: "12px", pointerEvents: "none" }, children: `${Math.round(slice.percentage)}%` } ) ] }, slice.cat.key ); }) } ), hoveredData && /* @__PURE__ */ jsx( "div", { className: "absolute bg-background-900 text-white px-4 py-2 rounded-lg shadow-[0px_3px_10px_0px_rgba(38,38,38,0.2)] pointer-events-none z-10", style: { left: tooltipPos.x, top: tooltipPos.y, transform: "translate(-50%, calc(-100% - 8px))" }, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsx( "div", { className: "w-2 h-2 rounded-full shrink-0", style: { background: bgClassToCssVar(hoveredData.cat.colorClass) } } ), /* @__PURE__ */ jsxs( Text_default, { as: "span", size: "xs", weight: "bold", color: "text-white", className: "whitespace-nowrap", children: [ hoveredData.cat.label, ": ", Math.round(hoveredData.percentage), "%" ] } ) ] }) } ) ] }); }; var TimeChart = ({ data, barChartTitle = "Dados de horas por semana", pieChartTitle = "Dados de horas por item", chartHeight = 180, pieSize = 200, unitSuffix = "h", className, ...props }) => { const { categories, hoursByPeriod, hoursByItem } = data; const periodTotals = hoursByPeriod.map( (day) => categories.reduce((sum, cat) => sum + getDayValue(day, cat.key), 0) ); const maxPeriodTotal = Math.max(...periodTotals, 0); const yAxisTicks = calculateHourTicks(maxPeriodTotal); const adjustedMax = yAxisTicks[0]; const categoryTotals = {}; if (hoursByItem) { for (const cat of categories) { categoryTotals[cat.key] = hoursByItem[cat.key] ?? 0; } } else { for (const cat of categories) { categoryTotals[cat.key] = hoursByPeriod.reduce( (sum, day) => sum + getDayValue(day, cat.key), 0 ); } } return /* @__PURE__ */ jsxs( "div", { className: cn("grid grid-cols-1 lg:grid-cols-2 gap-4", className), "aria-label": "Gr\xE1ficos de dados de horas por semana e por categoria", ...props, children: [ /* @__PURE__ */ jsx( ChartCard, { title: barChartTitle, categories, contentGapClassName: "gap-8", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-row", children: [ /* @__PURE__ */ jsx( YAxis, { ticks: yAxisTicks, chartHeight, unitSuffix } ), /* @__PURE__ */ jsx("div", { className: "w-4" }), /* @__PURE__ */ jsxs("div", { className: "flex-1 relative", children: [ /* @__PURE__ */ jsx(GridLines, { ticks: yAxisTicks, chartHeight }), /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-1 gap-2 relative z-10", children: hoursByPeriod.map((day, index) => /* @__PURE__ */ jsx( StackedBar, { day, categories, maxValue: adjustedMax, chartHeight, unitSuffix }, `${day.label}-${index}` )) }) ] }) ] }) } ), /* @__PURE__ */ jsx(ChartCard, { title: pieChartTitle, categories, children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx( PieChart, { categories, totals: categoryTotals, size: pieSize } ) }) }) ] } ); }; var TimeChart_default = TimeChart; export { TIME_CHART_CATEGORY_KEY, STUDENT_CATEGORIES, DEFAULT_CATEGORIES, calculateHourTicks, TimeChart, TimeChart_default }; //# sourceMappingURL=chunk-XHQ3UYXG.mjs.map