analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
1,349 lines (1,333 loc) • 932 kB
JavaScript
// src/utils/utils.ts
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
// src/utils/dropdown.ts
var syncDropdownState = (open, isActive, setActiveStates, key) => {
if (!open && isActive) {
setActiveStates((prev) => ({ ...prev, [key]: false }));
}
};
// src/utils/activityFilters.ts
function getSelectedIdsFromCategories(categories, keys) {
const result = {};
for (const [outputKey, categoryKey] of Object.entries(keys)) {
const category = categories.find((c) => c.key === categoryKey);
result[outputKey] = category?.selectedIds || [];
}
return result;
}
function toggleArrayItem(array, item) {
return array.includes(item) ? array.filter((i) => i !== item) : [...array, item];
}
function toggleSingleValue(currentValue, newValue) {
return currentValue === newValue ? null : newValue;
}
function arraysEqual(a, b, comparator) {
if (a.length !== b.length) return false;
if (comparator) {
const sortedA2 = [...a].sort(comparator);
const sortedB2 = [...b].sort(comparator);
return sortedA2.every((val, index) => val === sortedB2[index]);
}
const sortedA = [...a].sort((x, y) => String(x).localeCompare(String(y)));
const sortedB = [...b].sort((x, y) => String(x).localeCompare(String(y)));
return sortedA.every((val, index) => val === sortedB[index]);
}
function areFiltersEqual(filters1, filters2) {
if (filters1 === filters2) return true;
if (!filters1 || !filters2) return false;
return arraysEqual(filters1.types, filters2.types) && arraysEqual(filters1.bankIds, filters2.bankIds) && arraysEqual(filters1.yearIds, filters2.yearIds) && arraysEqual(filters1.knowledgeIds, filters2.knowledgeIds) && arraysEqual(filters1.topicIds, filters2.topicIds) && arraysEqual(filters1.subtopicIds, filters2.subtopicIds) && arraysEqual(filters1.contentIds, filters2.contentIds);
}
// src/components/Quiz/useQuizStore.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";
var QUESTION_DIFFICULTY = /* @__PURE__ */ ((QUESTION_DIFFICULTY2) => {
QUESTION_DIFFICULTY2["FACIL"] = "FACIL";
QUESTION_DIFFICULTY2["MEDIO"] = "MEDIO";
QUESTION_DIFFICULTY2["DIFICIL"] = "DIFICIL";
return QUESTION_DIFFICULTY2;
})(QUESTION_DIFFICULTY || {});
var QUIZ_TYPE = /* @__PURE__ */ ((QUIZ_TYPE2) => {
QUIZ_TYPE2["SIMULADO"] = "SIMULADO";
QUIZ_TYPE2["QUESTIONARIO"] = "QUESTIONARIO";
QUIZ_TYPE2["ATIVIDADE"] = "ATIVIDADE";
return QUIZ_TYPE2;
})(QUIZ_TYPE || {});
var QUESTION_TYPE = /* @__PURE__ */ ((QUESTION_TYPE2) => {
QUESTION_TYPE2["ALTERNATIVA"] = "ALTERNATIVA";
QUESTION_TYPE2["DISSERTATIVA"] = "DISSERTATIVA";
QUESTION_TYPE2["MULTIPLA_ESCOLHA"] = "MULTIPLA_ESCOLHA";
QUESTION_TYPE2["VERDADEIRO_FALSO"] = "VERDADEIRO_FALSO";
QUESTION_TYPE2["LIGAR_PONTOS"] = "LIGAR_PONTOS";
QUESTION_TYPE2["PREENCHER"] = "PREENCHER";
QUESTION_TYPE2["IMAGEM"] = "IMAGEM";
return QUESTION_TYPE2;
})(QUESTION_TYPE || {});
var QUESTION_STATUS = /* @__PURE__ */ ((QUESTION_STATUS3) => {
QUESTION_STATUS3["PENDENTE_AVALIACAO"] = "PENDENTE_AVALIACAO";
QUESTION_STATUS3["RESPOSTA_CORRETA"] = "RESPOSTA_CORRETA";
QUESTION_STATUS3["RESPOSTA_INCORRETA"] = "RESPOSTA_INCORRETA";
QUESTION_STATUS3["NAO_RESPONDIDO"] = "NAO_RESPONDIDO";
return QUESTION_STATUS3;
})(QUESTION_STATUS || {});
var ANSWER_STATUS = /* @__PURE__ */ ((ANSWER_STATUS2) => {
ANSWER_STATUS2["RESPOSTA_CORRETA"] = "RESPOSTA_CORRETA";
ANSWER_STATUS2["RESPOSTA_INCORRETA"] = "RESPOSTA_INCORRETA";
ANSWER_STATUS2["PENDENTE_AVALIACAO"] = "PENDENTE_AVALIACAO";
ANSWER_STATUS2["NAO_RESPONDIDO"] = "NAO_RESPONDIDO";
return ANSWER_STATUS2;
})(ANSWER_STATUS || {});
var SUBTYPE_ENUM = /* @__PURE__ */ ((SUBTYPE_ENUM2) => {
SUBTYPE_ENUM2["PROVA"] = "PROVA";
SUBTYPE_ENUM2["ENEM_PROVA_1"] = "ENEM_PROVA_1";
SUBTYPE_ENUM2["ENEM_PROVA_2"] = "ENEM_PROVA_2";
SUBTYPE_ENUM2["VESTIBULAR"] = "VESTIBULAR";
SUBTYPE_ENUM2["SIMULADO"] = "SIMULADO";
SUBTYPE_ENUM2["SIMULADAO"] = "SIMULADAO";
return SUBTYPE_ENUM2;
})(SUBTYPE_ENUM || {});
var MINUTE_INTERVAL_MS = 6e4;
var useQuizStore = create()(
devtools(
(set, get) => {
let timerInterval = null;
let minuteCallbackInterval = null;
const startTimer = () => {
if (get().isFinished) {
return;
}
if (timerInterval) {
clearInterval(timerInterval);
}
timerInterval = setInterval(() => {
const { timeElapsed } = get();
set({ timeElapsed: timeElapsed + 1 });
}, 1e3);
};
const stopTimer = () => {
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
};
const setMinuteCallback = (callback) => {
set({ minuteCallback: callback });
};
const startMinuteCallback = () => {
const { minuteCallback, isFinished } = get();
if (isFinished || !minuteCallback) {
return;
}
if (minuteCallbackInterval) {
clearInterval(minuteCallbackInterval);
}
minuteCallbackInterval = setInterval(() => {
const {
minuteCallback: currentCallback,
isFinished: currentIsFinished
} = get();
if (currentIsFinished || !currentCallback) {
stopMinuteCallback();
return;
}
currentCallback();
}, MINUTE_INTERVAL_MS);
};
const stopMinuteCallback = () => {
if (minuteCallbackInterval) {
clearInterval(minuteCallbackInterval);
minuteCallbackInterval = null;
}
};
return {
// Initial State
quiz: null,
currentQuestionIndex: 0,
selectedAnswers: {},
userAnswers: [],
timeElapsed: 0,
isStarted: false,
isFinished: false,
userId: "",
variant: "default",
minuteCallback: null,
dissertativeCharLimit: void 0,
questionsResult: null,
currentQuestionResult: null,
// Setters
setQuiz: (quiz) => set({ quiz }),
setUserId: (userId) => set({ userId }),
setUserAnswers: (userAnswers) => set({ userAnswers }),
getUserId: () => get().userId,
setVariant: (variant) => set({ variant }),
setQuestionResult: (questionsResult) => set({ questionsResult }),
setDissertativeCharLimit: (limit) => set({ dissertativeCharLimit: limit }),
getDissertativeCharLimit: () => get().dissertativeCharLimit,
// Navigation
goToNextQuestion: () => {
const { currentQuestionIndex, getTotalQuestions } = get();
const totalQuestions = getTotalQuestions();
if (currentQuestionIndex < totalQuestions - 1) {
set({ currentQuestionIndex: currentQuestionIndex + 1 });
}
},
goToPreviousQuestion: () => {
const { currentQuestionIndex } = get();
if (currentQuestionIndex > 0) {
set({ currentQuestionIndex: currentQuestionIndex - 1 });
}
},
goToQuestion: (index) => {
const { getTotalQuestions } = get();
const totalQuestions = getTotalQuestions();
if (index >= 0 && index < totalQuestions) {
set({ currentQuestionIndex: index });
}
},
selectAnswer: (questionId, answerId) => {
const { quiz, userAnswers } = get();
if (!quiz) return;
const activityId = quiz.id;
const userId = get().getUserId();
if (!userId || userId === "") {
return;
}
const question = quiz.questions.find((q) => q.id === questionId);
if (!question) return;
const existingAnswerIndex = userAnswers.findIndex(
(answer) => answer.questionId === questionId
);
const newUserAnswer = {
questionId,
activityId,
userId,
answer: question.questionType === "DISSERTATIVA" /* DISSERTATIVA */ ? answerId : null,
optionId: question.questionType === "DISSERTATIVA" /* DISSERTATIVA */ ? null : answerId,
questionType: question.questionType,
answerStatus: "PENDENTE_AVALIACAO" /* PENDENTE_AVALIACAO */
};
let updatedUserAnswers;
if (existingAnswerIndex !== -1) {
updatedUserAnswers = [...userAnswers];
updatedUserAnswers[existingAnswerIndex] = newUserAnswer;
} else {
updatedUserAnswers = [...userAnswers, newUserAnswer];
}
set({
userAnswers: updatedUserAnswers
});
},
selectMultipleAnswer: (questionId, answerIds) => {
const { quiz, userAnswers } = get();
if (!quiz) return;
const activityId = quiz.id;
const userId = get().getUserId();
if (!userId || userId === "") {
return;
}
const question = quiz.questions.find((q) => q.id === questionId);
if (!question) return;
const filteredUserAnswers = userAnswers.filter(
(answer) => answer.questionId !== questionId
);
const newUserAnswers = answerIds.map(
(answerId) => ({
questionId,
activityId,
userId,
answer: null,
// selectMultipleAnswer is for non-dissertative questions
optionId: answerId,
// selectMultipleAnswer should only set optionId
questionType: question.questionType,
answerStatus: "PENDENTE_AVALIACAO" /* PENDENTE_AVALIACAO */
})
);
const updatedUserAnswers = [
...filteredUserAnswers,
...newUserAnswers
];
set({
userAnswers: updatedUserAnswers
});
},
selectDissertativeAnswer: (questionId, answer) => {
const { quiz, userAnswers, dissertativeCharLimit } = get();
if (!quiz) return;
const activityId = quiz.id;
const userId = get().getUserId();
if (!userId || userId === "") {
return;
}
const question = quiz.questions.find((q) => q.id === questionId);
if (!question || question.questionType !== "DISSERTATIVA" /* DISSERTATIVA */) {
return;
}
let validatedAnswer = answer;
if (dissertativeCharLimit !== void 0 && answer.length > dissertativeCharLimit) {
validatedAnswer = answer.substring(0, dissertativeCharLimit);
}
const existingAnswerIndex = userAnswers.findIndex(
(answerItem) => answerItem.questionId === questionId
);
const newUserAnswer = {
questionId,
activityId,
userId,
answer: validatedAnswer,
optionId: null,
questionType: "DISSERTATIVA" /* DISSERTATIVA */,
answerStatus: "PENDENTE_AVALIACAO" /* PENDENTE_AVALIACAO */
};
let updatedUserAnswers;
if (existingAnswerIndex !== -1) {
updatedUserAnswers = [...userAnswers];
updatedUserAnswers[existingAnswerIndex] = newUserAnswer;
} else {
updatedUserAnswers = [...userAnswers, newUserAnswer];
}
set({
userAnswers: updatedUserAnswers
});
},
skipQuestion: () => {
const { getCurrentQuestion, userAnswers, quiz } = get();
const currentQuestion = getCurrentQuestion();
if (!quiz) return;
if (currentQuestion) {
const activityId = quiz.id;
const userId = get().getUserId();
if (!userId || userId === "") {
return;
}
const existingAnswerIndex = userAnswers.findIndex(
(answer) => answer.questionId === currentQuestion.id
);
const newUserAnswer = {
questionId: currentQuestion.id,
activityId,
userId,
answer: null,
optionId: null,
questionType: currentQuestion.questionType,
answerStatus: "PENDENTE_AVALIACAO" /* PENDENTE_AVALIACAO */
};
let updatedUserAnswers;
if (existingAnswerIndex !== -1) {
updatedUserAnswers = [...userAnswers];
updatedUserAnswers[existingAnswerIndex] = newUserAnswer;
} else {
updatedUserAnswers = [...userAnswers, newUserAnswer];
}
set({
userAnswers: updatedUserAnswers
});
}
},
skipCurrentQuestionIfUnanswered: () => {
const { getCurrentQuestion, getCurrentAnswer, skipQuestion } = get();
const currentQuestion = getCurrentQuestion();
const currentAnswer = getCurrentAnswer();
if (!currentQuestion) return;
if (!currentAnswer || currentAnswer.optionId === null && currentAnswer.answer === null) {
skipQuestion();
}
},
addUserAnswer: (questionId, answerId) => {
const { quiz, userAnswers } = get();
if (!quiz) return;
const activityId = quiz.id;
const userId = get().getUserId();
if (!userId || userId === "") {
return;
}
const question = quiz.questions.find((q) => q.id === questionId);
if (!question) return;
const existingAnswerIndex = userAnswers.findIndex(
(answer) => answer.questionId === questionId
);
const newUserAnswer = {
questionId,
activityId,
userId,
answer: question.questionType === "DISSERTATIVA" /* DISSERTATIVA */ ? answerId || null : null,
optionId: question.questionType !== "DISSERTATIVA" /* DISSERTATIVA */ ? answerId || null : null,
questionType: question.questionType,
answerStatus: "PENDENTE_AVALIACAO" /* PENDENTE_AVALIACAO */
};
if (existingAnswerIndex !== -1) {
const updatedUserAnswers = [...userAnswers];
updatedUserAnswers[existingAnswerIndex] = newUserAnswer;
set({ userAnswers: updatedUserAnswers });
} else {
set({ userAnswers: [...userAnswers, newUserAnswer] });
}
},
startQuiz: () => {
set({ isStarted: true, timeElapsed: 0 });
startTimer();
startMinuteCallback();
},
finishQuiz: () => {
set({ isFinished: true });
stopTimer();
stopMinuteCallback();
},
resetQuiz: () => {
stopTimer();
stopMinuteCallback();
set({
quiz: null,
currentQuestionIndex: 0,
selectedAnswers: {},
userAnswers: [],
timeElapsed: 0,
isStarted: false,
isFinished: false,
userId: "",
variant: "default",
minuteCallback: null,
dissertativeCharLimit: void 0,
questionsResult: null,
currentQuestionResult: null
});
},
// Timer
updateTime: (time) => set({ timeElapsed: time }),
startTimer,
stopTimer,
// Minute Callback
setMinuteCallback,
startMinuteCallback,
stopMinuteCallback,
// Getters
getCurrentQuestion: () => {
const { currentQuestionIndex, quiz } = get();
if (!quiz) {
return null;
}
return quiz.questions[currentQuestionIndex];
},
getTotalQuestions: () => {
const { quiz } = get();
return quiz?.questions?.length || 0;
},
getAnsweredQuestions: () => {
const { userAnswers } = get();
return userAnswers.filter(
(answer) => answer.optionId !== null || answer.answer !== null
).length;
},
getUnansweredQuestions: () => {
const { quiz, userAnswers } = get();
if (!quiz) return [];
const unansweredQuestions = [];
quiz.questions.forEach((question, index) => {
const userAnswer = userAnswers.find(
(answer) => answer.questionId === question.id
);
const isAnswered = userAnswer && (userAnswer.optionId !== null || userAnswer.answer !== null);
const isSkipped = userAnswer && userAnswer.optionId === null && userAnswer.answer === null;
if (!isAnswered && !isSkipped) {
unansweredQuestions.push(index + 1);
}
});
return unansweredQuestions;
},
getSkippedQuestions: () => {
const { userAnswers } = get();
return userAnswers.filter(
(answer) => answer.optionId === null && answer.answer === null
).length;
},
getProgress: () => {
const { getTotalQuestions, getAnsweredQuestions } = get();
const total = getTotalQuestions();
const answered = getAnsweredQuestions();
return total > 0 ? answered / total * 100 : 0;
},
isQuestionAnswered: (questionId) => {
const { userAnswers } = get();
const userAnswer = userAnswers.find(
(answer) => answer.questionId === questionId
);
return userAnswer ? userAnswer.optionId !== null || userAnswer.answer !== null : false;
},
isQuestionSkipped: (questionId) => {
const { userAnswers } = get();
const userAnswer = userAnswers.find(
(answer) => answer.questionId === questionId
);
return userAnswer ? userAnswer.optionId === null && userAnswer.answer === null : false;
},
getCurrentAnswer: () => {
const { getCurrentQuestion, userAnswers } = get();
const currentQuestion = getCurrentQuestion();
if (!currentQuestion) return void 0;
const userAnswer = userAnswers.find(
(answer) => answer.questionId === currentQuestion.id
);
const hasAnswerContent = (ua) => !!ua && (ua.optionId !== null && ua.optionId !== "" || ua.answer !== null && ua.answer !== "");
if (!hasAnswerContent(userAnswer)) {
return void 0;
}
return userAnswer;
},
getAllCurrentAnswer: () => {
const { getCurrentQuestion, userAnswers } = get();
const currentQuestion = getCurrentQuestion();
if (!currentQuestion) return void 0;
const userAnswer = userAnswers.filter(
(answer) => answer.questionId === currentQuestion.id
);
return userAnswer;
},
getQuizTitle: () => {
const { quiz } = get();
return quiz?.title || "Quiz";
},
formatTime: (seconds) => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
},
getUserAnswers: () => {
const { userAnswers } = get();
return userAnswers;
},
getUnansweredQuestionsFromUserAnswers: () => {
const { quiz, userAnswers } = get();
if (!quiz) return [];
const unansweredQuestions = [];
quiz.questions.forEach((question, index) => {
const userAnswer = userAnswers.find(
(answer) => answer.questionId === question.id
);
const hasAnswer = userAnswer && (userAnswer.optionId !== null || userAnswer.answer !== null);
const isSkipped = userAnswer && userAnswer.optionId === null && userAnswer.answer === null;
if (!hasAnswer || isSkipped) {
unansweredQuestions.push(index + 1);
}
});
return unansweredQuestions;
},
getQuestionsGroupedBySubject: () => {
const { getQuestionResult, quiz, variant } = get();
const questions = variant == "result" ? getQuestionResult()?.answers : quiz?.questions;
if (!questions) return {};
const groupedQuestions = {};
questions.forEach((question) => {
const subjectId = question.knowledgeMatrix?.[0]?.subject?.id || "Sem mat\xE9ria";
if (!groupedQuestions[subjectId]) {
groupedQuestions[subjectId] = [];
}
groupedQuestions[subjectId].push(question);
});
return groupedQuestions;
},
// New methods for userAnswers
getUserAnswerByQuestionId: (questionId) => {
const { userAnswers } = get();
return userAnswers.find((answer) => answer.questionId === questionId) || null;
},
isQuestionAnsweredByUserAnswers: (questionId) => {
const { userAnswers } = get();
const answer = userAnswers.find(
(answer2) => answer2.questionId === questionId
);
return answer ? answer.optionId !== null || answer.answer !== null : false;
},
getQuestionStatusFromUserAnswers: (questionId) => {
const { userAnswers } = get();
const answer = userAnswers.find(
(answer2) => answer2.questionId === questionId
);
if (!answer) return "unanswered";
if (answer.optionId === null) return "skipped";
return "answered";
},
getUserAnswersForActivity: () => {
const { userAnswers } = get();
return userAnswers;
},
setCurrentQuestion: (question) => {
const { quiz, variant, questionsResult } = get();
if (!quiz) return;
let questionIndex = 0;
if (variant == "result") {
if (!questionsResult) return;
const questionResult = questionsResult.answers.find((q) => q.id === question.id) ?? questionsResult.answers.find((q) => q.questionId === question.id);
if (!questionResult) return;
questionIndex = quiz.questions.findIndex(
(q) => q.id === questionResult.questionId
);
} else {
questionIndex = quiz.questions.findIndex(
(q) => q.id === question.id
);
}
if (questionIndex === -1) {
return;
}
set({ currentQuestionIndex: questionIndex });
},
setAnswerStatus: (questionId, status) => {
const { userAnswers } = get();
const existingAnswerIndex = userAnswers.findIndex(
(answer) => answer.questionId === questionId
);
if (existingAnswerIndex !== -1) {
const updatedUserAnswers = [...userAnswers];
updatedUserAnswers[existingAnswerIndex] = {
...updatedUserAnswers[existingAnswerIndex],
answerStatus: status
};
set({ userAnswers: updatedUserAnswers });
}
},
getAnswerStatus: (questionId) => {
const { userAnswers } = get();
const userAnswer = userAnswers.find(
(answer) => answer.questionId === questionId
);
return userAnswer ? userAnswer.answerStatus : null;
},
getQuestionIndex: (questionId) => {
const { questionsResult, variant, quiz } = get();
if (variant == "result") {
if (!questionsResult) return 0;
let idx = questionsResult.answers.findIndex(
(q) => q.questionId === questionId
);
if (idx === -1) {
idx = questionsResult.answers.findIndex(
(q) => q.id === questionId
);
}
return idx !== -1 ? idx + 1 : 0;
} else {
if (!quiz) return 0;
const idx = quiz.questions.findIndex((q) => q.id === questionId);
return idx !== -1 ? idx + 1 : 0;
}
},
// Question Result
getQuestionResultByQuestionId: (questionId) => {
const { questionsResult } = get();
const question = questionsResult?.answers.find(
(answer) => answer.questionId === questionId
);
return question || null;
},
getQuestionResultStatistics: () => {
const { questionsResult } = get();
return questionsResult?.statistics || null;
},
getQuestionResult: () => {
const { questionsResult } = get();
return questionsResult;
},
setQuestionsResult: (questionsResult) => {
set({ questionsResult });
},
setCurrentQuestionResult: (currentQuestionResult) => {
set({ currentQuestionResult });
},
getCurrentQuestionResult: () => {
const { currentQuestionResult } = get();
return currentQuestionResult;
}
};
},
{
name: "quiz-store"
}
)
);
// src/utils/questionTypeUtils.ts
function mapQuestionTypeToEnum(type, fallback) {
const upperType = type.toUpperCase();
const typeMap = {
ALTERNATIVA: "ALTERNATIVA" /* ALTERNATIVA */,
DISSERTATIVA: "DISSERTATIVA" /* DISSERTATIVA */,
MULTIPLA_ESCOLHA: "MULTIPLA_ESCOLHA" /* MULTIPLA_ESCOLHA */,
VERDADEIRO_FALSO: "VERDADEIRO_FALSO" /* VERDADEIRO_FALSO */,
IMAGEM: "IMAGEM" /* IMAGEM */,
LIGAR_PONTOS: "LIGAR_PONTOS" /* LIGAR_PONTOS */,
PREENCHER: "PREENCHER" /* PREENCHER */
};
return typeMap[upperType] ?? fallback ?? null;
}
function mapQuestionTypeToEnumRequired(type, fallback = "ALTERNATIVA" /* ALTERNATIVA */) {
return mapQuestionTypeToEnum(type, fallback) ?? fallback;
}
// src/types/activityDetails.ts
var STUDENT_ACTIVITY_STATUS = {
CONCLUIDO: "CONCLUIDO",
AGUARDANDO_CORRECAO: "AGUARDANDO_CORRECAO",
AGUARDANDO_RESPOSTA: "AGUARDANDO_RESPOSTA",
NAO_ENTREGUE: "NAO_ENTREGUE"
};
var ACTIVITY_AVAILABILITY = {
DISPONIVEL: "DISPONIVEL",
NAO_INICIADA: "NAO_INICIADA",
EXPIRADA: "EXPIRADA"
};
// src/utils/activityDetailsUtils.ts
var getStatusBadgeConfig = (status) => {
const configs = {
[STUDENT_ACTIVITY_STATUS.CONCLUIDO]: {
label: "Conclu\xEDdo",
bgColor: "bg-green-50",
textColor: "text-green-800"
},
[STUDENT_ACTIVITY_STATUS.AGUARDANDO_CORRECAO]: {
label: "Aguardando Corre\xE7\xE3o",
bgColor: "bg-yellow-50",
textColor: "text-yellow-800"
},
[STUDENT_ACTIVITY_STATUS.AGUARDANDO_RESPOSTA]: {
label: "Aguardando Resposta",
bgColor: "bg-blue-50",
textColor: "text-blue-800"
},
[STUDENT_ACTIVITY_STATUS.NAO_ENTREGUE]: {
label: "N\xE3o Entregue",
bgColor: "bg-red-50",
textColor: "text-red-800"
},
default: {
label: "Desconhecido",
bgColor: "bg-gray-50",
textColor: "text-gray-800"
}
};
return configs[status] ?? configs.default;
};
var formatTimeSpent = (seconds) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor(seconds % 3600 / 60);
const secs = seconds % 60;
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
};
var formatQuestionNumbers = (numbers) => {
if (numbers.length === 0) return "-";
return numbers.map((n) => String(n + 1).padStart(2, "0")).join(", ");
};
var formatDateToBrazilian = (dateString) => {
const date = new Date(dateString);
const day = String(date.getUTCDate()).padStart(2, "0");
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
const year = date.getUTCFullYear();
return `${day}/${month}/${year}`;
};
// src/utils/utils.ts
function cn(...inputs) {
return twMerge(clsx(inputs));
}
function getSubjectColorWithOpacity(hexColor, isDark) {
if (!hexColor) return void 0;
let color = hexColor.replace(/^#/, "").toLowerCase();
if (isDark) {
if (color.length === 8) {
color = color.slice(0, 6);
}
return `#${color}`;
} else {
let resultColor;
if (color.length === 6) {
resultColor = `#${color}4d`;
} else if (color.length === 8) {
resultColor = `#${color}`;
} else {
resultColor = `#${color}`;
}
return resultColor;
}
}
// src/components/Text/Text.tsx
import { jsx } from "react/jsx-runtime";
var Text = ({
children,
size = "md",
weight = "normal",
color = "text-text-950",
as,
className = "",
...props
}) => {
let sizeClasses = "";
let weightClasses = "";
const sizeClassMap = {
"2xs": "text-2xs",
xs: "text-xs",
sm: "text-sm",
md: "text-md",
lg: "text-lg",
xl: "text-xl",
"2xl": "text-2xl",
"3xl": "text-3xl",
"4xl": "text-4xl",
"5xl": "text-5xl",
"6xl": "text-6xl"
};
sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;
const weightClassMap = {
hairline: "font-hairline",
light: "font-light",
normal: "font-normal",
medium: "font-medium",
semibold: "font-semibold",
bold: "font-bold",
extrabold: "font-extrabold",
black: "font-black"
};
weightClasses = weightClassMap[weight] ?? weightClassMap.normal;
const baseClasses = "font-primary";
const Component = as ?? "p";
return /* @__PURE__ */ jsx(
Component,
{
className: cn(baseClasses, sizeClasses, weightClasses, color, className),
...props,
children
}
);
};
var Text_default = Text;
// src/components/Button/Button.tsx
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
var VARIANT_ACTION_CLASSES = {
solid: {
primary: "bg-primary-950 text-text border border-primary-950 hover:bg-primary-800 hover:border-primary-800 focus-visible:outline-none focus-visible:bg-primary-950 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-primary-700 active:border-primary-700 disabled:bg-primary-500 disabled:border-primary-500 disabled:opacity-40 disabled:cursor-not-allowed",
positive: "bg-success-500 text-text border border-success-500 hover:bg-success-600 hover:border-success-600 focus-visible:outline-none focus-visible:bg-success-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-success-700 active:border-success-700 disabled:bg-success-500 disabled:border-success-500 disabled:opacity-40 disabled:cursor-not-allowed",
negative: "bg-error-500 text-text border border-error-500 hover:bg-error-600 hover:border-error-600 focus-visible:outline-none focus-visible:bg-error-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-error-700 active:border-error-700 disabled:bg-error-500 disabled:border-error-500 disabled:opacity-40 disabled:cursor-not-allowed"
},
outline: {
primary: "bg-transparent text-primary-950 border border-primary-950 hover:bg-background-50 hover:text-primary-400 hover:border-primary-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 active:border-primary-700 disabled:opacity-40 disabled:cursor-not-allowed",
positive: "bg-transparent text-success-500 border border-success-300 hover:bg-background-50 hover:text-success-400 hover:border-success-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 active:border-success-700 disabled:opacity-40 disabled:cursor-not-allowed",
negative: "bg-transparent text-error-500 border border-error-300 hover:bg-background-50 hover:text-error-400 hover:border-error-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 active:border-error-700 disabled:opacity-40 disabled:cursor-not-allowed"
},
link: {
primary: "bg-transparent text-primary-950 hover:text-primary-400 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 disabled:opacity-40 disabled:cursor-not-allowed",
positive: "bg-transparent text-success-500 hover:text-success-400 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 disabled:opacity-40 disabled:cursor-not-allowed",
negative: "bg-transparent text-error-500 hover:text-error-400 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 disabled:opacity-40 disabled:cursor-not-allowed"
}
};
var SIZE_CLASSES = {
"extra-small": "text-xs px-3.5 py-2",
small: "text-sm px-4 py-2.5",
medium: "text-md px-5 py-2.5",
large: "text-lg px-6 py-3",
"extra-large": "text-lg px-7 py-3.5"
};
var Button = ({
children,
iconLeft,
iconRight,
size = "medium",
variant = "solid",
action = "primary",
className = "",
disabled,
type = "button",
...props
}) => {
const sizeClasses = SIZE_CLASSES[size];
const variantClasses = VARIANT_ACTION_CLASSES[variant][action];
const baseClasses = "inline-flex items-center justify-center rounded-full cursor-pointer font-medium";
return /* @__PURE__ */ jsxs(
"button",
{
className: cn(baseClasses, variantClasses, sizeClasses, className),
disabled,
type,
...props,
children: [
iconLeft && /* @__PURE__ */ jsx2("span", { className: "mr-2 flex items-center", children: iconLeft }),
children,
iconRight && /* @__PURE__ */ jsx2("span", { className: "ml-2 flex items-center", children: iconRight })
]
}
);
};
var Button_default = Button;
// src/components/Badge/Badge.tsx
import { Bell } from "phosphor-react";
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
var VARIANT_ACTION_CLASSES2 = {
solid: {
error: "bg-error-background text-error-700 focus-visible:outline-none",
warning: "bg-warning text-warning-800 focus-visible:outline-none",
success: "bg-success text-success-800 focus-visible:outline-none",
info: "bg-info text-info-800 focus-visible:outline-none",
muted: "bg-background-muted text-background-800 focus-visible:outline-none"
},
outlined: {
error: "bg-error text-error-700 border border-error-300 focus-visible:outline-none",
warning: "bg-warning text-warning-800 border border-warning-300 focus-visible:outline-none",
success: "bg-success text-success-800 border border-success-300 focus-visible:outline-none",
info: "bg-info text-info-800 border border-info-300 focus-visible:outline-none",
muted: "bg-background-muted text-background-800 border border-border-300 focus-visible:outline-none"
},
exams: {
exam1: "bg-exam-1 text-info-700 focus-visible:outline-none",
exam2: "bg-exam-2 text-typography-1 focus-visible:outline-none",
exam3: "bg-exam-3 text-typography-2 focus-visible:outline-none",
exam4: "bg-exam-4 text-success-700 focus-visible:outline-none"
},
examsOutlined: {
exam1: "bg-exam-1 text-info-700 border border-info-700 focus-visible:outline-none",
exam2: "bg-exam-2 text-typography-1 border border-typography-1 focus-visible:outline-none",
exam3: "bg-exam-3 text-typography-2 border border-typography-2 focus-visible:outline-none",
exam4: "bg-exam-4 text-success-700 border border-success-700 focus-visible:outline-none"
},
resultStatus: {
negative: "bg-error text-error-800 focus-visible:outline-none",
positive: "bg-success text-success-800 focus-visible:outline-none"
},
notification: "text-primary"
};
var SIZE_CLASSES2 = {
small: "text-2xs px-2 py-1",
medium: "text-xs px-2 py-1",
large: "text-sm px-2 py-1"
};
var SIZE_CLASSES_ICON = {
small: "size-3",
medium: "size-3.5",
large: "size-4"
};
var Badge = ({
children,
iconLeft,
iconRight,
size = "medium",
variant = "solid",
action = "error",
className = "",
notificationActive = false,
...props
}) => {
const sizeClasses = SIZE_CLASSES2[size];
const sizeClassesIcon = SIZE_CLASSES_ICON[size];
const variantActionMap = VARIANT_ACTION_CLASSES2[variant] || {};
const variantClasses = typeof variantActionMap === "string" ? variantActionMap : variantActionMap[action] ?? variantActionMap.muted ?? "";
const baseClasses = "inline-flex items-center justify-center rounded-xs font-normal gap-1 relative";
const baseClassesIcon = "flex items-center";
if (variant === "notification") {
return /* @__PURE__ */ jsxs2(
"div",
{
className: cn(baseClasses, variantClasses, sizeClasses, className),
...props,
children: [
/* @__PURE__ */ jsx3(Bell, { size: 24, className: "text-current", "aria-hidden": "true" }),
notificationActive && /* @__PURE__ */ jsx3(
"span",
{
"data-testid": "notification-dot",
className: "absolute top-[5px] right-[10px] block h-2 w-2 rounded-full bg-indicator-error ring-2 ring-white"
}
)
]
}
);
}
return /* @__PURE__ */ jsxs2(
"div",
{
className: cn(baseClasses, variantClasses, sizeClasses, className),
...props,
children: [
iconLeft && /* @__PURE__ */ jsx3("span", { className: cn(baseClassesIcon, sizeClassesIcon), children: iconLeft }),
children,
iconRight && /* @__PURE__ */ jsx3("span", { className: cn(baseClassesIcon, sizeClassesIcon), children: iconRight })
]
}
);
};
var Badge_default = Badge;
// src/components/Alert/Alert.tsx
import { CheckCircle, Info, WarningCircle, XCircle } from "phosphor-react";
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
var VARIANT_ACTION_CLASSES3 = {
solid: {
default: "bg-background-50 border-transparent",
info: "bg-info border-transparent",
success: "bg-success border-transparent",
warning: "bg-warning border-transparent",
error: "bg-error border-transparent"
},
outline: {
default: "bg-background border border-border-100",
info: "bg-background border border-border-100",
success: "bg-background border border-border-100",
warning: "bg-background border border-border-100",
error: "bg-background border border-border-100"
}
};
var COLOR_CLASSES = {
default: "text-text-950",
info: "text-info-800",
success: "text-success-800",
warning: "text-warning-800",
error: "text-error-800"
};
var ICONS = {
default: /* @__PURE__ */ jsx4(CheckCircle, { size: 18 }),
info: /* @__PURE__ */ jsx4(Info, { size: 18 }),
success: /* @__PURE__ */ jsx4(CheckCircle, { size: 18 }),
warning: /* @__PURE__ */ jsx4(WarningCircle, { size: 18 }),
error: /* @__PURE__ */ jsx4(XCircle, { size: 18 })
};
var Alert = ({
variant = "solid",
title,
description,
action = "default",
className,
...props
}) => {
const baseClasses = "alert-wrapper flex items-start gap-2 w-[384px] py-3 px-4 font-inherit rounded-md";
const variantClasses = VARIANT_ACTION_CLASSES3[variant][action];
const variantColor = COLOR_CLASSES[action];
const variantIcon = ICONS[action];
const hasHeading = Boolean(title);
return /* @__PURE__ */ jsxs3("div", { className: cn(baseClasses, variantClasses, className), ...props, children: [
/* @__PURE__ */ jsx4("span", { className: cn("mt-0.5", variantColor), children: variantIcon }),
/* @__PURE__ */ jsxs3("div", { children: [
hasHeading && /* @__PURE__ */ jsx4(
Text_default,
{
size: "md",
weight: "medium",
color: variantColor,
className: "mb-0.5",
children: title
}
),
/* @__PURE__ */ jsx4(
Text_default,
{
size: hasHeading ? "sm" : "md",
weight: "normal",
color: !hasHeading ? variantColor : "text-text-700",
children: description
}
)
] })
] });
};
var Alert_default = Alert;
// src/components/LatexRenderer/LatexRenderer.tsx
import "katex/dist/katex.min.css";
import { InlineMath, BlockMath } from "react-katex";
import DOMPurify from "dompurify";
import parse, {
Element as Element2
} from "html-react-parser";
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
var sanitizeHtml = (value) => {
return DOMPurify.sanitize(value, {
ADD_ATTR: ["data-latex", "data-display-mode", "data-math", "data-math-id"]
});
};
var decodeHtmlEntities = (str) => {
const textarea = document.createElement("textarea");
textarea.innerHTML = str;
return textarea.value;
};
var cleanLatex = (str) => {
return str.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").trim();
};
var createMathReplacer = (mathParts, errorRenderer) => {
return (domNode) => {
if (domNode instanceof Element2 && domNode.name === "span" && domNode.attribs["data-math-id"]) {
const mathId = Number.parseInt(domNode.attribs["data-math-id"], 10);
const mathPart = mathParts[mathId];
if (!mathPart) return domNode;
if (mathPart.type === "inline") {
return /* @__PURE__ */ jsx5(
InlineMath,
{
math: mathPart.latex,
renderError: () => errorRenderer(mathPart.latex)
},
`math-${mathId}`
);
} else {
return /* @__PURE__ */ jsx5("div", { className: "my-2.5 text-center", children: /* @__PURE__ */ jsx5(
BlockMath,
{
math: mathPart.latex,
renderError: () => errorRenderer(mathPart.latex)
}
) }, `math-${mathId}`);
}
}
return domNode;
};
};
var LatexRenderer = ({
content,
className,
style,
onError
}) => {
const renderContentWithMath = (htmlContent) => {
if (!htmlContent) return null;
let processedContent = htmlContent;
const mathParts = [];
const mathFormulaPattern = /<span[^>]*class="math-formula"[^>]*data-latex="([^"]*)"[^>]*>[\s\S]*?<\/span>/g;
processedContent = processedContent.replaceAll(
mathFormulaPattern,
(match, latex) => {
const isDisplayMode = match.includes('data-display-mode="true"');
const id = mathParts.length;
mathParts.push({
id,
type: isDisplayMode ? "block" : "inline",
latex: cleanLatex(decodeHtmlEntities(latex))
});
return `<span data-math-id="${id}"></span>`;
}
);
const wrappedMathPattern = /<span[^>]*class="math-expression"[^>]*data-math="([^"]*)"[^>]*>.*?<\/span>/g;
processedContent = processedContent.replaceAll(
wrappedMathPattern,
(match, latex) => {
const id = mathParts.length;
mathParts.push({
id,
type: "inline",
latex: cleanLatex(decodeHtmlEntities(latex))
});
return `<span data-math-id="${id}"></span>`;
}
);
const doubleDollarPattern = /(?<!\\)\$\$([\s\S]+?)\$\$/g;
processedContent = processedContent.replaceAll(
doubleDollarPattern,
(match, latex) => {
const id = mathParts.length;
mathParts.push({
id,
type: "block",
latex: cleanLatex(latex)
});
return `<span data-math-id="${id}"></span>`;
}
);
const singleDollarPattern = /(?<!\\)\$([\s\S]+?)\$/g;
processedContent = processedContent.replaceAll(
singleDollarPattern,
(match, latex) => {
const id = mathParts.length;
mathParts.push({
id,
type: "inline",
latex: cleanLatex(latex)
});
return `<span data-math-id="${id}"></span>`;
}
);
const latexTagPattern = /(?:<latex>|<latex>)([\s\S]*?)(?:<\/latex>|<\/latex>)/g;
processedContent = processedContent.replaceAll(
latexTagPattern,
(match, latex) => {
const id = mathParts.length;
mathParts.push({
id,
type: "inline",
latex: cleanLatex(latex)
});
return `<span data-math-id="${id}"></span>`;
}
);
const latexEnvPattern = /\\begin\{([^}]+)\}([\s\S]*?)\\end\{\1\}/g;
processedContent = processedContent.replaceAll(latexEnvPattern, (match) => {
const id = mathParts.length;
mathParts.push({
id,
type: "block",
latex: cleanLatex(match)
});
return `<span data-math-id="${id}"></span>`;
});
const sanitizedContent = sanitizeHtml(processedContent);
const defaultErrorRenderer = (latex) => /* @__PURE__ */ jsxs4("span", { className: "text-red-600", children: [
"Math Error: ",
latex
] });
const errorRenderer = onError || defaultErrorRenderer;
const options = {
replace: createMathReplacer(mathParts, errorRenderer)
};
return /* @__PURE__ */ jsx5(Fragment, { children: parse(sanitizedContent, options) });
};
return /* @__PURE__ */ jsx5(
"div",
{
className: cn("whitespace-pre-wrap leading-relaxed", className),
style,
children: renderContentWithMath(content)
}
);
};
var LatexRenderer_default = LatexRenderer;
// src/components/IconButton/IconButton.tsx
import { forwardRef } from "react";
import { jsx as jsx6 } from "react/jsx-runtime";
var IconButton = forwardRef(
({ icon, size = "md", active = false, className = "", disabled, ...props }, ref) => {
const baseClasses = [
"inline-flex",
"items-center",
"justify-center",
"rounded-lg",
"font-medium",
"bg-transparent",
"text-text-950",
"cursor-pointer",
"hover:bg-primary-600",
"hover:text-text",
"focus-visible:outline-none",
"focus-visible:ring-2",
"focus-visible:ring-offset-0",
"focus-visible:ring-indicator-info",
"disabled:opacity-50",
"disabled:cursor-not-allowed",
"disabled:pointer-events-none"
];
const sizeClasses = {
sm: ["w-6", "h-6", "text-sm"],
md: ["w-10", "h-10", "text-base"]
};
const activeClasses = active ? ["!bg-primary-50", "!text-primary-950", "hover:!bg-primary-100"] : [];
const allClasses = [
...baseClasses,
...sizeClasses[size],
...activeClasses
].join(" ");
const ariaLabel = props["aria-label"] ?? "Bot\xE3o de a\xE7\xE3o";
return /* @__PURE__ */ jsx6(
"button",
{
ref,
type: "button",
className: cn(allClasses, className),
disabled,
"aria-pressed": active,
"aria-label": ariaLabel,
...props,
children: /* @__PURE__ */ jsx6("span", { className: "flex items-center justify-center", children: icon })
}
);
}
);
IconButton.displayName = "IconButton";
var IconButton_default = IconButton;
// src/components/IconRoundedButton/IconRoundedButton.tsx
import { jsx as jsx7 } from "react/jsx-runtime";
var IconRoundedButton = ({
icon,
className = "",
disabled,
...props
}) => {
const baseClasses = [
"inline-flex",
"items-center",
"justify-center",
"w-8",
"h-8",
"rounded-full",
"cursor-pointer",
"border",
"border-background-200",
"bg-background",
"text-text-950",
"hover:shadow-hard-shadow-1",
"focus-visible:outline-none",
"focus-visible:shadow-hard-shadow-1",
"focus-visible:ring-2",
"focus-visible:ring-indicator-info",
"focus-visible:ring-offset-0",
"disabled:opacity-50",
"disabled:cursor-not-allowed"
].join(" ");
return /* @__PURE__ */ jsx7(
"button",
{
type: "button",
className: cn(baseClasses, className),
disabled,
...props,
children: /* @__PURE__ */ jsx7("span", { className: "flex items-center justify-center w-5 h-5", children: icon })
}
);
};
var IconRoundedButton_default = IconRoundedButton;
// src/components/NavButton/NavButton.tsx
import { forwardRef as forwardRef2 } from "react";
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
var NavButton = forwardRef2(
({ icon, label, selected = false, className = "", disabled, ...props }, ref) => {
const baseClasses = [
"flex",
"flex-col",
"items-center",
"justify-center",
"gap-0.5",
"px-12",
"py-1",
"rounded-sm",
"cursor-pointer",
"text-text-950",
"text-xs",
"font-medium",
"hover:text-text",
"hover:bg-primary-600",
"focus-visible:outline-none",
"focus-visible:ring-2",
"focus-visible:ring-offset-0",
"focus-visible:ring-indicator-info",
"disabled:opacity-50",
"disabled:cursor-not-allowed",
"disabled:pointer-events-none"
];
const stateClasses = selected ? ["bg-primary-50", "text-primary-950"] : [];
const allClasses = [...baseClasses, ...stateClasses].join(" ");
return /* @__PURE__ */ jsxs5(
"button",
{
ref,
type: "button",
className: cn(allClasses, className),
disabled,
"aria-pressed": selected,
...props,
children: [
/* @__PURE__ */ jsx8("span", { className: "flex items-center justify-center w-5 h-5", children: icon }),
/* @__PURE__ */ jsx8("span", { className: "whitespace-nowrap", children: label })
]
}
);
}
);
NavButton.displayName = "NavButton";
var NavButton_default = NavButton;
// src/components/SelectionButton/SelectionButton.tsx
import { forwardRef as forwardRef3 } from "react";
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
var SelectionButton = forwardRef3(
({ icon, label, selected = false, className = "", disabled, ...props }, ref) => {
const baseClasses = [
"inline-flex",
"items-center",
"justify-start",
"gap-2",
"p-4",
"rounded-xl",
"curso