feat: filter cms courses by trail
This commit is contained in:
@@ -85,11 +85,19 @@ const BannerManager: React.FC<{ courses: CourseOption[] }> = ({ courses }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CoursesPanel: React.FC<{ courses: CourseOption[]; onManage: () => void }> = ({ courses, onManage }) => {
|
const CoursesPanel: React.FC<{ courses: CourseOption[]; onManage: () => void }> = ({ courses, onManage }) => {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const [selectedTrail, setSelectedTrail] = useState('all');
|
||||||
|
const trails: string[] = [...new Set<string>(courses.map((course) => course.category))].sort((left, right) => left.localeCompare(right, 'pt-BR'));
|
||||||
|
const visibleCourses = courses.filter((course) => {
|
||||||
|
const matchesTrail = selectedTrail === 'all' || course.category === selectedTrail;
|
||||||
|
const normalized = query.trim().toLocaleLowerCase('pt-BR');
|
||||||
|
return matchesTrail && (!normalized || `${course.title} ${course.category}`.toLocaleLowerCase('pt-BR').includes(normalized));
|
||||||
|
});
|
||||||
const byTrail: Record<string, CourseOption[]> = {};
|
const byTrail: Record<string, CourseOption[]> = {};
|
||||||
courses.forEach((course) => {
|
visibleCourses.forEach((course) => {
|
||||||
(byTrail[course.category] ||= []).push(course);
|
(byTrail[course.category] ||= []).push(course);
|
||||||
});
|
});
|
||||||
return <section className="rounded-2xl border border-white/10 bg-zinc-950/80 overflow-hidden"><div className="flex items-center justify-between gap-4 border-b border-white/10 p-5"><div><h2 className="text-lg font-bold text-white">Cursos</h2><p className="mt-1 text-sm text-gray-500">Visualize cada curso pela sua capa e Trilha.</p></div><button onClick={onManage} className="rounded-xl bg-orange-500 px-4 py-2.5 text-sm font-semibold text-white">Gerenciar cursos</button></div>{courses.length === 0 ? <p className="p-8 text-sm text-gray-500">Nenhum curso cadastrado.</p> : <div className="divide-y divide-white/10">{Object.entries(byTrail).sort(([a], [b]) => a.localeCompare(b, 'pt-BR')).map(([trail, trailCourses]) => <section key={trail} className="p-5"><div className="mb-3 flex items-center gap-2"><h3 className="font-semibold text-orange-300">{trail}</h3><span className="text-xs text-gray-600">{trailCourses.length} {trailCourses.length === 1 ? 'curso' : 'cursos'}</span></div><div className="grid gap-3 xl:grid-cols-2">{trailCourses.map((course) => <div key={course.id} className="flex items-center gap-3 rounded-xl border border-white/10 bg-black/20 p-3"><div className="h-14 w-24 shrink-0 overflow-hidden rounded-lg bg-zinc-900">{course.coverImageUrl ? <CourseCoverImage source={course.coverImageUrl} alt="" className="h-full w-full object-cover" /> : <div className="h-full w-full bg-gradient-to-br from-zinc-800 to-zinc-950" />}</div><div className="min-w-0 flex-1"><p className="truncate font-semibold text-white">{course.title}</p><span className={`mt-1 inline-block rounded-full px-2 py-0.5 text-[10px] font-bold uppercase ${course.status === 'published' ? 'bg-emerald-500/15 text-emerald-400' : 'bg-amber-500/15 text-amber-300'}`}>{course.status === 'published' ? 'Publicado' : 'Rascunho'}</span></div><button onClick={onManage} className="rounded-lg border border-white/10 px-3 py-1.5 text-xs font-semibold text-gray-300 hover:bg-white/10">Editar</button></div>)}</div></section>)}</div>}</section>;
|
return <section className="rounded-2xl border border-white/10 bg-zinc-950/80 overflow-hidden"><div className="flex items-center justify-between gap-4 border-b border-white/10 p-5"><div><h2 className="text-lg font-bold text-white">Cursos</h2><p className="mt-1 text-sm text-gray-500">Visualize cada curso pela sua capa e Trilha.</p></div><button onClick={onManage} className="rounded-xl bg-orange-500 px-4 py-2.5 text-sm font-semibold text-white">Gerenciar cursos</button></div>{courses.length === 0 ? <p className="p-8 text-sm text-gray-500">Nenhum curso cadastrado.</p> : <><div className="flex flex-col gap-2 border-b border-white/10 p-4 sm:flex-row"><input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Buscar curso" className="min-w-0 flex-1 rounded-xl border border-white/10 bg-zinc-900 px-3 py-2.5 text-sm text-white" /><select value={selectedTrail} onChange={(event) => setSelectedTrail(event.target.value)} className="rounded-xl border border-white/10 bg-zinc-900 px-3 py-2.5 text-sm text-white"><option value="all">Todas as Trilhas</option>{trails.map((trail) => <option key={trail} value={trail}>{trail}</option>)}</select></div>{visibleCourses.length === 0 ? <p className="p-10 text-center text-sm text-gray-500">Nenhum curso encontrado.</p> : <div className="divide-y divide-white/10">{Object.entries(byTrail).sort(([a], [b]) => a.localeCompare(b, 'pt-BR')).map(([trail, trailCourses]) => <section key={trail} className="p-5"><div className="mb-3 flex items-center gap-2"><h3 className="font-semibold text-orange-300">{trail}</h3><span className="text-xs text-gray-600">{trailCourses.length} {trailCourses.length === 1 ? 'curso' : 'cursos'}</span></div><div className="grid gap-3 xl:grid-cols-2">{trailCourses.map((course) => <div key={course.id} className="flex items-center gap-3 rounded-xl border border-white/10 bg-black/20 p-3"><div className="h-14 w-24 shrink-0 overflow-hidden rounded-lg bg-zinc-900">{course.coverImageUrl ? <CourseCoverImage source={course.coverImageUrl} alt="" className="h-full w-full object-cover" /> : <div className="h-full w-full bg-gradient-to-br from-zinc-800 to-zinc-950" />}</div><div className="min-w-0 flex-1"><p className="truncate font-semibold text-white">{course.title}</p><span className={`mt-1 inline-block rounded-full px-2 py-0.5 text-[10px] font-bold uppercase ${course.status === 'published' ? 'bg-emerald-500/15 text-emerald-400' : 'bg-amber-500/15 text-amber-300'}`}>{course.status === 'published' ? 'Publicado' : 'Rascunho'}</span></div><button onClick={onManage} className="rounded-lg border border-white/10 px-3 py-1.5 text-xs font-semibold text-gray-300 hover:bg-white/10">Editar</button></div>)}</div></section>)}</div>}</>}</section>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PathManager: React.FC<{ title: string; description: string; paths: LearningPath[]; onNew: () => void; onEdit: (path: LearningPath) => void; onDelete: (path: LearningPath) => void }> = ({ title, description, paths, onNew, onEdit, onDelete }) => <section className="rounded-2xl border border-white/10 bg-zinc-950/80 overflow-hidden"><div className="flex items-center justify-between gap-4 border-b border-white/10 p-5"><div><h2 className="text-lg font-bold text-white">{title}</h2><p className="mt-1 text-sm text-gray-500">{description}</p></div><button onClick={onNew} className="inline-flex items-center gap-2 rounded-xl bg-orange-500 px-4 py-2.5 text-sm font-semibold text-white"><Plus className="h-4 w-4" /> Nova</button></div><div className="divide-y divide-white/5">{paths.length === 0 ? <div className="p-10 text-center text-sm text-gray-500">Nenhuma {title.toLowerCase()} criada ainda.</div> : paths.map((path) => <div key={path.id} className="flex items-center gap-4 p-4"><div className="h-12 w-20 shrink-0 overflow-hidden rounded-lg bg-zinc-900">{path.coverImageUrl ? <img src={path.coverImageUrl} alt="" className="h-full w-full object-cover" /> : <FolderKanban className="m-3 h-6 w-6 text-orange-400" />}</div><div className="min-w-0 flex-1"><p className="truncate font-semibold text-white">{path.title}</p><p className="mt-1 text-xs text-gray-500">{path.courses.length} {path.courses.length === 1 ? 'curso' : 'cursos'} · {path.status === 'published' ? 'Publicada' : 'Rascunho'}</p></div><button onClick={() => onEdit(path)} className="rounded-lg border border-white/10 p-2 text-gray-300 hover:bg-white/10" title="Editar"><Pencil className="h-4 w-4" /></button><button onClick={() => void onDelete(path)} className="rounded-lg border border-red-500/20 p-2 text-red-400 hover:bg-red-500/10" title="Excluir"><Trash2 className="h-4 w-4" /></button></div>)}</div></section>;
|
const PathManager: React.FC<{ title: string; description: string; paths: LearningPath[]; onNew: () => void; onEdit: (path: LearningPath) => void; onDelete: (path: LearningPath) => void }> = ({ title, description, paths, onNew, onEdit, onDelete }) => <section className="rounded-2xl border border-white/10 bg-zinc-950/80 overflow-hidden"><div className="flex items-center justify-between gap-4 border-b border-white/10 p-5"><div><h2 className="text-lg font-bold text-white">{title}</h2><p className="mt-1 text-sm text-gray-500">{description}</p></div><button onClick={onNew} className="inline-flex items-center gap-2 rounded-xl bg-orange-500 px-4 py-2.5 text-sm font-semibold text-white"><Plus className="h-4 w-4" /> Nova</button></div><div className="divide-y divide-white/5">{paths.length === 0 ? <div className="p-10 text-center text-sm text-gray-500">Nenhuma {title.toLowerCase()} criada ainda.</div> : paths.map((path) => <div key={path.id} className="flex items-center gap-4 p-4"><div className="h-12 w-20 shrink-0 overflow-hidden rounded-lg bg-zinc-900">{path.coverImageUrl ? <img src={path.coverImageUrl} alt="" className="h-full w-full object-cover" /> : <FolderKanban className="m-3 h-6 w-6 text-orange-400" />}</div><div className="min-w-0 flex-1"><p className="truncate font-semibold text-white">{path.title}</p><p className="mt-1 text-xs text-gray-500">{path.courses.length} {path.courses.length === 1 ? 'curso' : 'cursos'} · {path.status === 'published' ? 'Publicada' : 'Rascunho'}</p></div><button onClick={() => onEdit(path)} className="rounded-lg border border-white/10 p-2 text-gray-300 hover:bg-white/10" title="Editar"><Pencil className="h-4 w-4" /></button><button onClick={() => void onDelete(path)} className="rounded-lg border border-red-500/20 p-2 text-red-400 hover:bg-red-500/10" title="Excluir"><Trash2 className="h-4 w-4" /></button></div>)}</div></section>;
|
||||||
|
|||||||
Reference in New Issue
Block a user