analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
1,445 lines (1,424 loc) • 62.6 kB
JavaScript
import {
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Select_default
} from "./chunk-UU7Q5S52.mjs";
import {
SkeletonRounded
} from "./chunk-NKVUUWCA.mjs";
import {
ProgressBar_default
} from "./chunk-LDBWF6EA.mjs";
import {
IconRender_default
} from "./chunk-QEZJZHJG.mjs";
import {
Badge_default
} from "./chunk-OEW3ST4F.mjs";
import {
Modal_default
} from "./chunk-TNLGS7SB.mjs";
import {
Button_default
} from "./chunk-LAYB7IKW.mjs";
import {
Text_default
} from "./chunk-IMCIR6TJ.mjs";
import {
cn,
formatPercentageRounded,
formatScore,
hexToRgba
} from "./chunk-53ICLDGS.mjs";
// src/components/SimulatedPerformance/useSimulatedPerformance.tsx
import { useEffect as useEffect2, useCallback as useCallback7, useState as useState7, useMemo as useMemo2, useRef as useRef6 } from "react";
import { useSearchParams } from "react-router-dom";
// src/components/GeneralOverviewSection/GeneralOverviewSection.tsx
import { jsx, jsxs } from "react/jsx-runtime";
function AreaCard({
area,
scoreType = "percentage" /* PERCENTAGE */
}) {
const bgColor = hexToRgba(area.color, 0.1);
return /* @__PURE__ */ jsxs(
"div",
{
className: "flex-1 min-w-0 flex flex-col items-center justify-center p-4 rounded-xl",
style: { backgroundColor: bgColor },
children: [
/* @__PURE__ */ jsx(
"div",
{
className: "w-8 h-8 flex items-center justify-center rounded-full mb-2",
style: { backgroundColor: area.color },
children: /* @__PURE__ */ jsx(IconRender_default, { iconName: area.icon, size: 18, color: "white" })
}
),
/* @__PURE__ */ jsx(
Text_default,
{
size: "xs",
weight: "semibold",
className: "text-center uppercase text-text-700 mb-1 line-clamp-2",
children: area.name
}
),
/* @__PURE__ */ jsx(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: formatScore(area.percentage, scoreType) })
]
}
);
}
function EssayCard({ essay }) {
const bgColor = hexToRgba(essay.color, 0.1);
return /* @__PURE__ */ jsxs(
"div",
{
className: "flex-1 min-w-0 flex flex-col items-center justify-center p-4 rounded-xl",
style: { backgroundColor: bgColor },
children: [
/* @__PURE__ */ jsx(
"div",
{
className: "w-8 h-8 flex items-center justify-center rounded-full mb-2",
style: { backgroundColor: essay.color },
children: /* @__PURE__ */ jsx(IconRender_default, { iconName: essay.icon, size: 18, color: "white" })
}
),
/* @__PURE__ */ jsx(
Text_default,
{
size: "xs",
weight: "semibold",
className: "text-center uppercase text-text-700 mb-1 line-clamp-2",
children: essay.name
}
),
/* @__PURE__ */ jsx(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: formatScore(essay.percentage, "percentage" /* PERCENTAGE */) })
]
}
);
}
function AreaCardSkeleton() {
return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 flex flex-col items-center justify-center p-4 rounded-xl bg-background-50", children: [
/* @__PURE__ */ jsx(SkeletonRounded, { className: "h-8 w-8 rounded-full mb-2" }),
/* @__PURE__ */ jsx(SkeletonRounded, { className: "h-3 w-16 mb-1" }),
/* @__PURE__ */ jsx(SkeletonRounded, { className: "h-6 w-12" })
] });
}
function GeneralOverviewSection({
data,
loading = false,
error = null,
scoreType = "percentage" /* PERCENTAGE */
}) {
if (error) {
return /* @__PURE__ */ jsx("div", { className: "p-4 bg-error-50 border border-error-200 rounded-xl", children: /* @__PURE__ */ jsx(Text_default, { size: "sm", className: "text-error-700", children: error }) });
}
if (loading) {
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-5 bg-background border border-border-50 rounded-xl", children: [
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
/* @__PURE__ */ jsx(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: "Geral" }),
/* @__PURE__ */ jsx(SkeletonRounded, { className: "h-4 w-3/4" })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(SkeletonRounded, { className: "h-3 w-full rounded-full" }) }),
/* @__PURE__ */ jsx(SkeletonRounded, { className: "h-6 w-12" })
] }),
/* @__PURE__ */ jsx("div", { className: "flex gap-3", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(AreaCardSkeleton, {}, i)) })
] });
}
if (!data) {
return null;
}
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-5 bg-background border border-border-50 rounded-xl", children: [
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
/* @__PURE__ */ jsx(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: "Geral" }),
/* @__PURE__ */ jsx(Text_default, { size: "sm", className: "text-text-500", children: "Dados que mostram a profici\xEAncia de todos os simulados digitais j\xE1 realizados em todas as \xE1reas do conhecimento" })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
ProgressBar_default,
{
value: scoreType === "tri" /* TRI */ ? data.overallPercentage / 10 : data.overallPercentage,
variant: "green",
size: "small"
}
) }),
/* @__PURE__ */ jsx(
Text_default,
{
size: "lg",
weight: "bold",
className: "text-text-950 min-w-[50px] text-right",
children: formatScore(data.overallPercentage, scoreType)
}
)
] }),
(data.areas.length > 0 || data.essay) && /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
data.areas.map((area) => /* @__PURE__ */ jsx(AreaCard, { area, scoreType }, area.id)),
data.essay && /* @__PURE__ */ jsx(EssayCard, { essay: data.essay })
] })
] });
}
// src/components/GeneralOverviewSection/useGeneralOverview.ts
import { useState, useCallback, useRef } from "react";
var initialState = {
data: null,
loading: false,
isRefreshing: false,
error: null
};
function useGeneralOverview(api) {
const [state, setState] = useState(initialState);
const requestIdRef = useRef(0);
const fetchOverview = useCallback(
async (params, refresh = false) => {
const currentRequestId = ++requestIdRef.current;
setState((prev) => ({
...prev,
loading: !refresh,
isRefreshing: refresh,
error: null
}));
try {
let url = "/performance/simulated/general-overview";
if (params.scoreType && params.scoreType !== "percentage") {
url += `?scoreType=${params.scoreType}`;
}
const body = {
period: params.period,
schoolIds: params.schoolIds || [],
schoolYearIds: params.schoolYearIds || [],
classIds: params.classIds || []
};
const response = await api.post(url, body);
if (currentRequestId !== requestIdRef.current) return;
setState({
data: response.data.data,
loading: false,
isRefreshing: false,
error: null
});
} catch (err) {
if (currentRequestId !== requestIdRef.current) return;
const errorMessage = err instanceof Error ? err.message : "Erro ao carregar dados gerais de desempenho";
console.error("Error fetching general overview:", err);
setState((prev) => ({
...prev,
loading: false,
isRefreshing: false,
error: errorMessage
}));
}
},
[api]
);
const reset = useCallback(() => {
requestIdRef.current++;
setState(initialState);
}, []);
return {
...state,
fetchOverview,
reset
};
}
// src/components/SimulatedStudentsOverview/useSimulatedOverview.ts
import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
// src/components/SimulatedStudentDetailsModal/types.ts
var SIMULATED_PERFORMANCE_TAG_CONFIG = {
["HIGHLIGHT" /* HIGHLIGHT */]: {
label: "Destaque da turma",
variant: "success" /* SUCCESS */
},
["ABOVE_AVERAGE" /* ABOVE_AVERAGE */]: {
label: "Acima da m\xE9dia",
variant: "info" /* INFO */
},
["BELOW_AVERAGE" /* BELOW_AVERAGE */]: {
label: "Abaixo da m\xE9dia",
variant: "warning" /* WARNING */
},
["ATTENTION_POINT" /* ATTENTION_POINT */]: {
label: "Ponto de aten\xE7\xE3o",
variant: "error" /* ERROR */
}
};
var PerformanceBadgeAction = /* @__PURE__ */ ((PerformanceBadgeAction2) => {
PerformanceBadgeAction2["SUCCESS"] = "success";
PerformanceBadgeAction2["INFO"] = "info";
PerformanceBadgeAction2["WARNING"] = "warning";
PerformanceBadgeAction2["ERROR"] = "error";
return PerformanceBadgeAction2;
})(PerformanceBadgeAction || {});
var PERFORMANCE_TAG_TO_BADGE_ACTION = {
["HIGHLIGHT" /* HIGHLIGHT */]: "success" /* SUCCESS */,
["ABOVE_AVERAGE" /* ABOVE_AVERAGE */]: "info" /* INFO */,
["BELOW_AVERAGE" /* BELOW_AVERAGE */]: "warning" /* WARNING */,
["ATTENTION_POINT" /* ATTENTION_POINT */]: "error" /* ERROR */
};
var ReportSimulationType = /* @__PURE__ */ ((ReportSimulationType2) => {
ReportSimulationType2["ENEM_1"] = "enem-1";
ReportSimulationType2["ENEM_2"] = "enem-2";
ReportSimulationType2["ESSAYS"] = "essays";
return ReportSimulationType2;
})(ReportSimulationType || {});
function isStudentSubjectsData(data) {
return "subjects" in data;
}
function isStudentContentsData(data) {
return "contents" in data;
}
function simulationTypeToActivityFilters(simulationType) {
switch (simulationType) {
case "enem-1" /* ENEM_1 */:
return {
types: ["SIMULADO"],
subtypes: ["ENEM_PROVA_1"]
};
case "enem-2" /* ENEM_2 */:
return {
types: ["SIMULADO"],
subtypes: ["ENEM_PROVA_2"]
};
default:
return {};
}
}
// src/components/SimulatedStudentsOverview/utils.ts
function getErrorMessage(err, fallbackMessage) {
return err instanceof Error ? err.message : fallbackMessage;
}
function isStudentsData(data, type) {
return type === "students" && data !== null && !("totalClasses" in data) && !("totalMunicipalities" in data);
}
function isClassesData(data, type) {
return type === "classes" && data !== null && "totalClasses" in data;
}
function isMunicipalitiesData(data, type) {
return type === "municipalities" && data !== null && "totalMunicipalities" in data;
}
// src/components/SimulatedStudentsOverview/useSimulatedOverview.ts
function buildEndpoint(simulationType, scoreType) {
if (simulationType === "essays") {
const params2 = new URLSearchParams();
if (scoreType && scoreType !== "percentage") {
params2.append("scoreType", scoreType);
}
const queryString2 = params2.toString();
const endpoint2 = "/performance/simulated/essays/students-overview";
if (!queryString2) {
return endpoint2;
}
return `${endpoint2}?${queryString2}`;
}
const activityFilters = simulationTypeToActivityFilters(simulationType);
const params = new URLSearchParams();
activityFilters.types?.forEach((t) => params.append("types", t));
activityFilters.statuses?.forEach((s) => params.append("statuses", s));
if (scoreType && scoreType !== "percentage") {
params.append("scoreType", scoreType);
}
const queryString = params.toString();
const endpoint = "/performance/simulated/activities/students-overview";
if (!queryString) {
return endpoint;
}
return `${endpoint}?${queryString}`;
}
var initialState2 = {
data: null,
loading: false,
isRefreshing: false,
error: null
};
function useSimulatedOverview(api) {
const [data, setData] = useState2(
initialState2.data
);
const [loading, setLoading] = useState2(initialState2.loading);
const [isRefreshing, setIsRefreshing] = useState2(initialState2.isRefreshing);
const [error, setError] = useState2(initialState2.error);
const requestIdRef = useRef2(0);
const paramsToBody2 = (params) => {
return {
period: params.period,
subjectId: params.subjectId,
areaKnowledgeId: params.areaKnowledgeId,
schoolIds: params.schoolIds,
schoolYearIds: params.schoolYearIds,
classIds: params.classIds,
studentsIds: params.studentsIds,
page: params.page ?? 1,
limit: params.limit ?? 10,
orderBy: params.orderBy ?? "name",
order: params.order ?? "asc"
};
};
const fetchOverview = useCallback2(
async (params, refresh = false) => {
const currentRequestId = ++requestIdRef.current;
try {
if (refresh) {
setIsRefreshing(true);
} else {
setLoading(true);
}
setError(null);
const endpoint = buildEndpoint(params.simulationType, params.scoreType);
const body = paramsToBody2(params);
const response = await api.post(
endpoint,
body
);
if (currentRequestId !== requestIdRef.current) return;
setData(response.data.data);
} catch (err) {
if (currentRequestId !== requestIdRef.current) return;
setError(
getErrorMessage(
err,
"N\xE3o foi poss\xEDvel carregar os dados de simulados"
)
);
} finally {
if (currentRequestId === requestIdRef.current) {
setLoading(false);
setIsRefreshing(false);
}
}
},
[api]
);
const reset = useCallback2(() => {
requestIdRef.current++;
setData(initialState2.data);
setLoading(initialState2.loading);
setIsRefreshing(initialState2.isRefreshing);
setError(initialState2.error);
}, []);
return { data, loading, isRefreshing, error, fetchOverview, reset };
}
// src/components/SimulatedStudentsOverview/useAggregatedOverview.ts
import { useState as useState3, useCallback as useCallback3, useRef as useRef3 } from "react";
function buildEndpoint2(aggregationType, simulationType, scoreType) {
if (simulationType === "essays") {
return "/performance/simulated/essays/students-overview";
}
const activityFilters = simulationTypeToActivityFilters(simulationType);
const params = new URLSearchParams();
params.append("aggregationType", aggregationType);
activityFilters.types?.forEach((t) => params.append("types", t));
activityFilters.statuses?.forEach((s) => params.append("statuses", s));
if (scoreType && scoreType !== "percentage") {
params.append("scoreType", scoreType);
}
const basePath = "/performance/simulated/activities/overview";
return `${basePath}?${params.toString()}`;
}
var initialState3 = {
data: null,
loading: false,
isRefreshing: false,
error: null
};
function useAggregatedOverview(api) {
const [data, setData] = useState3(
initialState3.data
);
const [loading, setLoading] = useState3(initialState3.loading);
const [isRefreshing, setIsRefreshing] = useState3(initialState3.isRefreshing);
const [error, setError] = useState3(initialState3.error);
const requestIdRef = useRef3(0);
const paramsToBody2 = (params) => {
return {
period: params.period,
subjectId: params.subjectId,
areaKnowledgeId: params.areaKnowledgeId,
schoolIds: params.schoolIds,
schoolYearIds: params.schoolYearIds,
classIds: params.classIds,
studentsIds: params.studentsIds
};
};
const fetchOverview = useCallback3(
async (params, refresh = false) => {
const currentRequestId = ++requestIdRef.current;
try {
if (refresh) {
setIsRefreshing(true);
} else {
setLoading(true);
}
setError(null);
const endpoint = buildEndpoint2(
params.aggregationType,
params.simulationType,
params.scoreType
);
const body = paramsToBody2(params);
let responseData;
switch (params.aggregationType) {
case "classes": {
const response = await api.post(
endpoint,
body
);
responseData = response.data.data;
break;
}
case "municipalities": {
const response = await api.post(
endpoint,
body
);
responseData = response.data.data;
break;
}
case "students":
default: {
const response = await api.post(
endpoint,
body
);
responseData = response.data.data;
break;
}
}
if (currentRequestId !== requestIdRef.current) return;
setData(responseData);
} catch (err) {
if (currentRequestId !== requestIdRef.current) return;
setError(
getErrorMessage(err, "N\xE3o foi poss\xEDvel carregar os dados de overview")
);
} finally {
if (currentRequestId === requestIdRef.current) {
setLoading(false);
setIsRefreshing(false);
}
}
},
[api]
);
const reset = useCallback3(() => {
requestIdRef.current++;
setData(initialState3.data);
setLoading(initialState3.loading);
setIsRefreshing(initialState3.isRefreshing);
setError(initialState3.error);
}, []);
return { data, loading, isRefreshing, error, fetchOverview, reset };
}
// src/components/SimulatedContentsPerformance/useSimulatedContents.ts
import { useState as useState4, useCallback as useCallback4, useRef as useRef4 } from "react";
function buildEndpoint3(simulationType, scoreType) {
if (simulationType === "essays" /* ESSAYS */) {
return null;
}
const activityFilters = simulationTypeToActivityFilters(simulationType);
const params = new URLSearchParams();
activityFilters.types?.forEach((t) => params.append("types", t));
activityFilters.statuses?.forEach((s) => params.append("statuses", s));
if (scoreType && scoreType !== "percentage") {
params.append("scoreType", scoreType);
}
const queryString = params.toString();
const endpoint = "/performance/simulated/activities/contents-performance";
if (!queryString) {
return endpoint;
}
return `${endpoint}?${queryString}`;
}
function useSimulatedContents(api) {
const [data, setData] = useState4(null);
const [loading, setLoading] = useState4(false);
const [isRefreshing, setIsRefreshing] = useState4(false);
const [error, setError] = useState4(null);
const isFirstLoad = useRef4(true);
const latestRequestId = useRef4(0);
const isCurrentRequest = (requestId) => requestId === latestRequestId.current;
const buildRequestBody = (params) => ({
period: params.period,
subjectId: params.subjectId && params.subjectId !== "all" ? params.subjectId : void 0,
areaKnowledgeId: params.areaKnowledgeId && params.areaKnowledgeId !== "all" ? params.areaKnowledgeId : void 0,
schoolIds: params.schoolIds,
schoolYearIds: params.schoolYearIds,
classIds: params.classIds,
studentsIds: params.studentsIds,
page: params.page ?? 1,
limit: params.limit ?? 10,
orderBy: params.orderBy ?? "correctPercentage",
order: params.order ?? "asc"
});
const handleNoEndpoint = (requestId) => {
if (!isCurrentRequest(requestId)) {
return;
}
setData(null);
setError(null);
setLoading(false);
setIsRefreshing(false);
isFirstLoad.current = true;
};
const startRequestState = (requestId, refresh) => {
if (!isCurrentRequest(requestId)) {
return;
}
if (isFirstLoad.current && !refresh) {
setLoading(true);
} else {
setIsRefreshing(true);
}
setError(null);
};
const handleSuccess = (requestId, response) => {
if (!isCurrentRequest(requestId)) {
return;
}
setData(response.data.data);
isFirstLoad.current = false;
};
const handleError = (requestId, err) => {
if (!isCurrentRequest(requestId)) {
return;
}
const errorMessage = err instanceof Error ? err.message : "Erro ao carregar dados de habilidades";
setError(errorMessage);
setData(null);
};
const finishRequestState = (requestId) => {
if (!isCurrentRequest(requestId)) {
return;
}
setLoading(false);
setIsRefreshing(false);
};
const fetchContents = useCallback4(
async (params, refresh = false) => {
const localRequestId = ++latestRequestId.current;
const endpoint = buildEndpoint3(params.simulationType, params.scoreType);
if (!endpoint) {
handleNoEndpoint(localRequestId);
return;
}
startRequestState(localRequestId, refresh);
try {
const response = await api.post(
endpoint,
buildRequestBody(params)
);
handleSuccess(localRequestId, response);
} catch (err) {
handleError(localRequestId, err);
} finally {
finishRequestState(localRequestId);
}
},
[api]
);
const reset = useCallback4(() => {
setData(null);
setLoading(false);
setIsRefreshing(false);
setError(null);
isFirstLoad.current = true;
}, []);
return {
data,
loading,
isRefreshing,
error,
fetchContents,
reset
};
}
// src/components/AreaKnowledgeSelector/AreaKnowledgeSelector.tsx
import { useMemo } from "react";
import { GridFourIcon } from "@phosphor-icons/react/dist/csr/GridFour";
// src/components/AreaKnowledgeSelector/types.ts
var ESSAY_AREA_ID = "essay";
// src/components/AreaKnowledgeSelector/AreaKnowledgeSelector.tsx
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
function AreaKnowledgeSelector({
areas,
selectedAreaId,
onAreaChange,
loading = false,
label = "\xC1rea de conhecimento",
includeEssay = true
}) {
const selectItems = useMemo(() => {
const allOption = {
id: "all",
name: "Todos"
};
const areaItems = areas.map(
(area) => ({
id: area.id,
name: area.name,
color: area.color
})
);
const items = [allOption, ...areaItems];
if (includeEssay) {
const essayOption = {
id: ESSAY_AREA_ID,
name: "Reda\xE7\xE3o"
};
items.push(essayOption);
}
return items;
}, [areas, includeEssay]);
const handleValueChange = (value) => {
if (value === "all") {
onAreaChange(null);
} else {
onAreaChange(value);
}
};
const effectiveValue = selectedAreaId || "all";
return /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsx2(Text_default, { size: "sm", weight: "medium", className: "text-text-700", children: label }),
/* @__PURE__ */ jsx2("div", { className: "relative", children: /* @__PURE__ */ jsxs2(
Select_default,
{
defaultValue: "all",
value: effectiveValue,
onValueChange: handleValueChange,
children: [
/* @__PURE__ */ jsx2(
SelectTrigger,
{
className: cn("w-full", loading && "opacity-50"),
disabled: loading,
children: /* @__PURE__ */ jsx2(SelectValue, { placeholder: "Selecione uma \xE1rea" })
}
),
/* @__PURE__ */ jsx2(SelectContent, { children: selectItems.map((item) => {
let itemIcon = null;
if (item.id === "all") {
itemIcon = /* @__PURE__ */ jsx2("span", { className: "w-[21px] h-[21px] flex items-center justify-center", children: /* @__PURE__ */ jsx2(
GridFourIcon,
{
size: 17,
weight: "bold",
className: "text-gray-600"
}
) });
} else if (item.color) {
itemIcon = /* @__PURE__ */ jsx2(
"span",
{
className: "w-[21px] h-[21px] flex items-center justify-center rounded-sm",
style: { backgroundColor: `${item.color}20` },
children: /* @__PURE__ */ jsx2(
"span",
{
className: "w-2 h-2 rounded-full",
style: { backgroundColor: item.color }
}
)
}
);
}
return /* @__PURE__ */ jsx2(SelectItem, { value: item.id, disabled: loading, children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
itemIcon,
/* @__PURE__ */ jsx2("span", { className: "whitespace-nowrap", children: item.name })
] }) }, item.id);
}) })
]
}
) })
] });
}
// src/components/SimulatedStudentDetailsModal/SimulatedStudentDetailsModal.tsx
import { useEffect, useState as useState6, useCallback as useCallback6 } from "react";
import { ArrowLeftIcon } from "@phosphor-icons/react/dist/csr/ArrowLeft";
import { CaretRightIcon } from "@phosphor-icons/react/dist/csr/CaretRight";
// src/components/SimulatedStudentDetailsModal/useSimulatedStudentDetails.ts
import { useState as useState5, useCallback as useCallback5, useRef as useRef5 } from "react";
// src/components/SimulatedStudentDetailsModal/utils.ts
function paramsToBody(params) {
return {
userInstitutionId: params.userInstitutionId,
period: params.period,
subjectId: params.subjectId ?? void 0,
page: params.page ?? 1,
limit: params.limit ?? 20
};
}
// src/components/SimulatedStudentDetailsModal/useSimulatedStudentDetails.ts
function buildEndpoint4(simulationType) {
if (simulationType === "essays" /* ESSAYS */) {
return "/performance/simulated/essays/student-details";
}
const activityFilters = simulationTypeToActivityFilters(simulationType);
const params = new URLSearchParams();
activityFilters.types?.forEach((t) => params.append("types", t));
activityFilters.statuses?.forEach((s) => params.append("statuses", s));
const queryString = params.toString();
const endpoint = "/performance/simulated/activities/student-details";
if (!queryString) {
return endpoint;
}
return `${endpoint}?${queryString}`;
}
function useSimulatedStudentDetails(api) {
const [data, setData] = useState5(null);
const [loading, setLoading] = useState5(false);
const [error, setError] = useState5(null);
const requestIdRef = useRef5(0);
const fetchDetails = useCallback5(
async (params) => {
const currentRequestId = ++requestIdRef.current;
try {
setLoading(true);
setError(null);
setData(null);
const endpoint = buildEndpoint4(params.simulationType);
const body = paramsToBody(params);
const response = await api.post(
endpoint,
body
);
if (currentRequestId !== requestIdRef.current) return;
setData(response.data.data);
} catch (err) {
if (currentRequestId !== requestIdRef.current) return;
const errorMessage = err instanceof Error ? err.message : "N\xE3o foi poss\xEDvel carregar os detalhes do estudante";
setData(null);
setError(errorMessage);
} finally {
if (currentRequestId === requestIdRef.current) {
setLoading(false);
}
}
},
[api]
);
const reset = useCallback5(() => {
requestIdRef.current++;
setData(null);
setLoading(false);
setError(null);
}, []);
return { data, loading, error, fetchDetails, reset };
}
// src/components/SimulatedStudentDetailsModal/SimulatedStudentDetailsModal.tsx
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
function SimulatedStudentDetailsModal({
api,
isOpen,
onClose,
simulationType,
userInstitutionId,
studentName,
period
}) {
const { data, loading, error, fetchDetails, reset } = useSimulatedStudentDetails(api);
const [selectedSubject, setSelectedSubject] = useState6(null);
useEffect(() => {
if (isOpen && userInstitutionId) {
fetchDetails({
simulationType,
userInstitutionId,
period,
subjectId: null
});
}
}, [isOpen, userInstitutionId, simulationType, period, fetchDetails]);
useEffect(() => {
if (!isOpen) {
reset();
setSelectedSubject(null);
}
}, [isOpen, reset]);
const handleSubjectClick = useCallback6(
(subject) => {
if (!userInstitutionId) return;
setSelectedSubject({ id: subject.id, name: subject.name });
fetchDetails({
simulationType,
userInstitutionId,
period,
subjectId: subject.id
});
},
[userInstitutionId, simulationType, period, fetchDetails]
);
const handleBack = useCallback6(() => {
if (!userInstitutionId) return;
setSelectedSubject(null);
fetchDetails({
simulationType,
userInstitutionId,
period,
subjectId: null
});
}, [userInstitutionId, simulationType, period, fetchDetails]);
const modalTitle = selectedSubject ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx3(
Button_default,
{
onClick: handleBack,
variant: "raw",
className: "p-1 hover:bg-background-100 rounded-md transition-colors",
"aria-label": "Voltar para lista de mat\xE9rias",
children: /* @__PURE__ */ jsx3(ArrowLeftIcon, { size: 20, className: "text-text-600" })
}
),
/* @__PURE__ */ jsx3(Text_default, { children: selectedSubject.name })
] }) : `Desempenho de ${studentName || "Estudante"}`;
if (loading) {
return /* @__PURE__ */ jsx3(Modal_default, { isOpen, onClose, title: modalTitle, size: "lg", children: /* @__PURE__ */ jsx3("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx3(Text_default, { size: "sm", className: "text-text-500", children: "Carregando..." }) }) });
}
if (error) {
return /* @__PURE__ */ jsx3(Modal_default, { isOpen, onClose, title: modalTitle, size: "lg", children: /* @__PURE__ */ jsx3("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx3(Text_default, { size: "sm", className: "text-error-500", children: error }) }) });
}
if (!data) {
return /* @__PURE__ */ jsx3(Modal_default, { isOpen, onClose, title: modalTitle, size: "lg", children: /* @__PURE__ */ jsx3("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx3(Text_default, { size: "sm", className: "text-text-500", children: "Nenhum dado encontrado" }) }) });
}
const isSubjectsLevel = isStudentSubjectsData(data);
const renderDetailsContent = () => {
if (isSubjectsLevel) {
if (data.subjects.length > 0) {
return data.subjects.map((subject) => /* @__PURE__ */ jsx3(
SubjectItem,
{
subject,
onClick: () => handleSubjectClick(subject),
questionsLabel: "quest\xF5es"
},
subject.id
));
}
return /* @__PURE__ */ jsx3(Text_default, { size: "sm", className: "text-text-500 text-center py-4", children: "Nenhuma mat\xE9ria encontrada" });
}
if (data.contents.length > 0) {
return data.contents.map((content) => /* @__PURE__ */ jsx3(
ContentItem,
{
content,
questionsLabel: "quest\xF5es",
ofLabel: "de"
},
content.contentId
));
}
return /* @__PURE__ */ jsx3(Text_default, { size: "sm", className: "text-text-500 text-center py-4", children: "Nenhuma habilidade encontrada" });
};
const detailsContent = renderDetailsContent();
return /* @__PURE__ */ jsx3(Modal_default, { isOpen, onClose, title: modalTitle, size: "lg", children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-4", children: [
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between p-4 bg-background-50 rounded-xl", children: [
/* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
/* @__PURE__ */ jsx3(Text_default, { size: "md", weight: "semibold", className: "text-text-950", children: data.student.name }),
/* @__PURE__ */ jsxs3(Text_default, { size: "sm", className: "text-text-500", children: [
data.student.school,
" - ",
data.student.class
] })
] }),
/* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-end gap-1", children: [
/* @__PURE__ */ jsx3(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: formatPercentageRounded(data.student.average) }),
/* @__PURE__ */ jsx3(
Badge_default,
{
variant: "solid",
action: PERFORMANCE_TAG_TO_BADGE_ACTION[data.student.performance],
size: "small",
children: SIMULATED_PERFORMANCE_TAG_CONFIG[data.student.performance].label
}
)
] })
] }),
/* @__PURE__ */ jsx3("div", { className: "flex flex-col gap-2 max-h-[400px] overflow-y-auto", children: detailsContent })
] }) });
}
function SubjectItem({
subject,
onClick,
questionsLabel
}) {
return /* @__PURE__ */ jsxs3(
"button",
{
onClick,
className: "flex items-center gap-4 p-4 bg-background border border-border-50 rounded-xl hover:bg-background-50 transition-colors text-left w-full",
children: [
subject.color && /* @__PURE__ */ jsx3(
"div",
{
className: "w-3 h-3 rounded-full flex-shrink-0",
style: { backgroundColor: subject.color }
}
),
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsx3(Text_default, { size: "sm", weight: "semibold", className: "text-text-950 truncate", children: subject.name }),
/* @__PURE__ */ jsxs3(Text_default, { size: "xs", className: "text-text-500", children: [
subject.questionsCount,
" ",
questionsLabel
] })
] }),
/* @__PURE__ */ jsx3("div", { className: "w-32 flex-shrink-0", children: /* @__PURE__ */ jsx3(
ProgressBar_default,
{
value: subject.performance.correctPercentage,
variant: "green",
size: "small",
showPercentage: true
}
) }),
/* @__PURE__ */ jsx3(CaretRightIcon, { size: 16, className: "text-text-400 flex-shrink-0" })
]
}
);
}
function ContentItem({
content,
questionsLabel,
ofLabel
}) {
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-4 p-4 bg-background border border-border-50 rounded-xl", children: [
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsx3(Text_default, { size: "sm", weight: "semibold", className: "text-text-950 truncate", children: content.contentName }),
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
content.bnccCode && /* @__PURE__ */ jsx3(Text_default, { size: "xs", className: "text-primary-600", children: content.bnccCode }),
/* @__PURE__ */ jsxs3(Text_default, { size: "xs", className: "text-text-500", children: [
content.questionsCount,
" ",
questionsLabel
] })
] })
] }),
/* @__PURE__ */ jsx3("div", { className: "w-32 flex-shrink-0", children: /* @__PURE__ */ jsx3(
ProgressBar_default,
{
value: content.performance.correctPercentage,
variant: "green",
size: "small",
showPercentage: true
}
) }),
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
/* @__PURE__ */ jsx3(Text_default, { size: "sm", weight: "semibold", className: "text-success-600", children: content.performance.correct }),
/* @__PURE__ */ jsxs3(Text_default, { size: "xs", className: "text-text-500 whitespace-nowrap", children: [
ofLabel,
" ",
content.questionsCount
] })
] })
] });
}
// src/utils/profileAggregation.ts
function getAggregationTypeByProfile(profileName) {
switch (profileName) {
case "UNIT_MANAGER" /* UNIT_MANAGER */:
return "classes";
case "REGIONAL_MANAGER" /* REGIONAL_MANAGER */:
return "municipalities";
case "GENERAL_MANAGER" /* GENERAL_MANAGER */:
case "TEACHER" /* TEACHER */:
default:
return "students";
}
}
function shouldUseAggregatedOverview(profileName) {
return profileName === "UNIT_MANAGER" /* UNIT_MANAGER */ || profileName === "REGIONAL_MANAGER" /* REGIONAL_MANAGER */;
}
// src/components/SimulatedPerformance/types.ts
var SimulatedViewTab = /* @__PURE__ */ ((SimulatedViewTab2) => {
SimulatedViewTab2["STUDENTS"] = "students";
SimulatedViewTab2["SKILLS"] = "skills";
return SimulatedViewTab2;
})(SimulatedViewTab || {});
// src/components/SimulatedPerformance/useSimulatedPerformance.tsx
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
function SimulatedPerformanceTagCell({
row
}) {
const tagConfig = SIMULATED_PERFORMANCE_TAG_CONFIG[row.performance];
return /* @__PURE__ */ jsx4(Badge_default, { variant: "solid", action: tagConfig.variant, size: "small", children: tagConfig.label });
}
function ContentPerformanceCell({
percentage
}) {
const getColorClass = () => {
if (percentage >= 70) {
return "bg-success-500";
}
if (percentage >= 50) {
return "bg-warning-500";
}
return "bg-error-500";
};
const colorClass = getColorClass();
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-center gap-2", children: [
/* @__PURE__ */ jsxs4("span", { className: "text-sm text-text-950 min-w-[40px]", children: [
Math.round(percentage),
"%"
] }),
/* @__PURE__ */ jsx4("div", { className: "w-16 h-2 bg-background-100 rounded-full overflow-hidden", children: /* @__PURE__ */ jsx4(
"div",
{
className: `h-full rounded-full ${colorClass}`,
style: { width: `${percentage}%` }
}
) })
] });
}
var centeredColumn = {
sortable: true,
className: "py-3 px-4 text-center",
align: "center"
};
var centeredContentsColumn = {
sortable: true,
className: "py-3 px-4 text-center",
align: "center"
};
var simulatedTableColumnsBase = [
{
key: "name",
label: "Nome",
sortable: true,
className: "py-3 px-4 text-start"
},
{
key: "school",
label: "Escola",
sortable: false,
className: "py-3 px-4 text-start"
},
{
...centeredColumn,
key: "schoolYear",
label: "Ano",
width: "80px",
sortable: false
},
{
...centeredColumn,
key: "class",
label: "Turma",
width: "80px",
sortable: false
},
{
...centeredColumn,
key: "average",
label: "M\xE9dia",
width: "100px"
},
{
...centeredColumn,
key: "performance",
label: "Desempenho",
width: "160px"
}
];
var contentsTableColumns = [
{
key: "contentName",
label: "Habilidade",
sortable: true,
className: "py-3 px-4 text-start"
},
{
key: "subject",
label: "Componente",
sortable: false,
className: "py-3 px-4 text-start",
render: (_value, row) => row.subject.name
},
{
...centeredContentsColumn,
key: "simulatedExamsCount",
label: "Simulados",
width: "100px"
},
{
...centeredContentsColumn,
key: "questionsCount",
label: "Quest\xF5es",
width: "100px"
},
{
...centeredContentsColumn,
key: "studentsCount",
label: "Estudantes",
width: "100px"
},
{
...centeredContentsColumn,
key: "performance",
label: "Desempenho",
width: "160px",
render: (_value, row) => /* @__PURE__ */ jsx4(ContentPerformanceCell, { percentage: row.performance.correctPercentage })
}
];
function useSimulatedPerformance({
api,
profileName,
enabled = true
}) {
const [searchParams, setSearchParams] = useSearchParams();
const aggregationType = useMemo2(
() => getAggregationTypeByProfile(profileName),
[profileName]
);
const period = useMemo2(() => {
const periodParam = searchParams.get("period");
if (periodParam === "7_DAYS") return "7_DAYS";
if (periodParam === "1_MONTH") return "1_MONTH";
if (periodParam === "3_MONTHS") return "3_MONTHS";
if (periodParam === "6_MONTHS") return "6_MONTHS";
if (periodParam === "1_YEAR") return "1_YEAR";
return "1_MONTH";
}, [searchParams]);
const scoreType = useMemo2(() => {
const scoreTypeParam = searchParams.get("scoreType");
if (scoreTypeParam === "tri") return "tri" /* TRI */;
return "percentage" /* PERCENTAGE */;
}, [searchParams]);
const [selectedAreaKnowledgeId, setSelectedAreaKnowledgeId] = useState7(null);
const [selectedAreaRelatedIds, setSelectedAreaRelatedIds] = useState7([]);
const [selectedSubjectId, setSelectedSubjectId] = useState7(
null
);
const [simulatedViewTab, setSimulatedViewTab] = useState7(
"students" /* STUDENTS */
);
const [isStudentModalOpen, setIsStudentModalOpen] = useState7(false);
const [selectedStudent, setSelectedStudent] = useState7(null);
const [isContentModalOpen, setIsContentModalOpen] = useState7(false);
const [selectedContent, setSelectedContent] = useState7(null);
const [isFiltersModalOpen, setIsFiltersModalOpen] = useState7(false);
const [filters, setFilters] = useState7({
schoolIds: [],
schoolYearIds: [],
classIds: [],
studentsIds: []
});
const selectedAreaKnowledgeIdRef = useRef6(
selectedAreaKnowledgeId
);
const selectedSubjectIdRef = useRef6(selectedSubjectId);
const filtersRef = useRef6(filters);
const simulatedViewTabRef = useRef6(simulatedViewTab);
const scoreTypeRef = useRef6(scoreType);
const periodRef = useRef6(period);
const aggregationTypeRef = useRef6(aggregationType);
useEffect2(() => {
selectedAreaKnowledgeIdRef.current = selectedAreaKnowledgeId;
}, [selectedAreaKnowledgeId]);
useEffect2(() => {
selectedSubjectIdRef.current = selectedSubjectId;
}, [selectedSubjectId]);
useEffect2(() => {
filtersRef.current = filters;
}, [filters]);
useEffect2(() => {
simulatedViewTabRef.current = simulatedViewTab;
}, [simulatedViewTab]);
useEffect2(() => {
scoreTypeRef.current = scoreType;
}, [scoreType]);
useEffect2(() => {
periodRef.current = period;
}, [period]);
useEffect2(() => {
aggregationTypeRef.current = aggregationType;
}, [aggregationType]);
const {
data: generalOverviewData,
loading: generalOverviewLoading,
error: generalOverviewError,
fetchOverview: fetchGeneralOverview
} = useGeneralOverview(api);
const {
data: aggregatedData,
loading: aggregatedLoading,
isRefreshing: aggregatedRefreshing,
error: aggregatedError,
fetchOverview: fetchAggregatedOverview
} = useAggregatedOverview(api);
const {
data: simulatedData,
loading: simulatedLoading,
isRefreshing: simulatedRefreshing,
error: simulatedError,
fetchOverview: fetchSimulatedOverview
} = useSimulatedOverview(api);
const {
data: contentsData,
loading: contentsLoading,
isRefreshing: contentsRefreshing,
error: contentsError,
fetchContents
} = useSimulatedContents(api);
const isEssaySelected = selectedAreaKnowledgeId === ESSAY_AREA_ID;
const activeFiltersCount = useMemo2(() => {
let count = 0;
if (filters.schoolIds.length > 0) count++;
if (filters.schoolYearIds.length > 0) count++;
if (filters.classIds.length > 0) count++;
if (filters.studentsIds.length > 0) count++;
return count;
}, [filters]);
useEffect2(() => {
if (!selectedAreaKnowledgeId || !generalOverviewData?.areas) {
return;
}
const area = generalOverviewData.areas.find(
(a) => a.id === selectedAreaKnowledgeId
);
const relatedIds = area?.relatedIds ?? [selectedAreaKnowledgeId];
setSelectedAreaRelatedIds(relatedIds);
}, [generalOverviewData, selectedAreaKnowledgeId]);
const studentsTableColumns = useMemo2(
() => simulatedTableColumnsBase.map((col) => {
if (col.key === "average") {
return {
...col,
render: (_value, row) => formatScore(row.average, scoreType)
};
}
if (col.key === "performance") {
return {
...col,
render: (_value, row) => /* @__PURE__ */ jsx4(SimulatedPerformanceTagCell, { row })
};
}
return col;
}),
[scoreType]
);
const loadStudentsData = useCallback7(
async (areaKnowledgeId, subjectId, page = 1, refresh = false, periodOverride, scoreTypeOverride) => {
const currentFilters = filtersRef.current;
const currentPeriod = periodOverride ?? periodRef.current;
const currentScoreType = scoreTypeOverride ?? scoreTypeRef.current;
const isEssay = areaKnowledgeId === ESSAY_AREA_ID;
const effectiveSubjectId = !isEssay && subjectId && subjectId !== "all" ? subjectId : void 0;
const effectiveAreaKnowledgeId = !isEssay && areaKnowledgeId && areaKnowledgeId !== "all" ? areaKnowledgeId : void 0;
await fetchSimulatedOverview(
{
simulationType: isEssay ? "essays" : "enem-1",
period: currentPeriod,
subjectId: effectiveSubjectId,
areaKnowledgeId: effectiveAreaKnowledgeId,
schoolIds: currentFilters.schoolIds,
schoolYearIds: currentFilters.schoolYearIds,
classIds: currentFilters.classIds,
studentsIds: currentFilters.studentsIds,
page,
limit: 10,
orderBy: "name",
order: "asc",
scoreType: currentScoreType
},
refresh
);
},
[fetchSimulatedOverview]
);
const loadSkillsData = useCallback7(
async (areaKnowledgeId, subjectId, periodOverride, scoreTypeOverride) => {
if (areaKnowledgeId === ESSAY_AREA_ID) {
return;
}
const currentFilters = filtersRef.current;
const currentPeriod = periodOverride ?? periodRef.current;
const currentScoreType = scoreTypeOverride ?? scoreTypeRef.current;
const effectiveSubjectId = subjectId && subjectId !== "all" ? subjectId : void 0;
const effectiveAreaKnowledgeId = areaKnowledgeId && areaKnowledgeId !== "all" ? areaKnowledgeId : void 0;
await fetchContents({
simulationType: "enem-1",
period: currentPeriod,
subjectId: effectiveSubjectId,
areaKnowledgeId: effectiveAreaKnowledgeId,
schoolIds: currentFilters.schoolIds,
schoolYearIds: currentFilters.schoolYearIds,
classIds: currentFilters.classIds,
studentsIds: currentFilters.studentsIds,
page: 1,
limit: 10,
orderBy: "correctPercentage",
order: "asc",
scoreType: currentScoreType
});
},
[fetchContents]
);
const loadGeneralOverviewData = useCallback7(
async (periodOverride, scoreTypeOverride) => {
const currentFilters = filtersRef.current;
const currentScoreType = scoreTypeOverride ?? scoreTypeRef.current;
await fetchGeneralOverview({
period: periodOverride ?? periodRef.current,
schoolIds: currentFilters.schoolIds,
schoolYearIds: currentFilters.schoolYearIds,
classIds: currentFilters.classIds,
studentsIds: currentFilters.studentsIds,
scoreType: currentScoreType
});
},
[fetchGeneralOverview]
);
const loadAggregatedOverviewData = useCallback7(
async (areaKnowledgeId, subjectId, refresh = false, periodOverride, scoreTypeOverride) => {
const currentFilters = filtersRef.current;
const currentPeriod = periodOverride ?? periodRef.current;
const currentScoreType = scoreTypeOverride ?? scoreTypeRef.current;
const currentAggregationType = aggregationTypeRef.current;
const isEssay = areaKnowledgeId === ESSAY_AREA_ID;
const effectiveSubjectId = !isEssay && subjectId && subjectId !== "all" ? subjectId : void 0;
const effectiveAreaKnowledgeId = !isEssay && areaKnowledgeId && areaKnowledgeId !== "all" ? areaKnowledgeId : void 0;
await fetchAggregatedOverview(
{
aggregationType: currentAggregationType,
simulationType: isEssay ? "essays" : "enem-1",
period: currentPeriod,
subjectId: effectiveSubjectId,
areaKnowledgeId: effectiveAreaKnowledgeId,
schoolIds: currentFilters.schoolIds,
schoolYearIds: currentFilters.schoolYearIds,
classIds: currentFilters.classIds,
studentsIds: currentFilters.studentsIds,
scoreType: currentScoreType
},
refresh
);
},
[fetchAggregatedOverview]
);
const handlePeriodChange = useCallback7(
(value) => {
const newPeriod = value;
if (newPeriod === periodRef.current) {
return;
}
const effectiveScoreType = scoreTypeRef.current;
setSearchParams((prev) => {
const newParams = new URLSearchParams(prev);
if (newPeriod === "1_MONTH") {
newParams.delete("period");
} else {
newParams.set("period", newPeriod);
}
if (effectiveScoreType === "percentage") {
newParams.delete("scoreType");
} else {
newParams.set("scoreType", effectiveScoreType);
}
return newParams;
});
loadAggregatedOverviewData(
selectedAreaKnowledgeIdRef.current,
selectedSubjectIdRef.current,
false,
newPeriod,