feat: expand admin and instructor tools
All checks were successful
CI / Validate frontend and API (push) Successful in 50s
CI / Build and publish Docker images (push) Successful in 20s

This commit is contained in:
Cauê Faleiros
2026-09-01 13:19:29 -03:00
parent caaf904281
commit 4736fb5208
13 changed files with 385 additions and 22 deletions

View File

@@ -27,9 +27,10 @@ import {
} from 'lucide-react';
import { Course, Comment, Attachment, Lesson, AttachmentType } from '../types';
import { useToast } from '../context/ToastContext';
import { getManagedCourses, saveCourse, deleteCourse, getComments, saveComment } from '../services/db';
import { getManagedCourses, saveCourse, deleteCourse, getComments, replyToComment, moderateComment } from '../services/db';
import { CATEGORIES } from '../constants';
import { getAttachmentIcon, getAttachmentBadge } from '../components/MaterialCard';
import { instructorApi, InstructorAnalytics } from '../services/api';
// 1. Create/Edit Course Modal
const CourseModal: React.FC<{
@@ -525,6 +526,7 @@ export const ManageCourses: React.FC = () => {
const [allComments, setAllComments] = useState<{comment: Comment, courseTitle: string}[]>([]);
const [replyingTo, setReplyingTo] = useState<string | null>(null);
const [replyText, setReplyText] = useState('');
const [analytics, setAnalytics] = useState<InstructorAnalytics | null>(null);
const { showToast } = useToast();
@@ -532,6 +534,8 @@ export const ManageCourses: React.FC = () => {
setLoading(true);
const data = await getManagedCourses();
setCourses(data);
const analyticsResponse = await instructorApi.analytics();
setAnalytics(analyticsResponse.data);
// Load all comments
const commentsAccumulator: {comment: Comment, courseTitle: string}[] = [];
@@ -578,12 +582,7 @@ export const ManageCourses: React.FC = () => {
const target = allComments.find(c => c.comment.id === commentId);
if (target) {
const updatedComment: Comment = {
...target.comment,
reply: replyText,
replyDate: new Date().toLocaleDateString('pt-BR')
};
await saveComment(updatedComment);
await replyToComment(commentId, replyText);
showToast('Resposta enviada com sucesso!', 'success');
setReplyingTo(null);
setReplyText('');
@@ -591,8 +590,17 @@ export const ManageCourses: React.FC = () => {
}
};
const handleModerateComment = async (commentId: string) => {
if (!window.confirm('Remover este comentário permanentemente?')) return;
await moderateComment(commentId);
showToast('Comentário removido.', 'success');
loadData();
};
// Metrics
const totalViews = courses.reduce((acc, c) => acc + (c.views || 0), 0);
const totalLearners = analytics?.learners ?? 0;
const completedLessons = analytics?.completedLessons ?? 0;
const totalQuestions = analytics?.comments ?? allComments.length;
const totalLessons = courses.reduce((acc, c) => acc + c.lessons.length, 0);
const totalMaterials = courses.reduce((acc, c) => {
const globalCount = c.attachments?.length || 0;
@@ -631,7 +639,7 @@ export const ManageCourses: React.FC = () => {
</div>
{/* Metrics Row */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 my-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-6 gap-4 my-8">
<div className="bg-zinc-900/60 border border-white/10 p-5 rounded-[8px] shadow-xl">
<span className="text-caption text-gray-400 font-semibold uppercase tracking-caption">Total de Cursos</span>
<p className="font-display text-subheading md:text-heading-sm font-bold text-white mt-1">{courses.length}</p>
@@ -645,8 +653,16 @@ export const ManageCourses: React.FC = () => {
<p className="font-display text-subheading md:text-heading-sm font-bold text-emerald-400 mt-1">{totalMaterials}</p>
</div>
<div className="bg-zinc-900/60 border border-white/10 p-5 rounded-[8px] shadow-xl">
<span className="text-caption text-gray-400 font-semibold uppercase tracking-caption">Visualizações Acumuladas</span>
<p className="font-display text-subheading md:text-heading-sm font-bold text-white mt-1">{totalViews.toLocaleString('pt-BR')}</p>
<span className="text-caption text-gray-400 font-semibold uppercase tracking-caption">Alunos com Progresso</span>
<p className="font-display text-subheading md:text-heading-sm font-bold text-white mt-1">{totalLearners.toLocaleString('pt-BR')}</p>
</div>
<div className="bg-zinc-900/60 border border-white/10 p-5 rounded-[8px] shadow-xl">
<span className="text-caption text-gray-400 font-semibold uppercase tracking-caption">Aulas Concluídas</span>
<p className="font-display text-subheading md:text-heading-sm font-bold text-orange-400 mt-1">{completedLessons.toLocaleString('pt-BR')}</p>
</div>
<div className="bg-zinc-900/60 border border-white/10 p-5 rounded-[8px] shadow-xl">
<span className="text-caption text-gray-400 font-semibold uppercase tracking-caption">Dúvidas Recebidas</span>
<p className="font-display text-subheading md:text-heading-sm font-bold text-white mt-1">{totalQuestions.toLocaleString('pt-BR')}</p>
</div>
</div>
@@ -810,6 +826,7 @@ export const ManageCourses: React.FC = () => {
)}
</div>
)}
<button onClick={() => handleModerateComment(comment.id)} className="text-xs text-red-400 hover:text-red-300 font-semibold">Remover comentário</button>
</div>
))
)}