microtecflix/src/components/AdminContentManager.tsx

998 lines
42 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
GraduationCap, Plus, Edit, Trash2, FolderPlus, FilePlus, ChevronRight,
ArrowLeft, Film, Youtube, RefreshCw, Layers, CheckCircle, CheckCircle2
} from 'lucide-react';
interface Lesson {
id: string;
moduleId: string;
title: string;
videoUrl: string;
videoType: 'youtube' | 'direct';
order: number;
content: string;
thumbnailUrl?: string;
}
interface Module {
id: string;
courseId: string;
title: string;
order: number;
lessons: Lesson[];
}
interface Course {
id: string;
title: string;
description: string;
thumbnail: string;
category: string;
isLocked?: boolean;
price?: number;
certificateMode?: string;
}
interface AdminContentManagerProps {
token: string | null;
}
export default function AdminContentManager({ token }: AdminContentManagerProps) {
const [courses, setCourses] = useState<Course[]>([]);
const [selectedCourse, setSelectedCourse] = useState<Course | null>(null);
const [modules, setModules] = useState<Module[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Forms states
const [showCourseForm, setShowCourseForm] = useState(false);
const [editingCourse, setEditingCourse] = useState<Course | null>(null);
const [courseForm, setCourseForm] = useState({
title: '', description: '', thumbnail: '', category: '', isLocked: false, price: 0, certificateMode: 'FULL_COURSE' as 'FULL_COURSE' | 'PER_MODULE'
});
const [showModuleForm, setShowModuleForm] = useState(false);
const [editingModule, setEditingModule] = useState<Module | null>(null);
const [moduleForm, setModuleForm] = useState({ title: '', order: 1 });
const [showLessonForm, setShowLessonForm] = useState(false);
const [editingLesson, setEditingLesson] = useState<Lesson | null>(null);
const [activeModuleForLesson, setActiveModuleForLesson] = useState<string | null>(null);
const [lessonForm, setLessonForm] = useState({
title: '', videoUrl: '', videoType: 'youtube' as 'youtube' | 'direct', order: 1, content: '', thumbnailUrl: ''
});
const [managingActivityForModule, setManagingActivityForModule] = useState<string | null>(null);
const [showActivityModal, setShowActivityModal] = useState(false);
const [activeModuleForActivity, setActiveModuleForActivity] = useState<string | null>(null);
const [activityQuestions, setActivityQuestions] = useState<{question: string, options: string[], correctIndex: number}[]>([]);
const [uploadingImage, setUploadingImage] = useState(false);
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
setUploadingImage(true);
try {
const res = await fetch('/api/admin/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const data = await res.json();
if (res.ok && data.url) {
setCourseForm(prev => ({ ...prev, thumbnail: data.url }));
} else {
alert(data.error || 'Erro ao fazer upload da imagem');
}
} catch (err) {
console.error(err);
alert('Erro de conexão ao fazer upload');
} finally {
setUploadingImage(false);
}
};
useEffect(() => {
fetchCourses();
}, []);
const fetchCourses = async () => {
setIsLoading(true);
try {
const res = await fetch('/api/courses', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
const data = await res.json();
setCourses(data);
}
} catch (err) {
console.error('Error fetching courses:', err);
} finally {
setIsLoading(false);
}
};
const fetchCourseModulesAndLessons = async (courseId: string) => {
setIsLoading(true);
try {
const res = await fetch(`/api/courses/${courseId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
const data = await res.json();
setModules(data.modules);
}
} catch (err) {
console.error('Error loading modules:', err);
} finally {
setIsLoading(false);
}
};
const handleSelectCourse = (course: Course) => {
setSelectedCourse(course);
fetchCourseModulesAndLessons(course.id);
};
// --- COURSE CRUD ---
const handleSaveCourse = async (e: React.FormEvent) => {
e.preventDefault();
const method = editingCourse ? 'PUT' : 'POST';
const url = editingCourse ? `/api/admin/courses/${editingCourse.id}` : '/api/admin/courses';
try {
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(courseForm)
});
if (res.ok) {
await fetchCourses();
setShowCourseForm(false);
setEditingCourse(null);
setCourseForm({ title: '', description: '', thumbnail: '', category: '', isLocked: false, price: 0, certificateMode: 'FULL_COURSE' });
} else {
const errData = await res.json();
alert(`Erro ao salvar curso: ${errData.error || 'Falha na resposta do servidor'}`);
}
} catch (err) {
console.error('Error saving course:', err);
alert('Erro de rede ao salvar curso.');
}
};
const handleEditCourse = (course: Course) => {
setEditingCourse(course);
setCourseForm({
title: course.title,
description: course.description,
thumbnail: course.thumbnail,
category: course.category,
isLocked: course.isLocked || false,
price: course.price || 0,
certificateMode: (course.certificateMode || 'FULL_COURSE') as 'PER_MODULE' | 'FULL_COURSE'
});
setShowCourseForm(true);
setTimeout(() => {
document.getElementById('admin-content-top')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 50);
};
const handleDeleteCourse = async (courseId: string) => {
if (!window.confirm('Tem certeza que deseja excluir este curso e todo o seu conteúdo (módulos, aulas, anotações)?')) return;
try {
const res = await fetch(`/api/admin/courses/${courseId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
fetchCourses();
if (selectedCourse?.id === courseId) {
setSelectedCourse(null);
setModules([]);
}
}
} catch (err) {
console.error('Error deleting course:', err);
}
};
// --- MODULE CRUD ---
const handleSaveModule = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedCourse) return;
const method = editingModule ? 'PUT' : 'POST';
const url = editingModule ? `/api/admin/modules/${editingModule.id}` : '/api/admin/modules';
const payload = editingModule ? { ...moduleForm } : { ...moduleForm, courseId: selectedCourse.id };
try {
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
});
if (res.ok) {
await fetchCourseModulesAndLessons(selectedCourse.id);
setShowModuleForm(false);
setEditingModule(null);
setModuleForm({ title: '', order: 1 });
}
} catch (err) {
console.error('Error saving module:', err);
}
};
const handleEditModule = (mod: Module) => {
setEditingModule(mod);
setModuleForm({ title: mod.title, order: mod.order });
setShowModuleForm(true);
setTimeout(() => {
document.getElementById('admin-content-top')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 50);
};
const handleDeleteModule = async (modId: string) => {
if (!window.confirm('Excluir este módulo apagará todas as suas aulas associadas. Continuar?')) return;
if (!selectedCourse) return;
try {
const res = await fetch(`/api/admin/modules/${modId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
fetchCourseModulesAndLessons(selectedCourse.id);
}
} catch (err) {
console.error('Error deleting module:', err);
}
};
// --- LESSON CRUD ---
const handleSaveLesson = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedCourse) return;
const method = editingLesson ? 'PUT' : 'POST';
const url = editingLesson ? `/api/admin/lessons/${editingLesson.id}` : '/api/admin/lessons';
const payload = editingLesson ? { ...lessonForm } : { ...lessonForm, moduleId: activeModuleForLesson };
try {
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
});
if (res.ok) {
await fetchCourseModulesAndLessons(selectedCourse.id);
setShowLessonForm(false);
setEditingLesson(null);
setActiveModuleForLesson(null);
setLessonForm({ title: '', videoUrl: '', videoType: 'youtube', order: 1, content: '', thumbnailUrl: '' });
}
} catch (err) {
console.error('Error saving lesson:', err);
}
};
const handleNewLesson = (moduleId: string, lessonsCount: number) => {
setActiveModuleForLesson(moduleId);
setEditingLesson(null);
setLessonForm({
title: '',
videoUrl: '',
videoType: 'youtube',
order: lessonsCount + 1,
content: '',
thumbnailUrl: ''
});
setShowLessonForm(true);
setTimeout(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, 50);
};
const handleEditLesson = (lesson: Lesson) => {
setEditingLesson(lesson);
setActiveModuleForLesson(lesson.moduleId);
setLessonForm({
title: lesson.title,
videoUrl: lesson.videoUrl,
videoType: lesson.videoType,
order: lesson.order,
content: lesson.content || '',
thumbnailUrl: lesson.thumbnailUrl || ''
});
setShowLessonForm(true);
setTimeout(() => {
document.getElementById('admin-content-top')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 50);
};
const handleManageActivity = async (moduleId: string) => {
setActiveModuleForActivity(moduleId);
setActivityQuestions([]);
try {
const res = await fetch(`/api/admin/modules/${moduleId}/activity`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
const data = await res.json();
setActivityQuestions(data.questions || []);
}
} catch (err) {
console.error('Error fetching activity:', err);
}
setShowActivityModal(true);
};
const handleSaveActivity = async () => {
if (!activeModuleForActivity) return;
try {
const res = await fetch(`/api/admin/modules/${activeModuleForActivity}/activity`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ questions: activityQuestions })
});
if (res.ok) {
alert('Atividade salva com sucesso!');
setShowActivityModal(false);
} else {
alert('Erro ao salvar atividade.');
}
} catch (err) {
console.error('Error saving activity:', err);
alert('Erro ao salvar atividade.');
}
};
const handleDeleteLesson = async (lessonId: string) => {
if (!window.confirm('Excluir esta aula?')) return;
if (!selectedCourse) return;
try {
const res = await fetch(`/api/admin/lessons/${lessonId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
fetchCourseModulesAndLessons(selectedCourse.id);
}
} catch (err) {
console.error('Error deleting lesson:', err);
}
};
if (isLoading && courses.length === 0) {
return (
<div className="flex flex-col items-center justify-center min-h-[50vh] text-zinc-400 space-y-4">
<RefreshCw className="w-8 h-8 text-[#E50914] animate-spin" />
<p className="text-sm font-semibold">Carregando catálogo de treinamentos...</p>
</div>
);
}
return (
<div id="admin-content-top" className="space-y-8">
{/* Top Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between border-b border-white/10 pb-5 gap-4">
<div>
<h1 className="text-2xl md:text-3xl font-display font-extrabold text-white tracking-tight flex items-center space-x-2">
<GraduationCap className="w-8 h-8 text-[#E50914]" />
<span>Gestão de Cursos, Módulos e Aulas</span>
</h1>
<p className="text-xs text-zinc-500 mt-1">Crie cursos, adicione módulos e configure links de vídeo (YouTube ou Upload Direto).</p>
</div>
{!selectedCourse && !showCourseForm && (
<button
onClick={() => { setEditingCourse(null); setCourseForm({ title: '', description: '', thumbnail: '', category: '', isLocked: false, price: 0, certificateMode: 'FULL_COURSE' }); setShowCourseForm(true); }}
className="glass-btn-primary text-white font-bold text-xs px-4 py-2.5 rounded-lg flex items-center justify-center space-x-2 cursor-pointer self-start sm:self-auto"
>
<Plus className="w-4 h-4" />
<span>Adicionar Novo Curso</span>
</button>
)}
</div>
{/* Helper Banner for Questionnaires / Activities */}
{!selectedCourse && !showCourseForm && (
<div className="glass-card p-4 rounded-xl border border-emerald-500/30 bg-emerald-950/20 text-emerald-300 text-xs leading-relaxed flex items-center space-x-3">
<div className="p-2.5 bg-emerald-500/20 text-emerald-400 rounded-xl flex-shrink-0">
<CheckCircle className="w-6 h-6" />
</div>
<div className="space-y-1">
<p className="font-extrabold text-white text-sm">Como Criar as Questões e Atividades dos Alunos?</p>
<p className="text-zinc-300">
Escolha um curso abaixo clicando no botão <strong className="text-white bg-white/10 px-1.5 py-0.5 rounded">Gerenciar Módulos & Questões</strong>. Em seguida, em cada módulo, clique no botão verde <strong className="text-emerald-300 bg-emerald-500/30 border border-emerald-400/40 px-2 py-0.5 rounded">📝 Criar / Editar Questões</strong> para adicionar perguntas e respostas!
</p>
</div>
</div>
)}
{/* --- RENDER COURSE FORM --- */}
{showCourseForm && (
<div className="glass-card p-6 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-white/5 pb-4">
<h3 className="text-lg font-bold text-white">
{editingCourse ? 'Editar Detalhes do Curso' : 'Criar Novo Curso'}
</h3>
<button
onClick={() => setShowCourseForm(false)}
className="text-zinc-500 hover:text-zinc-300 text-xs font-semibold"
>
Cancelar
</button>
</div>
<form onSubmit={handleSaveCourse} className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider">Título do Curso</label>
<input
type="text"
required
value={courseForm.title}
onChange={(e) => setCourseForm({ ...courseForm, title: e.target.value })}
placeholder="Ex: Infraestrutura de Servidores Linux"
className="w-full glass-input rounded-lg p-3 text-sm text-white focus:outline-none"
/>
</div>
<div className="space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider">Categoria / Trilha</label>
<input
type="text"
required
value={courseForm.category}
onChange={(e) => setCourseForm({ ...courseForm, category: e.target.value })}
placeholder="Ex: Redes e Servidores"
className="w-full glass-input rounded-lg p-3 text-sm text-white focus:outline-none"
/>
</div>
<div className="md:col-span-2 space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider">Imagem de Capa (Thumbnail URL)</label>
<div className="flex gap-2">
<input
type="url"
required
value={courseForm.thumbnail}
onChange={(e) => setCourseForm({ ...courseForm, thumbnail: e.target.value })}
placeholder="https://images.unsplash.com/... ou link de imagem direta"
className="flex-1 glass-input rounded-lg p-3 text-sm text-white focus:outline-none"
/>
<label className="bg-zinc-800 hover:bg-zinc-700 text-white text-xs font-bold py-3 px-4 rounded-lg border border-white/10 transition-all flex items-center justify-center space-x-1.5 cursor-pointer select-none">
{uploadingImage ? 'Enviando...' : 'Upload S3'}
<input
type="file"
accept="image/*"
onChange={handleImageUpload}
disabled={uploadingImage}
className="hidden"
/>
</label>
</div>
</div>
<div className="space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider">Preço do Curso para Desbloqueio (R$)</label>
<input
type="number"
step="0.01"
min="0"
value={courseForm.price}
onChange={(e) => setCourseForm({ ...courseForm, price: Number(e.target.value) })}
className="w-full glass-input rounded-lg p-3 text-sm text-white focus:outline-none"
/>
</div>
<div className="space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider">Modo do Certificado</label>
<select
value={courseForm.certificateMode}
onChange={(e) => setCourseForm({ ...courseForm, certificateMode: e.target.value as 'FULL_COURSE' | 'PER_MODULE' })}
className="w-full glass-input rounded-lg p-3 text-sm text-white focus:outline-none appearance-none"
>
<option value="FULL_COURSE">Por Curso Completo (Ao finalizar tudo)</option>
<option value="PER_MODULE">Por Módulo (Certificado para cada módulo)</option>
</select>
</div>
<div className="md:col-span-2 space-y-2 flex flex-col justify-center">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider mb-2">Status de Bloqueio</label>
<label className="inline-flex items-center space-x-3 cursor-pointer">
<input
type="checkbox"
checked={courseForm.isLocked}
onChange={(e) => setCourseForm({ ...courseForm, isLocked: e.target.checked })}
className="rounded bg-zinc-900 border-white/10 text-[#E50914] focus:ring-[#E50914] focus:ring-offset-0 w-4 h-4"
/>
<span className="text-sm font-semibold text-zinc-300">Curso Trancado (Requer pagamento ou desbloqueio manual)</span>
</label>
</div>
<div className="md:col-span-2 space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase tracking-wider">Descrição Detalhada</label>
<textarea
required
value={courseForm.description}
onChange={(e) => setCourseForm({ ...courseForm, description: e.target.value })}
placeholder="Fale brevemente sobre o objetivo deste treinamento, o que o aluno vai aprender..."
className="w-full h-24 glass-input rounded-lg p-3 text-sm text-white focus:outline-none resize-none"
/>
</div>
<div className="md:col-span-2 flex justify-end">
<button
type="submit"
className="glass-btn-primary text-white font-extrabold text-xs md:text-sm py-3 px-6 rounded-xl shadow-lg hover:scale-105 transition-all flex items-center gap-2 cursor-pointer"
>
<CheckCircle2 size={18} />
<span>{editingCourse ? 'Salvar Alterações' : 'Criar Curso'}</span>
</button>
</div>
</form>
</div>
)}
{/* --- RENDER COURSE DETAIL / CURRICULUM MANAGER --- */}
{selectedCourse ? (
<div id="admin-content-top" className="space-y-6">
{/* Active Course Breadcrumb */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 glass-card p-4 rounded-xl">
<button
onClick={() => setSelectedCourse(null)}
className="flex items-center space-x-1.5 text-zinc-400 hover:text-white transition-colors text-xs font-bold"
>
<ArrowLeft className="w-4 h-4" />
<span>Voltar para Catálogo Completo</span>
</button>
<div className="flex items-center space-x-3">
<span className="text-zinc-600 text-xs font-bold">Curso Selecionado:</span>
<span className="text-white text-xs font-bold bg-[#E50914] px-2.5 py-1 rounded">
{selectedCourse.title}
</span>
</div>
<div className="flex space-x-2">
<button
onClick={() => { setEditingModule(null); setModuleForm({ title: '', order: modules.length + 1 }); setShowModuleForm(true); }}
className="glass-btn-secondary text-white text-xs font-bold px-3 py-1.5 rounded flex items-center space-x-1"
>
<FolderPlus className="w-3.5 h-3.5 text-zinc-400" />
<span>Criar Módulo</span>
</button>
</div>
</div>
{/* --- MODULE FORM DIALOG --- */}
{showModuleForm && (
<div className="glass-card p-6 rounded-2xl border-[#E50914]/30 space-y-4">
<h4 className="text-sm font-bold text-white uppercase tracking-wider">
{editingModule ? 'Editar Módulo' : 'Adicionar Novo Módulo'}
</h4>
<form onSubmit={handleSaveModule} className="flex flex-col sm:flex-row gap-4 items-end">
<div className="flex-1 space-y-1">
<label className="text-[10px] text-zinc-500 font-bold uppercase">Título do Módulo</label>
<input
type="text"
required
value={moduleForm.title}
onChange={(e) => setModuleForm({ ...moduleForm, title: e.target.value })}
placeholder="Ex: Módulo 1 - Fundamentos"
className="w-full glass-input rounded-lg p-2.5 text-xs text-white focus:outline-none"
/>
</div>
<div className="w-24 space-y-1">
<label className="text-[10px] text-zinc-500 font-bold uppercase">Ordem</label>
<input
type="number"
required
value={moduleForm.order}
onChange={(e) => setModuleForm({ ...moduleForm, order: Number(e.target.value) })}
className="w-full glass-input rounded-lg p-2.5 text-xs text-white focus:outline-none"
/>
</div>
<div className="flex space-x-2">
<button type="submit" className="glass-btn-primary text-white font-extrabold text-xs py-2.5 px-6 rounded-xl shadow-lg hover:scale-105 transition-all flex items-center gap-2 cursor-pointer">
<CheckCircle2 size={16} />
<span>Salvar Módulo</span>
</button>
<button type="button" onClick={() => setShowModuleForm(false)} className="text-xs text-zinc-500 px-2">
Cancelar
</button>
</div>
</form>
</div>
)}
{/* --- LESSON FORM DIALOG --- */}
{showLessonForm && (
<div className="glass-card p-6 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-white/5 pb-4">
<h4 className="text-sm font-bold text-white uppercase tracking-wider">
{editingLesson ? 'Editar Aula' : 'Adicionar Nova Aula'}
</h4>
<button type="button" onClick={() => setShowLessonForm(false)} className="text-xs text-zinc-500">
Cancelar
</button>
</div>
<form onSubmit={handleSaveLesson} className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="md:col-span-2 space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase">Título da Aula</label>
<input
type="text"
required
value={lessonForm.title}
onChange={(e) => setLessonForm({ ...lessonForm, title: e.target.value })}
placeholder="Ex: Como configurar SSH no Servidor Linux"
className="w-full glass-input rounded-lg p-3 text-xs text-white focus:outline-none"
/>
</div>
<div className="space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase">Ordem Numérica</label>
<input
type="number"
required
value={lessonForm.order}
onChange={(e) => setLessonForm({ ...lessonForm, order: Number(e.target.value) })}
className="w-full glass-input rounded-lg p-3 text-xs text-white focus:outline-none"
/>
</div>
<div className="space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase">Tipo de Vídeo</label>
<select
value={lessonForm.videoType}
onChange={(e) => setLessonForm({ ...lessonForm, videoType: e.target.value as 'youtube' | 'direct' })}
className="w-full glass-input rounded-lg p-3 text-xs text-white focus:outline-none"
>
<option value="youtube">YouTube Embed (Privado/Não Listado)</option>
<option value="direct">Upload Direto / Link HTML5 MP4</option>
</select>
</div>
<div className="md:col-span-2 space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase">
{lessonForm.videoType === 'youtube'
? 'Link do Vídeo no YouTube (ou ID do vídeo)'
: 'Link URL do arquivo de vídeo direto (.mp4 / HLS)'}
</label>
<input
type="text"
required
value={lessonForm.videoUrl}
onChange={(e) => setLessonForm({ ...lessonForm, videoUrl: e.target.value })}
placeholder={lessonForm.videoType === 'youtube'
? 'Ex: https://www.youtube.com/watch?v=K8w3m_pM6lA'
: 'Ex: https://meuservidor.com/aulas/aula1.mp4'}
className="w-full glass-input rounded-lg p-3 text-xs text-white focus:outline-none"
/>
</div>
<div className="md:col-span-3 space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase">Imagem de Capa da Aula (Thumbnail URL - Opcional)</label>
<input
type="text"
value={lessonForm.thumbnailUrl}
onChange={(e) => setLessonForm({ ...lessonForm, thumbnailUrl: e.target.value })}
placeholder="Cole a URL da imagem de capa para esta aula (ex: https://meuservidor.com/capa.jpg)"
className="w-full glass-input rounded-lg p-3 text-xs text-white focus:outline-none"
/>
</div>
<div className="md:col-span-3 space-y-2">
<label className="text-xs text-zinc-400 font-bold uppercase font-sans">Recursos Extras & Material de Apoio (Opcional)</label>
<textarea
value={lessonForm.content}
onChange={(e) => setLessonForm({ ...lessonForm, content: e.target.value })}
placeholder="Cole códigos de exemplo, links de arquivos zip ou notas de apoio aqui..."
className="w-full h-24 glass-input rounded-lg p-3 text-xs text-white focus:outline-none"
/>
</div>
<div className="md:col-span-3 flex justify-end">
<button type="submit" className="glass-btn-primary text-white font-extrabold text-xs md:text-sm py-3 px-6 rounded-xl shadow-lg hover:scale-105 transition-all flex items-center gap-2 cursor-pointer">
<CheckCircle2 size={18} />
<span>{editingLesson ? 'Salvar Aula' : 'Criar Aula'}</span>
</button>
</div>
</form>
</div>
)}
{/* Curriculum Index (Modules & Lessons list) */}
<div className="space-y-4">
{modules.length === 0 ? (
<div className="p-8 text-center glass-card rounded-xl text-zinc-500">
<Layers className="w-8 h-8 mx-auto text-zinc-600 mb-2" />
<p className="text-sm font-semibold">Nenhum módulo cadastrado neste curso.</p>
<button
onClick={() => { setEditingModule(null); setModuleForm({ title: '', order: 1 }); setShowModuleForm(true); }}
className="mt-3 text-xs text-[#E50914] font-bold"
>
Criar primeiro módulo agora
</button>
</div>
) : (
modules.map((mod, modIdx) => (
<div key={mod.id} className="glass-card rounded-xl overflow-hidden">
{/* Module Header Panel */}
<div className="glass-accordion-header px-4 py-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div className="flex items-center space-x-2">
<span className="bg-zinc-800 border border-zinc-700 text-[#E50914] text-[10px] font-extrabold px-1.5 py-0.5 rounded">
M{mod.order}
</span>
<h4 className="font-sans font-extrabold text-sm text-zinc-100 tracking-tight">
{mod.title}
</h4>
</div>
<div className="flex items-center space-x-2">
{/* Add Lesson */}
<button
onClick={() => handleNewLesson(mod.id, mod.lessons.length)}
className="bg-zinc-800 text-zinc-300 hover:text-white hover:bg-zinc-700 text-[10px] font-bold px-2 py-1 rounded flex items-center space-x-1 border border-zinc-700 transition-colors"
>
<FilePlus className="w-3 h-3 text-[#E50914]" />
<span>Nova Aula</span>
</button>
{/* Manage Activity / Questions */}
<button
onClick={() => handleManageActivity(mod.id)}
className="bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-extrabold px-3 py-1.5 rounded-lg flex items-center space-x-1.5 border border-emerald-400/40 transition-all shadow-[0_0_12px_rgba(16,185,129,0.35)] cursor-pointer"
title="Criar ou editar o questionário deste módulo"
>
<CheckCircle className="w-4 h-4 text-white" />
<span>📝 Criar / Editar Questões</span>
</button>
{/* Edit Module */}
<button
onClick={() => handleEditModule(mod)}
className="text-zinc-500 hover:text-zinc-300 p-1"
title="Editar Módulo"
>
<Edit className="w-3.5 h-3.5" />
</button>
{/* Delete Module */}
<button
onClick={() => handleDeleteModule(mod.id)}
className="text-zinc-500 hover:text-red-400 p-1"
title="Excluir Módulo"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
{/* Lessons Listing inside Module */}
<div className="divide-y divide-white/5 bg-black/10">
{mod.lessons.length === 0 ? (
<div className="p-4 text-xs text-zinc-600 italic text-center">Nenhuma aula cadastrada neste módulo.</div>
) : (
mod.lessons.map(lesson => (
<div key={lesson.id} className="px-6 py-3 flex items-center justify-between text-xs hover:bg-zinc-900/10 transition-colors">
<div className="flex items-center space-x-3 min-w-0">
<span className="text-zinc-500 font-mono font-bold w-4 text-right">#{lesson.order}</span>
{lesson.videoType === 'youtube' ? (
<Youtube className="w-4 h-4 text-red-500 flex-shrink-0" />
) : (
<Film className="w-4 h-4 text-emerald-500 flex-shrink-0" />
)}
<span className="font-semibold text-zinc-300 truncate max-w-sm sm:max-w-md md:max-w-lg">
{lesson.title}
</span>
</div>
<div className="flex items-center space-x-2 ml-4">
{/* Edit */}
<button
onClick={() => handleEditLesson(lesson)}
className="text-zinc-500 hover:text-white p-1"
title="Editar Aula"
>
<Edit className="w-3.5 h-3.5" />
</button>
{/* Delete */}
<button
onClick={() => handleDeleteLesson(lesson.id)}
className="text-zinc-500 hover:text-red-400 p-1"
title="Excluir Aula"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
))
)}
</div>
</div>
))
)}
</div>
</div>
) : (
/* --- LIST ALL COURSES IN CATALOGUE --- */
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{courses.map(course => (
<div
key={course.id}
className="glass-card rounded-2xl overflow-hidden flex flex-col justify-between"
>
<div>
{/* Thumbnail */}
<div className="relative w-full aspect-video">
<img src={course.thumbnail} alt={course.title} className="w-full h-full object-cover" referrerPolicy="no-referrer" />
<span className="absolute top-3 left-3 bg-[#E50914]/85 text-white text-[10px] font-black px-2 py-0.5 rounded uppercase tracking-wider glass-badge">
{course.category}
</span>
</div>
{/* Info */}
<div className="p-5 space-y-2">
<h3 className="font-display font-bold text-base text-white tracking-tight line-clamp-1">{course.title}</h3>
<p className="text-xs text-zinc-500 leading-relaxed line-clamp-3">{course.description}</p>
</div>
</div>
{/* Actions Footer */}
<div className="p-5 border-t border-white/5 bg-white/3 flex items-center justify-between">
<button
onClick={() => handleSelectCourse(course)}
className="glass-btn-primary text-white text-xs font-bold px-3.5 py-2 rounded-lg flex items-center space-x-1.5 cursor-pointer shadow-md hover:scale-105 transition-all"
>
<Layers className="w-4 h-4 text-white" />
<span>Gerenciar Módulos & Questões</span>
<ChevronRight className="w-3.5 h-3.5" />
</button>
<div className="flex space-x-1.5">
<button
onClick={() => handleEditCourse(course)}
className="p-2 text-zinc-400 hover:text-white rounded bg-zinc-800/30 hover:bg-zinc-800 border border-zinc-850 transition-all"
title="Editar Curso"
>
<Edit className="w-4 h-4" />
</button>
<button
onClick={() => handleDeleteCourse(course.id)}
className="p-2 text-zinc-400 hover:text-red-400 rounded bg-zinc-800/30 hover:bg-zinc-800 border border-zinc-850 transition-all"
title="Excluir Curso"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
)}
{/* --- ACTIVITY MODAL --- */}
{showActivityModal && (
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4 overflow-y-auto">
<div className="bg-zinc-900 border border-white/10 rounded-2xl w-full max-w-4xl p-6 relative flex flex-col max-h-[90vh]">
<h3 className="text-xl font-bold text-white mb-4">Gerenciar Atividades do Módulo</h3>
<div className="flex-1 overflow-y-auto space-y-6 pr-2">
{activityQuestions.map((q, qIndex) => (
<div key={qIndex} className="bg-black/50 border border-white/5 rounded-xl p-4 space-y-4">
<div className="flex justify-between items-center">
<h4 className="text-sm font-bold text-zinc-300">Pergunta {qIndex + 1}</h4>
<button
onClick={() => setActivityQuestions(activityQuestions.filter((_, i) => i !== qIndex))}
className="text-red-400 hover:text-red-300 text-xs"
>
Remover Pergunta
</button>
</div>
<input
type="text"
value={q.question}
onChange={(e) => {
const newQ = [...activityQuestions];
newQ[qIndex].question = e.target.value;
setActivityQuestions(newQ);
}}
placeholder="Digite a pergunta..."
className="w-full glass-input rounded-lg p-3 text-sm text-white focus:outline-none"
/>
<div className="space-y-2 pl-4 border-l-2 border-white/10">
<label className="text-xs font-bold text-zinc-500 uppercase">Opções (Marque a correta)</label>
{q.options.map((opt, optIndex) => (
<div key={optIndex} className="flex items-center space-x-2">
<input
type="radio"
name={`correct-${qIndex}`}
checked={q.correctIndex === optIndex}
onChange={() => {
const newQ = [...activityQuestions];
newQ[qIndex].correctIndex = optIndex;
setActivityQuestions(newQ);
}}
className="w-4 h-4"
/>
<input
type="text"
value={opt}
onChange={(e) => {
const newQ = [...activityQuestions];
newQ[qIndex].options[optIndex] = e.target.value;
setActivityQuestions(newQ);
}}
placeholder={`Opção ${optIndex + 1}`}
className="w-full bg-zinc-800/50 border border-white/5 rounded p-2 text-xs text-white focus:outline-none"
/>
</div>
))}
</div>
</div>
))}
<button
onClick={() => setActivityQuestions([...activityQuestions, { question: '', options: ['', '', '', ''], correctIndex: 0 }])}
className="w-full py-3 border border-dashed border-white/20 text-zinc-400 hover:text-white hover:border-white/50 rounded-xl text-sm font-bold transition-colors"
>
+ Adicionar Pergunta
</button>
</div>
<div className="mt-6 flex justify-end space-x-4 border-t border-white/10 pt-4">
<button onClick={() => setShowActivityModal(false)} className="px-4 py-2 text-sm text-zinc-400 hover:text-white">
Cancelar
</button>
<button onClick={handleSaveActivity} className="glass-btn-primary text-white font-extrabold text-xs md:text-sm py-3 px-6 rounded-xl shadow-lg hover:scale-105 transition-all flex items-center gap-2 cursor-pointer">
<CheckCircle2 size={18} />
<span>Salvar Atividade</span>
</button>
</div>
</div>
</div>
)}
</div>
);
}