import React, { useState, useEffect } from 'react'; import { Plus, Settings, Users, BarChart, Trash2, Edit2, X, MessageSquare, Send, Image as ImageIcon, Video, Loader2, FileText, FileSpreadsheet, FileArchive, Link as LinkIcon, Paperclip, Lightbulb, ListVideo, Check, Lock, Unlock, Globe, Download ,Copy, ChevronUp, ChevronDown } from 'lucide-react'; import { Course, Comment, Attachment, Lesson, AttachmentType, LessonMedia } from '../types'; import { useToast } from '../context/ToastContext'; import { getManagedCourses, saveCourse, deleteCourse, duplicateCourse, getComments, replyToComment, moderateComment } from '../services/db'; import { getAttachmentIcon, getAttachmentBadge } from '../components/MaterialCard'; import { BunnyVideo, courseApi, instructorApi, InstructorAnalytics } from '../services/api'; // 1. Create/Edit Course Modal const CourseModal: React.FC<{ isOpen: boolean; onClose: () => void; onSave: (course: Course) => Promise; initialData?: Course; }> = ({ isOpen, onClose, onSave, initialData }) => { const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [category, setCategory] = useState(''); const [status, setStatus] = useState('draft'); const [thumbnailPreview, setThumbnailPreview] = useState(''); const [categories, setCategories] = useState([]); // Lesson Management const [lessons, setLessons] = useState([]); // New Lesson State const [newLessonTitle, setNewLessonTitle] = useState(''); const [newLessonDuration, setNewLessonDuration] = useState(''); const [newLessonVideoUrl, setNewLessonVideoUrl] = useState(''); const [newLessonProvider, setNewLessonProvider] = useState('external'); const [newLessonMedia, setNewLessonMedia] = useState(null); const [newLessonIsFree, setNewLessonIsFree] = useState(false); const [bunnyConfigured, setBunnyConfigured] = useState(null); const [bunnyUploadState, setBunnyUploadState] = useState<'idle' | 'uploading' | 'uploaded' | 'error'>('idle'); const [bunnyUploadMessage, setBunnyUploadMessage] = useState(''); // Tips & Global Attachments const [tips, setTips] = useState([]); const [newTip, setNewTip] = useState(''); const [attachments, setAttachments] = useState([]); // New Attachment State const [newAttName, setNewAttName] = useState(''); const [newAttType, setNewAttType] = useState('pdf'); const [newAttUrl, setNewAttUrl] = useState(''); const [newAttSize, setNewAttSize] = useState(''); const [newAttDesc, setNewAttDesc] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { if (isOpen) { if (initialData) { setTitle(initialData.title); setDescription(initialData.description); setCategory(initialData.category); setStatus(initialData.status || 'published'); setThumbnailPreview(initialData.thumbnail); setTips(initialData.tips || []); setAttachments(initialData.attachments || []); setLessons(initialData.lessons || []); } else { setTitle(''); setDescription(''); setCategory(''); setStatus('draft'); setThumbnailPreview(''); setTips([]); setAttachments([]); setLessons([]); } setNewLessonMedia(null); setBunnyUploadState('idle'); setBunnyUploadMessage(''); instructorApi.bunnyConfiguration() .then((response) => setBunnyConfigured(response.data.configured)) .catch(() => setBunnyConfigured(false)); courseApi.categories() .then((response) => { setCategories(response.data); if (!initialData) setCategory((current) => current || response.data[0] || ''); }) .catch(() => setCategories([])); } }, [isOpen, initialData]); const toBunnyMedia = (video: BunnyVideo): LessonMedia => ({ provider: 'bunny', externalId: video.id, status: video.status, durationSeconds: video.durationSeconds, }); const handleBunnyUpload = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; event.target.value = ''; if (!file) return; if (!newLessonTitle.trim()) { alert('Informe o título da aula antes de enviar o vídeo.'); return; } if (!bunnyConfigured) { alert('A integração com Bunny Stream ainda não foi configurada pelo administrador.'); return; } setBunnyUploadState('uploading'); setBunnyUploadMessage(`Enviando ${file.name} para Bunny Stream...`); try { const created = await instructorApi.createBunnyVideo(newLessonTitle.trim()); const uploaded = await instructorApi.uploadBunnyVideo(created.data.id, file); const media = toBunnyMedia(uploaded.data); setNewLessonMedia(media); if (!newLessonDuration.trim() && uploaded.data.durationSeconds) { const minutes = Math.max(1, Math.round(uploaded.data.durationSeconds / 60)); setNewLessonDuration(`${minutes}m`); } setBunnyUploadState('uploaded'); setBunnyUploadMessage(uploaded.data.status === 'ready' ? 'Vídeo pronto para ser adicionado à aula.' : 'Upload concluído. Bunny Stream está processando o vídeo; você já pode salvar a aula.'); } catch (error) { setBunnyUploadState('error'); setBunnyUploadMessage(error instanceof Error ? error.message : 'Não foi possível enviar o vídeo para Bunny Stream.'); } }; const refreshLessonMedia = async (lessonId: string) => { const lesson = lessons.find((item) => item.id === lessonId); const media = lesson?.media?.[0]; if (!lesson || !media || media.provider !== 'bunny') return; try { const response = await instructorApi.bunnyVideoStatus(media.externalId); const refreshed = toBunnyMedia(response.data); setLessons((current) => current.map((item) => item.id === lessonId ? { ...item, duration: refreshed.durationSeconds ? `${Math.max(1, Math.round(refreshed.durationSeconds / 60))}m` : item.duration, media: [refreshed], } : item)); } catch (error) { alert(error instanceof Error ? error.message : 'Não foi possível atualizar o status do vídeo.'); } }; // Bunny encodes asynchronously. While the course editor stays open, keep // pending lessons up to date and persist the latest state on the API. useEffect(() => { if (!isOpen) return; const pending = lessons .map((lesson) => ({ lesson, media: lesson.media?.[0] })) .filter((item): item is { lesson: Lesson; media: LessonMedia } => item.media?.provider === 'bunny' && item.media.status === 'processing'); if (!pending.length) return; const timer = window.setTimeout(() => { Promise.all(pending.map(async ({ lesson, media }) => { try { const response = await instructorApi.bunnyVideoStatus(media.externalId); return { lessonId: lesson.id, media: toBunnyMedia(response.data) }; } catch { return null; } })).then((updates) => { const available = updates.filter((update): update is { lessonId: string; media: LessonMedia } => Boolean(update)); if (!available.length) return; setLessons((current) => current.map((lesson) => { const update = available.find((item) => item.lessonId === lesson.id); if (!update) return lesson; return { ...lesson, duration: update.media.durationSeconds ? `${Math.max(1, Math.round(update.media.durationSeconds / 60))}m` : lesson.duration, media: [update.media], }; })); }); }, 10_000); return () => window.clearTimeout(timer); }, [isOpen, lessons]); const handleAddLesson = () => { if (!newLessonTitle.trim()) { alert("Digite um título para a aula."); return; } if (!newLessonMedia && !newLessonVideoUrl.trim()) { alert('Envie um vídeo para Bunny Stream ou informe uma URL de vídeo hospedado.'); return; } const newLesson: Lesson = { id: `lesson-${Date.now()}`, title: newLessonTitle, duration: newLessonDuration || '15m', videoUrl: newLessonMedia?.playbackUrl || newLessonVideoUrl, mediaProvider: newLessonMedia?.provider || newLessonProvider || 'external', media: newLessonMedia ? [newLessonMedia] : undefined, isFree: newLessonIsFree, description: '' }; setLessons([...lessons, newLesson]); setNewLessonTitle(''); setNewLessonDuration(''); setNewLessonVideoUrl(''); setNewLessonProvider('external'); setNewLessonMedia(null); setNewLessonIsFree(false); setBunnyUploadState('idle'); setBunnyUploadMessage(''); }; const removeLesson = (id: string) => { setLessons(lessons.filter(l => l.id !== id)); }; const toggleLessonAccess = (id: string) => { setLessons(lessons.map(l => l.id === id ? { ...l, isFree: !l.isFree } : l)); }; const moveLesson = (id: string, direction: -1 | 1) => { setLessons((current) => { const index = current.findIndex((lesson) => lesson.id === id); const nextIndex = index + direction; if (index < 0 || nextIndex < 0 || nextIndex >= current.length) return current; const next = [...current]; [next[index], next[nextIndex]] = [next[nextIndex], next[index]]; return next; }); }; const handleAddTip = () => { if (newTip.trim()) { setTips([...tips, newTip.trim()]); setNewTip(''); } }; const removeTip = (idx: number) => { setTips(tips.filter((_, i) => i !== idx)); }; const handleAddAttachment = () => { if (!newAttName.trim()) { alert("Informe o nome do arquivo para download."); return; } const att: Attachment = { id: `att-${Date.now()}`, name: newAttName.trim(), type: newAttType, url: newAttUrl.trim() || '#', size: newAttSize.trim() || '1.5 MB', description: newAttDesc.trim() }; setAttachments([...attachments, att]); setNewAttName(''); setNewAttSize(''); setNewAttUrl(''); setNewAttDesc(''); }; const removeAttachment = (id: string) => { setAttachments(attachments.filter(a => a.id !== id)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (lessons.length === 0) { alert("Adicione pelo menos uma aula à trilha."); return; } setIsSubmitting(true); const durationStr = `${lessons.length} Aulas`; try { await onSave({ id: initialData?.id || `course-${Date.now()}`, title, description, category: category || categories[0], status, thumbnail: thumbnailPreview || '/course-placeholder.svg', duration: durationStr, instructor: initialData?.instructor || '', views: initialData?.views || 0, progress: initialData?.progress || 0, lessons: lessons, tips: tips, attachments: attachments }); onClose(); } catch (error) { alert(error instanceof Error ? error.message : 'Não foi possível salvar este curso.'); } finally { setIsSubmitting(false); } }; if (!isOpen) return null; return (
{/* Left Column: Course Details, Tips & Downloads */}

{initialData ? 'Editar Curso' : 'Criar Curso'}

{/* Thumbnail */}
{thumbnailPreview ? ( <> Preview
Alterar Capa
) : (

Add a hosted cover image URL below

)}
setThumbnailPreview(event.target.value)} placeholder="Hosted cover image URL" className="w-full mt-2 bg-black/50 text-white text-xs border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-orange-500/50" />
setTitle(e.target.value)} placeholder="Ex: Masterclass: Escala de Tráfego Pago" className="w-full bg-black/50 text-white text-base font-bold border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-orange-500/50" required />