import React, { useEffect, useMemo, useState } from 'react'; import { ArrowLeft, BookOpen, Download, ExternalLink, Filter, FolderOpen, LayoutGrid, List, Loader2, Play, Search, X } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { Attachment, AttachmentType, Course } from '../types'; import { getCourses } from '../services/db'; import { getAttachmentBadge, getAttachmentIcon } from '../components/MaterialCard'; import { useToast } from '../context/ToastContext'; interface MaterialsPageProps { onPlayCourse: (course: Course) => void; } type Material = { id: string; attachment: Attachment; course: Course; lessonTitle?: string; }; type SortOption = 'recent' | 'name' | 'type'; const filterTypes: Array<{ value: 'all' | AttachmentType; label: string }> = [ { value: 'all', label: 'Todos os formatos' }, { value: 'pdf', label: 'PDF' }, { value: 'spreadsheet', label: 'Planilha' }, { value: 'doc', label: 'Documento' }, { value: 'zip', label: 'Arquivo compactado' }, { value: 'image', label: 'Imagem' }, { value: 'link', label: 'Link' }, ]; const formatDate = (date?: string) => date ? new Intl.DateTimeFormat('pt-BR', { dateStyle: 'medium' }).format(new Date(date)) : 'Data não informada'; const materialDate = (material: Material) => { const value = material.attachment.createdAt ? new Date(material.attachment.createdAt).getTime() : 0; return Number.isNaN(value) ? 0 : value; }; export const MaterialsPage: React.FC = ({ onPlayCourse }) => { const navigate = useNavigate(); const { showDownloadToast, showToast } = useToast(); const [courses, setCourses] = useState([]); const [isLoading, setIsLoading] = useState(true); const [query, setQuery] = useState(''); const [selectedTrail, setSelectedTrail] = useState('all'); const [selectedCourse, setSelectedCourse] = useState('all'); const [selectedType, setSelectedType] = useState<'all' | AttachmentType>('all'); const [sort, setSort] = useState('recent'); const [view, setView] = useState<'grid' | 'list'>('grid'); useEffect(() => { window.scrollTo(0, 0); getCourses().then(setCourses).catch(() => showToast('Não foi possível carregar os materiais.', 'error')).finally(() => setIsLoading(false)); }, [showToast]); const materials = useMemo(() => courses.flatMap((course) => [ ...(course.attachments || []).map((attachment) => ({ id: `course-${course.id}-${attachment.id}`, attachment, course })), ...course.lessons.flatMap((lesson) => (lesson.attachments || []).map((attachment) => ({ id: `lesson-${course.id}-${lesson.id}-${attachment.id}`, attachment, course, lessonTitle: lesson.title }))), ]), [courses]); const trails = useMemo(() => Array.from(new Set(courses.map((course) => course.category))).filter(Boolean).sort((a, b) => a.localeCompare(b, 'pt-BR')), [courses]); const courseOptions = useMemo(() => courses.filter((course) => selectedTrail === 'all' || course.category === selectedTrail).sort((a, b) => a.title.localeCompare(b.title, 'pt-BR')), [courses, selectedTrail]); const filteredMaterials = useMemo(() => materials.filter((material) => { const normalized = query.trim().toLocaleLowerCase('pt-BR'); const searchMatches = !normalized || [material.attachment.name, material.attachment.description, material.course.title, material.course.category, material.lessonTitle].some((value) => value?.toLocaleLowerCase('pt-BR').includes(normalized)); return searchMatches && (selectedTrail === 'all' || material.course.category === selectedTrail) && (selectedCourse === 'all' || material.course.id === selectedCourse) && (selectedType === 'all' || material.attachment.type === selectedType); }).sort((left, right) => { if (sort === 'name') return left.attachment.name.localeCompare(right.attachment.name, 'pt-BR'); if (sort === 'type') return left.attachment.type.localeCompare(right.attachment.type, 'pt-BR') || left.attachment.name.localeCompare(right.attachment.name, 'pt-BR'); return materialDate(right) - materialDate(left) || left.attachment.name.localeCompare(right.attachment.name, 'pt-BR'); }), [materials, query, selectedTrail, selectedCourse, selectedType, sort]); const hasFilters = Boolean(query) || selectedTrail !== 'all' || selectedCourse !== 'all' || selectedType !== 'all'; const clearFilters = () => { setQuery(''); setSelectedTrail('all'); setSelectedCourse('all'); setSelectedType('all'); }; const openMaterial = (material: Material) => material.attachment.type === 'link' ? showToast(`Abrindo “${material.attachment.name}”.`, 'info') : showDownloadToast(material.attachment.name, material.attachment.size); return

Central de recursos

Biblioteca de materiais

Materiais disponibilizados pelos instrutores nos cursos e aulas da plataforma.

{materials.length}

{materials.length === 1 ? 'material disponível' : 'materiais disponíveis'}

{ setSelectedTrail(value); setSelectedCourse('all'); }} label="Trilha" options={[{ value: 'all', label: 'Todas as trilhas' }, ...trails.map((trail) => ({ value: trail, label: trail }))]} /> ({ value: course.id, label: course.title }))]} /> setSelectedType(value as 'all' | AttachmentType)} label="Formato" options={filterTypes} />
{filteredMaterials.length} {filteredMaterials.length === 1 ? 'resultado' : 'resultados'}
{hasFilters && }
{isLoading ?
: filteredMaterials.length === 0 ? 0} onClear={clearFilters} /> : view === 'grid' ?
{filteredMaterials.map((material) => )}
:
MaterialCurso e aulaAdicionado emAções
{filteredMaterials.map((material) => )}
}
; }; const FilterSelect: React.FC<{ value: string; label: string; options: Array<{ value: string; label: string }>; onChange: (value: string) => void }> = ({ value, label, options, onChange }) => ; const EmptyState: React.FC<{ hasMaterials: boolean; onClear: () => void }> = ({ hasMaterials, onClear }) =>

{hasMaterials ? 'Nenhum material corresponde aos filtros' : 'A biblioteca ainda não tem materiais'}

{hasMaterials ? 'Tente alterar a busca ou os filtros selecionados.' : 'Quando um instrutor adicionar materiais a um curso ou aula, eles aparecerão aqui.'}

{hasMaterials && }
; const MaterialMeta: React.FC<{ material: Material }> = ({ material }) =>

{material.course.category}

{material.course.title}

{material.lessonTitle &&

Aula: {material.lessonTitle}

}
; const MaterialGridCard: React.FC<{ material: Material; onOpen: (material: Material) => void; onCourse: (course: Course) => void }> = ({ material, onOpen, onCourse }) => { const { attachment } = material; const badge = getAttachmentBadge(attachment.type); const isLink = attachment.type === 'link'; return
{getAttachmentIcon(attachment.type)}
{badge.label}

{attachment.name}

{attachment.description &&

{attachment.description}

}
{attachment.size || 'Tamanho não informado'}{formatDate(attachment.createdAt)}
onOpen(material)} className="ml-auto inline-flex items-center gap-1.5 rounded-lg bg-orange-500 px-3 py-2 text-xs font-semibold text-white transition hover:bg-orange-600">{isLink ? : }{isLink ? 'Abrir' : 'Baixar'}
; }; const MaterialListRow: React.FC<{ material: Material; onOpen: (material: Material) => void; onCourse: (course: Course) => void }> = ({ material, onOpen, onCourse }) => { const { attachment } = material; const isLink = attachment.type === 'link'; return ; };