import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ArrowDown, ArrowLeft, ArrowUp, BookOpen, ExternalLink, Loader2, Pencil, Plus, Save, Trash2, Upload, X } from 'lucide-react'; import { useNavigate, useParams } from 'react-router-dom'; import { adminApi, instructorApi, LearningPath, LearningPathInput } from '../services/api'; import { deleteCourse, getManagedCourses } from '../services/db'; import { CourseCoverImage } from '../components/CourseCoverImage'; import { useToast } from '../context/ToastContext'; type CourseOption = { id: string; title: string; status: string; category: string; coverImageUrl: string | null }; const emptyInput = (): LearningPathInput => ({ title: '', description: '', coverImageUrl: null, coverImageZoom: 1, coverImagePositionX: 50, coverImagePositionY: 50, status: 'draft', courseIds: [] }); const toInput = (trail: LearningPath): LearningPathInput => ({ title: trail.title, description: trail.description, coverImageUrl: trail.coverImageUrl, coverImageZoom: trail.coverImageZoom, coverImagePositionX: trail.coverImagePositionX, coverImagePositionY: trail.coverImagePositionY, status: trail.status === 'published' ? 'published' : 'draft', courseIds: trail.courses.map((course) => course.id) }); export const TrailDetail: React.FC = () => { const { trailId } = useParams<{ trailId: string }>(); const isNew = trailId === 'nova'; const navigate = useNavigate(); const { showToast } = useToast(); const coverInput = useRef(null); const [trail, setTrail] = useState(null); const [courses, setCourses] = useState([]); const [input, setInput] = useState(emptyInput()); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isUploading, setIsUploading] = useState(false); const [courseToAdd, setCourseToAdd] = useState(''); const load = async () => { setIsLoading(true); try { const [pathsResponse, managedCourses] = await Promise.all([adminApi.listPaths(), getManagedCourses()]); setCourses(managedCourses.map((course) => ({ id: course.id, title: course.title, status: course.status || 'draft', category: course.category, coverImageUrl: course.thumbnail || null, }))); if (!isNew) { const found = pathsResponse.data.find((item) => item.id === trailId); if (!found) { showToast('Trilha não encontrada.', 'error'); navigate('/admin'); return; } setTrail(found); setInput(toInput(found)); } } catch { showToast('Não foi possível carregar a trilha.', 'error'); } finally { setIsLoading(false); } }; useEffect(() => { void load(); }, [trailId]); const selectedCourses = useMemo(() => input.courseIds.map((id) => courses.find((course) => course.id === id)).filter((course): course is CourseOption => Boolean(course)), [courses, input.courseIds]); const availableCourses = useMemo(() => courses.filter((course) => !input.courseIds.includes(course.id)), [courses, input.courseIds]); const addSelectedCourse = () => { if (!courseToAdd) return; setInput((current) => ({ ...current, courseIds: [...current.courseIds, courseToAdd] })); setCourseToAdd(''); }; const moveCourse = (courseId: string, direction: -1 | 1) => setInput((current) => { const index = current.courseIds.indexOf(courseId); const target = index + direction; if (index < 0 || target < 0 || target >= current.courseIds.length) return current; const courseIds = [...current.courseIds]; [courseIds[index], courseIds[target]] = [courseIds[target], courseIds[index]]; return { ...current, courseIds }; }); const uploadCover = async (file?: File) => { if (!file) return; setIsUploading(true); try { const response = await instructorApi.uploadCover(file); setInput((current) => ({ ...current, coverImageUrl: response.data.coverImageUrl })); showToast('Capa enviada. Salve a trilha para confirmar.', 'success'); } catch { showToast('Não foi possível enviar a capa.', 'error'); } finally { setIsUploading(false); } }; const save = async (event: React.FormEvent) => { event.preventDefault(); if (input.status === 'published' && input.courseIds.length === 0) { showToast('Adicione pelo menos um curso antes de publicar a trilha.', 'error'); return; } setIsSaving(true); try { const response = isNew ? await adminApi.createPath(input) : await adminApi.updatePath(trail!.id, input); showToast(isNew ? 'Trilha criada.' : 'Trilha atualizada.', 'success'); navigate(`/admin/trilhas/${response.data.id}`, { replace: true }); setTrail(response.data); setInput(toInput(response.data)); } catch (error) { showToast(error instanceof Error ? error.message : 'Não foi possível salvar a trilha.', 'error'); } finally { setIsSaving(false); } }; const removeCourse = (courseId: string) => setInput((current) => ({ ...current, courseIds: current.courseIds.filter((id) => id !== courseId) })); const archiveCourse = async (course: CourseOption) => { if (!window.confirm(`Arquivar “${course.title}”? Ele será removido desta trilha e deixará de aparecer para alunos.`)) return; try { await deleteCourse(course.id); setCourses((current) => current.filter((item) => item.id !== course.id)); removeCourse(course.id); showToast('Curso arquivado e removido da trilha.', 'success'); } catch { showToast('Não foi possível arquivar este curso.', 'error'); } }; const deleteTrail = async () => { if (!trail || !window.confirm(`Excluir a trilha “${trail.title}”? Os cursos serão preservados.`)) return; try { await adminApi.deletePath(trail.id); showToast('Trilha excluída. Os cursos foram preservados.', 'success'); navigate('/admin'); } catch { showToast('Não foi possível excluir a trilha.', 'error'); } }; if (isLoading) return
; return
{trail?.status === 'published' && }
{ void uploadCover(event.target.files?.[0]); event.currentTarget.value = ''; }} />

{isNew ? 'Nova trilha' : 'Trilha de cursos'}