analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
935 lines (894 loc) • 35.2 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
var _chunkKYUUQXB7js = require('./chunk-KYUUQXB7.js');
var _chunk23TTOILXjs = require('./chunk-23TTOILX.js');
var _chunkHZE6VBF5js = require('./chunk-HZE6VBF5.js');
var _chunkSZD7L65Qjs = require('./chunk-SZD7L65Q.js');
var _chunkJR4KWECXjs = require('./chunk-JR4KWECX.js');
var _chunkNEDUOGWUjs = require('./chunk-NEDUOGWU.js');
var _chunkLGPTKTMPjs = require('./chunk-LGPTKTMP.js');
var _chunk76VXVNA2js = require('./chunk-76VXVNA2.js');
var _chunkTCLRDUMFjs = require('./chunk-TCLRDUMF.js');
var _chunk34ST3MKOjs = require('./chunk-34ST3MKO.js');
var _chunkANT66KVKjs = require('./chunk-ANT66KVK.js');
var _chunkTN3AYOMVjs = require('./chunk-TN3AYOMV.js');
// src/components/Forum/Forum.tsx
var _react = require('react');
var _react3 = require('@phosphor-icons/react');
var _jsxruntime = require('react/jsx-runtime');
var MONTHS_PT = [
"Janeiro",
"Fevereiro",
"Mar\xE7o",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro"
];
function formatForumDate(isoDate) {
const d = new Date(isoDate);
const day = d.getDate();
const month = MONTHS_PT[d.getMonth()];
const year = d.getFullYear();
const hours = d.getHours();
const minutes = d.getMinutes().toString().padStart(2, "0");
return `${day} ${month} ${year} \u2022 ${hours}:${minutes}`;
}
function getInitials(name) {
return name.split(" ").slice(0, 2).map((word) => _nullishCoalesce(word[0], () => ( ""))).join("").toUpperCase();
}
function formatReplyCount(count) {
if (count === 1) return "1 resposta";
return `${count} respostas`;
}
function AuthorAvatar({ name, photoUrl }) {
if (photoUrl) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"img",
{
src: photoUrl,
alt: name,
className: "w-8 h-8 rounded-full object-cover shrink-0"
}
);
}
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "xs", weight: "semibold", color: "text-white", children: getInitials(name) }) });
}
function AuthorMeta({ authorName, authorPhoto, createdAt }) {
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, AuthorAvatar, { name: authorName, photoUrl: authorPhoto }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-1", children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkANT66KVKjs.Text_default, { size: "xs", weight: "semibold", color: "text-primary-700", children: [
"Postado por ",
authorName
] }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkANT66KVKjs.Text_default, { size: "xs", color: "text-text-600", children: [
"\u2022 ",
formatForumDate(createdAt)
] })
] })
] });
}
function PostActionsMenu({ onEdit, onDelete }) {
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk76VXVNA2js.DropdownMenu_default, { children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk76VXVNA2js.DropdownMenuTrigger,
{
"aria-label": "Abrir a\xE7\xF5es do post",
className: "p-1 rounded hover:bg-background-100 transition-colors shrink-0",
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.DotsThreeVerticalIcon, { size: 20, className: "text-text-600" })
}
),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk76VXVNA2js.DropdownMenuContent, { align: "end", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk76VXVNA2js.DropdownMenuItem,
{
iconLeft: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.PencilSimpleIcon, { size: 16 }),
onClick: onEdit,
children: "Editar"
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk76VXVNA2js.DropdownMenuItem, { iconLeft: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.TrashIcon, { size: 16 }), onClick: onDelete, children: "Deletar" })
] })
] });
}
function PostContentModal({
isOpen,
title,
submitLabel,
content,
imageUrl,
isSubmitting,
onContentChange,
onClose,
onSubmit,
onUploadImage,
onImageUploaded,
maxLength,
richText = false,
showGradeQuestion = false,
countsForGrade,
onCountsForGradeChange
}) {
const handleImageSelect = _react.useCallback.call(void 0,
async (file) => {
if (!onUploadImage) return;
try {
const url = await onUploadImage(file);
onImageUploaded(url);
} catch (e2) {
}
},
[onUploadImage, onImageUploaded]
);
const plainTextContent = richText ? _chunkHZE6VBF5js.stripHtml.call(void 0, content) : content;
const isContentEmpty = !plainTextContent.trim();
const exceedsMaxLength = plainTextContent.trim().length > maxLength;
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkTCLRDUMFjs.Modal_default,
{
isOpen,
onClose,
title,
size: "md",
footer: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex justify-end gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "outline",
action: "secondary",
size: "medium",
onClick: onClose,
children: "Cancelar"
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "solid",
action: "primary",
size: "medium",
disabled: isContentEmpty || exceedsMaxLength || isSubmitting,
onClick: onSubmit,
children: submitLabel
}
)
] }),
children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-3", children: [
richText ? (
// key forces remount (and thus reset) whenever the modal opens/closes
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkKYUUQXB7js.RichEditor,
{
content,
onChange: (data) => onContentChange(data.html),
placeholder: "Escreva o conte\xFAdo do seu post aqui."
},
String(isOpen)
)
) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkSZD7L65Qjs.TextArea_default,
{
placeholder: "Escreva o conte\xFAdo do seu post aqui.",
value: content,
onChange: (e) => onContentChange(e.target.value),
autoResize: true,
minHeight: 120,
maxLength
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk23TTOILXjs.ImageUpload,
{
variant: "compact",
onFileSelect: handleImageSelect,
onRemoveFile: () => onImageUploaded(""),
buttonIcon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.ImageIcon, { size: 16 })
}
),
imageUrl && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"img",
{
src: imageUrl,
alt: "Preview da imagem",
className: "max-h-40 rounded-lg object-contain"
}
),
showGradeQuestion && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", weight: "medium", children: "Este f\xF3rum vale nota no boletim?" }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-4", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkJR4KWECXjs.Radio_default,
{
name: "countsForGrade",
value: "yes",
label: "Sim",
size: "small",
checked: countsForGrade === true,
onChange: () => _optionalChain([onCountsForGradeChange, 'optionalCall', _ => _(true)])
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkJR4KWECXjs.Radio_default,
{
name: "countsForGrade",
value: "no",
label: "N\xE3o",
size: "small",
checked: countsForGrade === false,
onChange: () => _optionalChain([onCountsForGradeChange, 'optionalCall', _2 => _2(false)])
}
)
] })
] })
] })
}
);
}
function TopicListSkeleton() {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNEDUOGWUjs.SkeletonList, { items: 3, showAvatar: true, showTitle: true, showDescription: true, lines: 2 });
}
function TopicDetailSkeleton() {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkNEDUOGWUjs.SkeletonCard,
{
showAvatar: true,
showTitle: true,
showDescription: true,
showActions: false,
lines: 2
}
);
}
function Forum({
apiClient,
currentUserId,
userRole = "STUDENT" /* STUDENT */,
title = "F\xF3rum",
subtitle = "Espa\xE7o para troca de conhecimento",
onUploadImage,
onEvaluateReply,
className
}) {
const isTeacher = userRole === "TEACHER" /* TEACHER */;
const [view, setView] = _react.useState.call(void 0, "list");
const [topics, setTopics] = _react.useState.call(void 0, []);
const [isLoadingTopics, setIsLoadingTopics] = _react.useState.call(void 0, false);
const [selectedTopic, setSelectedTopic] = _react.useState.call(void 0, null);
const [replies, setReplies] = _react.useState.call(void 0, []);
const [isLoadingTopic, setIsLoadingTopic] = _react.useState.call(void 0, false);
const [isSubmitting, setIsSubmitting] = _react.useState.call(void 0, false);
const [isCreateModalOpen, setIsCreateModalOpen] = _react.useState.call(void 0, false);
const [createContent, setCreateContent] = _react.useState.call(void 0, "");
const [createImageUrl, setCreateImageUrl] = _react.useState.call(void 0, );
const [createCountsForGrade, setCreateCountsForGrade] = _react.useState.call(void 0, );
const [isReplyModalOpen, setIsReplyModalOpen] = _react.useState.call(void 0, false);
const [replyContent, setReplyContent] = _react.useState.call(void 0, "");
const [replyImageUrl, setReplyImageUrl] = _react.useState.call(void 0, );
const [editingTopic, setEditingTopic] = _react.useState.call(void 0, null);
const [editTopicContent, setEditTopicContent] = _react.useState.call(void 0, "");
const [editTopicImageUrl, setEditTopicImageUrl] = _react.useState.call(void 0, );
const [editingReply, setEditingReply] = _react.useState.call(void 0, null);
const [editReplyContent, setEditReplyContent] = _react.useState.call(void 0, "");
const [editReplyImageUrl, setEditReplyImageUrl] = _react.useState.call(void 0, );
const [evaluatingReplyId, setEvaluatingReplyId] = _react.useState.call(void 0,
null
);
const [gradeValue, setGradeValue] = _react.useState.call(void 0, null);
const [isEditingGrade, setIsEditingGrade] = _react.useState.call(void 0, false);
const [pendingDelete, setPendingDelete] = _react.useState.call(void 0, null);
const fetchTopics = _react.useCallback.call(void 0, async () => {
setIsLoadingTopics(true);
try {
const response = await apiClient.getTopics({ limit: 50 });
setTopics(response.topics);
} finally {
setIsLoadingTopics(false);
}
}, [apiClient]);
const fetchTopic = _react.useCallback.call(void 0,
async (topicId) => {
setIsLoadingTopic(true);
try {
const response = await apiClient.getTopic(topicId);
setSelectedTopic(response.topic);
setReplies(response.replies);
} finally {
setIsLoadingTopic(false);
}
},
[apiClient]
);
_react.useEffect.call(void 0, () => {
fetchTopics();
}, [fetchTopics]);
const handleTopicClick = _react.useCallback.call(void 0,
(topic) => {
setSelectedTopic(topic);
setView("detail");
fetchTopic(topic.id);
},
[fetchTopic]
);
const handleBack = _react.useCallback.call(void 0, () => {
setView("list");
setSelectedTopic(null);
setReplies([]);
fetchTopics();
}, [fetchTopics]);
const handleCloseCreateModal = _react.useCallback.call(void 0, () => {
setIsCreateModalOpen(false);
setCreateContent("");
setCreateImageUrl(void 0);
setCreateCountsForGrade(void 0);
}, []);
const handleCreateTopic = _react.useCallback.call(void 0, async () => {
if (!createContent.trim()) return;
setIsSubmitting(true);
try {
await apiClient.createTopic({
content: createContent.trim(),
...createImageUrl && { imageUrl: createImageUrl },
...isTeacher && createCountsForGrade !== void 0 && {
countsForGrade: createCountsForGrade
}
});
handleCloseCreateModal();
await fetchTopics();
} finally {
setIsSubmitting(false);
}
}, [
apiClient,
createContent,
createImageUrl,
createCountsForGrade,
isTeacher,
handleCloseCreateModal,
fetchTopics
]);
const handleOpenEditTopic = _react.useCallback.call(void 0, (topic) => {
setEditingTopic(topic);
setEditTopicContent(topic.content);
setEditTopicImageUrl(_nullishCoalesce(topic.imageUrl, () => ( void 0)));
}, []);
const handleCloseEditTopic = _react.useCallback.call(void 0, () => {
setEditingTopic(null);
setEditTopicContent("");
setEditTopicImageUrl(void 0);
}, []);
const handleEditTopic = _react.useCallback.call(void 0, async () => {
if (!editingTopic || !editTopicContent.trim()) return;
setIsSubmitting(true);
try {
await apiClient.updateTopic(editingTopic.id, {
content: editTopicContent.trim(),
imageUrl: _nullishCoalesce(editTopicImageUrl, () => ( null))
});
handleCloseEditTopic();
if (view === "list") {
await fetchTopics();
} else {
await fetchTopic(editingTopic.id);
}
} finally {
setIsSubmitting(false);
}
}, [
apiClient,
editingTopic,
editTopicContent,
editTopicImageUrl,
handleCloseEditTopic,
view,
fetchTopics,
fetchTopic
]);
const handleDeleteTopic = _react.useCallback.call(void 0,
async (topicId) => {
setIsSubmitting(true);
try {
await apiClient.deleteTopic(topicId);
if (view === "detail") {
handleBack();
} else {
await fetchTopics();
}
} finally {
setIsSubmitting(false);
setPendingDelete(null);
}
},
[apiClient, view, handleBack, fetchTopics]
);
const handleCloseReplyModal = _react.useCallback.call(void 0, () => {
setIsReplyModalOpen(false);
setReplyContent("");
setReplyImageUrl(void 0);
}, []);
const handleCreateReply = _react.useCallback.call(void 0, async () => {
if (!selectedTopic || !replyContent.trim()) return;
setIsSubmitting(true);
try {
await apiClient.createReply(selectedTopic.id, {
content: replyContent.trim(),
...replyImageUrl && { imageUrl: replyImageUrl }
});
handleCloseReplyModal();
await fetchTopic(selectedTopic.id);
} finally {
setIsSubmitting(false);
}
}, [
apiClient,
selectedTopic,
replyContent,
replyImageUrl,
handleCloseReplyModal,
fetchTopic
]);
const handleOpenEditReply = _react.useCallback.call(void 0, (reply) => {
setEditingReply(reply);
setEditReplyContent(reply.content);
setEditReplyImageUrl(_nullishCoalesce(reply.imageUrl, () => ( void 0)));
}, []);
const handleCloseEditReply = _react.useCallback.call(void 0, () => {
setEditingReply(null);
setEditReplyContent("");
setEditReplyImageUrl(void 0);
}, []);
const handleEditReply = _react.useCallback.call(void 0, async () => {
if (!editingReply || !editReplyContent.trim() || !selectedTopic) return;
setIsSubmitting(true);
try {
await apiClient.updateReply(editingReply.id, {
content: editReplyContent.trim(),
imageUrl: _nullishCoalesce(editReplyImageUrl, () => ( null))
});
handleCloseEditReply();
await fetchTopic(selectedTopic.id);
} finally {
setIsSubmitting(false);
}
}, [
apiClient,
editingReply,
editReplyContent,
editReplyImageUrl,
handleCloseEditReply,
selectedTopic,
fetchTopic
]);
const handleDeleteReply = _react.useCallback.call(void 0,
async (replyId) => {
if (!selectedTopic) return;
setIsSubmitting(true);
try {
await apiClient.deleteReply(replyId);
await fetchTopic(selectedTopic.id);
} finally {
setIsSubmitting(false);
setPendingDelete(null);
}
},
[apiClient, selectedTopic, fetchTopic]
);
const handleCloseEvaluateModal = _react.useCallback.call(void 0, () => {
setEvaluatingReplyId(null);
setGradeValue(null);
setIsEditingGrade(false);
}, []);
const handleOpenEvaluate = _react.useCallback.call(void 0, (reply) => {
setEvaluatingReplyId(reply.id);
setGradeValue(null);
setIsEditingGrade(false);
}, []);
const handleOpenEditGrade = _react.useCallback.call(void 0, (reply) => {
setEvaluatingReplyId(reply.id);
setGradeValue(_nullishCoalesce(reply.grade, () => ( null)));
setIsEditingGrade(true);
}, []);
const isValidGrade = gradeValue !== null && Number.isInteger(gradeValue) && gradeValue >= 0 && gradeValue <= 10;
const handleSubmitEvaluation = _react.useCallback.call(void 0, async () => {
if (!evaluatingReplyId || !isValidGrade || !onEvaluateReply) return;
setIsSubmitting(true);
try {
await onEvaluateReply(evaluatingReplyId, gradeValue);
setReplies(
(prev) => prev.map(
(r) => r.id === evaluatingReplyId ? { ...r, grade: gradeValue } : r
)
);
handleCloseEvaluateModal();
} finally {
setIsSubmitting(false);
}
}, [
evaluatingReplyId,
gradeValue,
isValidGrade,
onEvaluateReply,
handleCloseEvaluateModal
]);
const handleConfirmDelete = _react.useCallback.call(void 0, async () => {
if (!pendingDelete) return;
if (pendingDelete.type === "topic") {
await handleDeleteTopic(pendingDelete.id);
} else {
await handleDeleteReply(pendingDelete.id);
}
}, [pendingDelete, handleDeleteTopic, handleDeleteReply]);
const topicListContent = (() => {
if (isLoadingTopics) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TopicListSkeleton, {});
if (topics.length === 0) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex items-center justify-center py-16 border border-border-200 rounded-lg", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "md", color: "text-text-500", children: "Nenhum t\xF3pico ainda. Seja o primeiro a criar um post!" }) });
}
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex flex-col gap-4", children: topics.map((topic) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"div",
{
className: "flex items-start bg-white border border-border-100 rounded-lg pt-4 pr-2 pb-4 pl-2 shadow-sm",
children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"button",
{
type: "button",
className: "flex flex-col gap-3 flex-1 min-w-0 text-left hover:opacity-90 transition-opacity",
onClick: () => handleTopicClick(topic),
children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
AuthorMeta,
{
authorName: topic.authorName,
authorPhoto: topic.authorPhoto,
createdAt: topic.createdAt
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", className: "leading-normal line-clamp-3 ml-10", children: _chunkHZE6VBF5js.stripHtml.call(void 0, topic.content) }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-1 text-text-600 ml-10", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.ChatCircleTextIcon, { size: 16 }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "xs", children: formatReplyCount(topic.replyCount) })
] })
]
}
),
topic.userInstitutionId === currentUserId && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostActionsMenu,
{
onEdit: () => handleOpenEditTopic(topic),
onDelete: () => setPendingDelete({ type: "topic", id: topic.id })
}
)
]
},
topic.id
)) });
})();
const topicDetailContent = (() => {
if (isLoadingTopic) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TopicDetailSkeleton, {});
if (!selectedTopic) return null;
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-start gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-3 flex-1 min-w-0", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
AuthorMeta,
{
authorName: selectedTopic.authorName,
authorPhoto: selectedTopic.authorPhoto,
createdAt: selectedTopic.createdAt
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
className: "text-sm text-text-950 leading-normal ml-10 prose prose-sm max-w-none",
dangerouslySetInnerHTML: {
__html: _chunkHZE6VBF5js.sanitizeHtmlForDisplay.call(void 0, selectedTopic.content)
}
}
),
selectedTopic.imageUrl && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"img",
{
src: selectedTopic.imageUrl,
alt: "Imagem do post",
className: "ml-10 max-w-lg rounded-lg"
}
)
] }),
selectedTopic.userInstitutionId === currentUserId && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostActionsMenu,
{
onEdit: () => handleOpenEditTopic(selectedTopic),
onDelete: () => setPendingDelete({ type: "topic", id: selectedTopic.id })
}
)
] }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", color: "text-text-600", children: formatReplyCount(selectedTopic.replyCount) }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "solid",
action: "primary",
size: "medium",
onClick: () => setIsReplyModalOpen(true),
children: "Responder"
}
)
] }),
replies.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex flex-col divide-y divide-border-200", children: replies.map((reply) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-start gap-2 py-4", children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-3 flex-1 min-w-0", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
AuthorMeta,
{
authorName: reply.authorName,
authorPhoto: reply.authorPhoto,
createdAt: reply.createdAt
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
className: "text-sm text-text-950 leading-normal ml-10 prose prose-sm max-w-none",
dangerouslySetInnerHTML: {
__html: _chunkHZE6VBF5js.sanitizeHtmlForDisplay.call(void 0, reply.content)
}
}
),
reply.imageUrl && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"img",
{
src: reply.imageUrl,
alt: "Imagem da resposta",
className: "ml-10 max-w-lg rounded-lg"
}
),
isTeacher && _optionalChain([selectedTopic, 'optionalAccess', _3 => _3.countsForGrade]) && onEvaluateReply && (reply.grade == null ? (
/* Not yet graded — show Avaliar button */
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"button",
{
type: "button",
className: "flex items-center gap-1.5 text-text-600 text-sm hover:text-text-950 transition-colors w-fit ml-10",
onClick: () => handleOpenEvaluate(reply),
children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.CheckIcon, { size: 16 }),
"Avaliar"
]
}
)
) : (
/* Grade already set — teacher sees it with edit pencil */
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-1.5 ml-10", children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", color: "text-text-600", children: [
"Nota ",
reply.grade
] }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"button",
{
type: "button",
className: "text-text-600 hover:text-text-950 transition-colors",
onClick: () => handleOpenEditGrade(reply),
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.PencilSimpleIcon, { size: 14 })
}
)
] })
)),
!isTeacher && reply.grade != null && reply.userInstitutionId === currentUserId && _optionalChain([selectedTopic, 'optionalAccess', _4 => _4.countsForGrade]) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", color: "text-text-600", className: "ml-10", children: [
"Nota ",
reply.grade
] })
] }),
reply.userInstitutionId === currentUserId && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostActionsMenu,
{
onEdit: () => handleOpenEditReply(reply),
onDelete: () => setPendingDelete({ type: "reply", id: reply.id })
}
)
] }, reply.id)) })
] });
})();
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkTN3AYOMVjs.cn.call(void 0, "flex flex-col gap-4", className), children: [
view === "list" ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { as: "h1", size: "2xl", weight: "bold", color: "text-text-950", children: title }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "solid",
action: "primary",
size: "medium",
onClick: () => setIsCreateModalOpen(true),
children: "Criar post"
}
)
] }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-3", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { as: "h2", size: "lg", weight: "semibold", color: "text-text-950", children: subtitle }),
topicListContent
] })
] }) : (
/* Detail view */
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"button",
{
type: "button",
className: "flex items-center gap-2 hover:opacity-80 transition-opacity w-fit",
onClick: handleBack,
children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.CaretLeftIcon, { size: 24, className: "text-text-950" }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "2xl", weight: "bold", children: "Voltar" })
]
}
),
topicDetailContent
] })
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostContentModal,
{
isOpen: isCreateModalOpen,
title: "Criar post",
submitLabel: "Criar post",
content: createContent,
imageUrl: createImageUrl,
isSubmitting,
onContentChange: setCreateContent,
onClose: handleCloseCreateModal,
onSubmit: handleCreateTopic,
onUploadImage,
onImageUploaded: setCreateImageUrl,
maxLength: 1e4,
richText: true,
showGradeQuestion: isTeacher,
countsForGrade: createCountsForGrade,
onCountsForGradeChange: setCreateCountsForGrade
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostContentModal,
{
isOpen: isReplyModalOpen,
title: "Responder",
submitLabel: "Responder",
content: replyContent,
imageUrl: replyImageUrl,
isSubmitting,
onContentChange: setReplyContent,
onClose: handleCloseReplyModal,
onSubmit: handleCreateReply,
onUploadImage,
onImageUploaded: setReplyImageUrl,
maxLength: 5e3,
richText: true
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostContentModal,
{
isOpen: !!editingTopic,
title: "Editar",
submitLabel: "Salvar",
content: editTopicContent,
imageUrl: editTopicImageUrl,
isSubmitting,
onContentChange: setEditTopicContent,
onClose: handleCloseEditTopic,
onSubmit: handleEditTopic,
onUploadImage,
onImageUploaded: setEditTopicImageUrl,
maxLength: 1e4,
richText: true
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
PostContentModal,
{
isOpen: !!editingReply,
title: "Editar",
submitLabel: "Salvar",
content: editReplyContent,
imageUrl: editReplyImageUrl,
isSubmitting,
onContentChange: setEditReplyContent,
onClose: handleCloseEditReply,
onSubmit: handleEditReply,
onUploadImage,
onImageUploaded: setEditReplyImageUrl,
maxLength: 5e3,
richText: true
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkTCLRDUMFjs.Modal_default,
{
isOpen: !!evaluatingReplyId,
onClose: handleCloseEvaluateModal,
title: isEditingGrade ? "Edite a nota da atividade" : "Defina a nota da atividade",
size: "md",
footer: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex justify-end gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "outline",
action: "secondary",
size: "medium",
onClick: handleCloseEvaluateModal,
children: "Cancelar"
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "solid",
action: "primary",
size: "medium",
disabled: !isValidGrade || isSubmitting,
onClick: handleSubmitEvaluation,
children: isEditingGrade ? "Salvar" : "Avaliar"
}
)
] }),
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkLGPTKTMPjs.Input_default,
{
type: "number",
min: 0,
max: 10,
step: 1,
placeholder: "Nota (0-10)",
value: _nullishCoalesce(gradeValue, () => ( "")),
onChange: (e) => {
const val = e.target.value;
const parsed = Number(val);
setGradeValue(val === "" || Number.isNaN(parsed) ? null : parsed);
}
}
)
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkTCLRDUMFjs.Modal_default,
{
isOpen: !!pendingDelete,
onClose: () => setPendingDelete(null),
title: "Deseja deletar post?",
size: "sm",
footer: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex justify-end gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "outline",
action: "secondary",
size: "medium",
onClick: () => setPendingDelete(null),
children: "Cancelar"
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk34ST3MKOjs.Button_default,
{
variant: "solid",
action: "negative",
size: "medium",
disabled: isSubmitting,
onClick: handleConfirmDelete,
children: "Deletar"
}
)
] }),
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "md", color: "text-text-600", children: "Essa a\xE7\xE3o \xE9 permanente e n\xE3o poder\xE1 ser desfeita." })
}
)
] });
}
var Forum_default = Forum;
exports.Forum = Forum; exports.Forum_default = Forum_default;
//# sourceMappingURL=chunk-4INYKH2P.js.map