analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
496 lines (494 loc) • 15.6 kB
JavaScript
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