feat: expand admin and instructor tools
This commit is contained in:
@@ -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>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user