Commit f47a9145 authored by Wellton Quirino's avatar Wellton Quirino

add filters in screens courses

parent 89d68af0
import { api } from '@/lib/axios'
export type AudiencesProps = {
id?: string
id: string
name: string
}
......
import { api } from '@/lib/axios'
export type CategoriesProps = {
id?: string
id: string
name: string
}
......
......@@ -37,6 +37,10 @@ export type CoursesProps = {
areaId: string
audienceId: string
categoryId: string
area?: {
id: string
name: string
}
audience?: {
id: string
name: string
......
......@@ -19,7 +19,6 @@ import { toast } from 'sonner'
export default function AreaId() {
const [desktopBanner, setDesktopBanner] = useState<File | null>(null)
console.log('🚀 ~ AreaId ~ desktopBanner:', desktopBanner)
const [mobileBanner, setMobileBanner] = useState<File | null>(null)
const params = useParams<{ id: string }>()
......
......@@ -86,7 +86,6 @@ export default function Curso() {
queryFn: () => getCourseId(params.id),
enabled: !!params.id,
})
console.log('🚀 ~ Curso:', course)
const formatDate = (dateString: string) => {
const date = parseISO(dateString)
......
......@@ -40,18 +40,6 @@ import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'
// const ACCOUNT_ID = env.NEXT_PUBLIC_CLOUDFLARE_ACCESS_ACCOUNT_ID
// const ACCESS_KEY_ID = env.NEXT_PUBLIC_CLOUDFLARE_ACCESS_KEY_ID
// const SECRET_ACCESS_KEY = env.NEXT_PUBLIC_CLOUDFLARE_SECRET_ACCESS_KEY
// const MAX_FILE_SIZE = 5000000
// const ACCEPTED_IMAGE_TYPES = [
// 'image/jpeg',
// 'image/jpg',
// 'image/png',
// 'image/webp',
// ]
const FormSchema = z.object({
name: z.string().trim().min(3, { message: 'Título obrigatório' }),
description: z.string().trim().min(3, { message: 'Descrição obrigatória' }),
......@@ -91,7 +79,6 @@ export default function Curso() {
},
})
const values = form.watch()
console.log('🚀 ~ Curso ~ values:', values)
const { data: areas } = useQuery({
queryKey: ['areas'],
......
......@@ -12,18 +12,28 @@ import { AuthContext } from '@/contexts/auth-context'
import { useQuery } from '@tanstack/react-query'
import { Pencil, Search } from 'lucide-react'
import Link from 'next/link'
import { useContext } from 'react'
import { useContext, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
export default function Admin() {
const { register } = useForm()
const { register, watch } = useForm()
const { user } = useContext(AuthContext)
const search = watch('search', '')
const [debouncedSearch, setDebouncedSearch] = useState('')
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearch(search)
}, 500)
return () => clearTimeout(handler)
}, [search])
const {
data: areas,
error,
isLoading,
isError,
isLoading: areasLoading,
isError: areasIsError,
} = useQuery({ queryKey: ['areas'], queryFn: getAreas })
const { data: categories } = useQuery({
......@@ -31,22 +41,34 @@ export default function Admin() {
queryFn: getCategories,
})
const { data: audiences } = useQuery({
const {
data: audiences,
isLoading: audiencesLoading,
isError: audiencesIsError,
} = useQuery({
queryKey: ['audiences'],
queryFn: getAudiences,
})
const { data: courses } = useQuery({
const {
data: courses,
isLoading: coursesLoading,
isError: coursesIsError,
} = useQuery({
queryKey: ['courses'],
queryFn: getCourses,
})
if (isLoading) {
const filteredCourses = courses?.data.filter((course) =>
course.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
)
if (areasLoading || coursesLoading || audiencesLoading) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {error.message}</span>
if (areasIsError || audiencesIsError || coursesIsError) {
return <span>Error: {error?.message}</span>
}
return (
......@@ -142,7 +164,7 @@ export default function Admin() {
<p className="text-yellow-100">Não há curso cadastrado!</p>
) : (
<div className="flex flex-wrap justify-around gap-4">
{courses?.data.map((course) => (
{filteredCourses?.map((course) => (
<Link
key={course.id}
className="lowercase scale-100 hover:scale-105 duration-300"
......@@ -166,6 +188,9 @@ export default function Admin() {
</div>
</Link>
))}
{filteredCourses?.length === 0 && (
<p className="text-yellow-100">Nenhum curso encontrado!</p>
)}
</div>
)}
</div>
......
'use client'
import Image from 'next/image'
import { getCourseId } from '@/api/courses'
import { BannerCategory } from '@/components/banner-category'
import { CarouselComponent } from '@/components/corousel-component'
import { CourseDetails } from '@/components/course-details'
......@@ -11,19 +14,35 @@ import {
} from '@/components/ui/accordion'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { useQuery } from '@tanstack/react-query'
import { useParams } from 'next/navigation'
import imageCapa from '../../../../public/images/banner.png'
export default function CoursePage() {
const params = useParams<{ courseId: string }>()
const { data: course, isLoading } = useQuery({
queryKey: ['course', params.courseId],
queryFn: () => getCourseId(params.courseId),
enabled: !!params.courseId,
})
if (isLoading) {
return <p>Carregando...</p>
}
return (
<>
<BannerCategory />
<BannerCategory data={course?.data.area.name} />
<div className="flex flex-col md:flex-row px-6 pt-20 gap-6">
<div className="flex flex-col gap-7">
<h1 className="block md:hidden text-green-400 text-3xl font-thin">
História Contemporânea dos Estados Unidos
{course?.data.name}
</h1>
<Image
src={imageCapa}
src={course?.data.mobileBanner || imageCapa}
width={500}
height={500}
className="min-w-[348px] h-[464px] object-cover rounded-lg"
alt="Imagem de capa do curso"
/>
......@@ -33,43 +52,43 @@ export default function CoursePage() {
</div>
<div className="flex flex-col gap-6">
<h1 className="hidden md:block text-green-400 text-4xl font-extrabold">
História Contemporânea dos Estados Unidos
{course?.data.name}
</h1>
<div className="flex flex-col-reverse lg:flex-row gap-6 w-full">
<div className="flex flex-col lg:w-1/2">
<h3 className="font-bold">O que você vai aprender</h3>
<p>
Este curso levará você por uma jornada desde os tumultuados anos
de 1970 até os dias atuais, explorando os impactos decisivos dos
governos de Bush, Obama, Trump e Biden. Descubra os
momentos-chave que moldaram a história contemporânea dos Estados
Unidos, desde a resiliência diante de crises até as mudanças
geopolíticas e sociais. Vamos desvendar os bastidores dos
eventos que marcaram época, proporcionando uma compreensão
profunda e contextualizada da evolução norte-americana.
Prepare-se para uma experiência de aprendizado envolvente e
reveladora, onde o passado ilumina o presente, moldando o
futuro.
</p>
<p>{course?.data.description}</p>
<h3 className="font-bold mt-6">Com quem você vai aprender</h3>
<p>Professor João Silva</p>
{course?.data.professors &&
course?.data.professors.length > 0 && (
<p>{course?.data.professors.join(', ')}</p>
)}
</div>
<div className="lg:w-1/2">
<CourseDetails />
<CourseDetails data={course?.data} />
</div>
</div>
<Button variant="secondary" className="uppercase block md:hidden">
Quero este curso
</Button>
<span className="font-bold mt-4 block md:hidden">Saiba mais:</span>
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>Módulos</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
</Accordion>
{course?.data.modules
.sort((a, b) => a.module.name.localeCompare(b.module.name))
.map((module) => (
<Accordion
type="single"
collapsible
className="w-full"
key={module.module.id}
>
<AccordionItem value={module.module.id}>
<AccordionTrigger>{module.module.name}</AccordionTrigger>
<AccordionContent>
{module.module.description}
</AccordionContent>
</AccordionItem>
</Accordion>
))}
</div>
</div>
<div className="my-10 px-6">
......
......@@ -22,7 +22,7 @@ export default function Companies() {
<div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 grid-rows-3 lg:grid-rows-2 gap-4 md:gap-6 p-6">
{CoursesCard.map((course) => (
<div className="flex justify-center" key={course.id}>
<Card.Root link={`curso/${course.id}`}>
<Card.Root link={`/curso/${course.id}`}>
<Card.Image image={course.image.src} width={273} height={365}>
<Card.Title title={course.title} />
</Card.Image>
......
'use client'
import { getCourses } from '@/api/courses'
import { About } from '@/components/about'
import { BannerCategory } from '@/components/banner-category'
import { Card } from '@/components/card'
......@@ -7,11 +10,36 @@ import { PaginationComponent } from '@/components/pagination-component'
import SearchFilter from '@/components/search-filter'
import { SignUp } from '@/components/sign-up'
import { SkeletonSerachParams } from '@/components/skeleton-serach-params'
import { CoursesCard } from '@/utils/courses-array'
import { formatDate } from '@/utils/formatDate'
import { useQuery } from '@tanstack/react-query'
import { Calendar, Clock4, User } from 'lucide-react'
import { usePathname, useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
export default function Students() {
const path = usePathname()
const searchParams = useSearchParams()
const category = searchParams.get('area')
console.log("🚀 ~ Students ~ category:", category)
const {
data: courses,
isLoading: coursesLoading,
isError: coursesIsError,
} = useQuery({
queryKey: ['courses'],
queryFn: getCourses,
})
const filterCoursesAudience = courses?.data.filter((item) =>
path.toLowerCase().includes(item?.audience?.name.toLowerCase() as string)
)
console.log("🚀 ~ Students ~ filterCoursesAudience:", filterCoursesAudience)
const filterCoursesAudiencesAndArea = filterCoursesAudience?.filter((area) => category?.includes(area?.area?.name.toLowerCase() as string))
console.log("🚀 ~ Students ~ filterCoursesAudiencesAndArea:", filterCoursesAudiencesAndArea)
return (
<main>
<Suspense fallback={<SkeletonSerachParams />}>
......@@ -20,24 +48,54 @@ export default function Students() {
</Suspense>
<SearchFilter />
<div className="grid rounded-none grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 grid-rows-3 lg:grid-rows-2 gap-4 md:gap-6 p-6">
{CoursesCard.map((course) => (
{!category && filterCoursesAudience && filterCoursesAudience.map(course => (
<div className="flex justify-center" key={course.id}>
<Card.Root link={`curso/${course.id}`}>
<Card.Image image={course.image.src} width={273} height={365}>
<Card.Title title={course.title} />
</Card.Image>
<Card.Content description={course.hours}>
<Card.Icon icon={Clock4} />
</Card.Content>
<Card.Content description={course.category}>
<Card.Icon icon={User} />
</Card.Content>
<Card.Content description={course.calender}>
<Card.Icon icon={Calendar} />
</Card.Content>
</Card.Root>
</div>
<Card.Root link={`/curso/${course.id}`}>
<Card.Image image={course.mobileBanner as string} width={273} height={365}>
<Card.Title title={course.name} />
</Card.Image>
<Card.Content description={`${course.workload} hrs / ${course.category?.name}`}>
<Card.Icon icon={Clock4} />
</Card.Content>
<Card.Content description={course.audience?.name as string}>
<Card.Icon icon={User} />
</Card.Content>
<Card.Content description={formatDate(course.startDate) as string}>
<Card.Icon icon={Calendar} />
</Card.Content>
</Card.Root>
</div>
))}
{category && filterCoursesAudiencesAndArea && filterCoursesAudiencesAndArea.map((course) => (
<div className="flex justify-center" key={course.id}>
<Card.Root link={`/curso/${course.id}`}>
<Card.Image image={course.mobileBanner as string} width={273} height={365}>
<Card.Title title={course.name} />
</Card.Image>
<Card.Content description={`${course.workload} hrs / ${course.category?.name}`}>
<Card.Icon icon={Clock4} />
</Card.Content>
<Card.Content description={course.audience?.name as string}>
<Card.Icon icon={User} />
</Card.Content>
<Card.Content description={formatDate(course.startDate) as string}>
<Card.Icon icon={Calendar} />
</Card.Content>
</Card.Root>
</div>
))
}
{category && filterCoursesAudiencesAndArea?.length === 0 && (
<p>Não há cursos para a área {category} com audiência {path}</p>
)}
{!category && filterCoursesAudience?.length === 0 && (
<p>Não há cursos para a audiência {path}</p>
)}
</div>
<div className="my-6">
<PaginationComponent pageIndex={0} totalCount={105} perPage={10} />
......
......@@ -22,7 +22,7 @@ export default function Professionals() {
<div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 grid-rows-3 lg:grid-rows-2 gap-4 md:gap-6 p-6">
{CoursesCard.map((course) => (
<div className="flex justify-center" key={course.id}>
<Card.Root link={`curso/${course.id}`}>
<Card.Root link={`/curso/${course.id}`}>
<Card.Image image={course.image.src} width={273} height={365}>
<Card.Title title={course.title} />
</Card.Image>
......
......@@ -7,16 +7,20 @@ import bannerStudants from '../../public/images/banners/students_banner.jpg'
import { getAreas } from '@/api/areas'
import { useQuery } from '@tanstack/react-query'
export function BannerCategory() {
export function BannerCategory(areaBanner: { data?: string | undefined }) {
const searchParams = useSearchParams()
const category = searchParams.get('area')
const { data: areas } = useQuery({ queryKey: ['areas'], queryFn: getAreas })
const area = areas?.data.find(
const bannerImage = areas?.data.find(
(item) => item.name.toLowerCase() === category?.toLowerCase(),
)
const bannerImageArea = areas?.data.find(
(item) => item.name.toLowerCase() === areaBanner?.data?.toLowerCase(),
)
return (
<div className="h-[300px] flex items-center justify-end">
<Image
......@@ -24,7 +28,11 @@ export function BannerCategory() {
className="hidden md:flex w-full h-full object-cover"
width={100}
height={100}
src={area?.desktopBanner || bannerStudants}
src={
bannerImage?.desktopBanner ||
bannerImageArea?.desktopBanner ||
bannerStudants
}
unoptimized
/>
<Image
......@@ -32,7 +40,11 @@ export function BannerCategory() {
className="flex md:hidden w-full h-full object-cover"
width={100}
height={100}
src={area?.mobileBanner || bannerStudants}
src={
bannerImage?.mobileBanner ||
bannerImageArea?.mobileBanner ||
bannerStudants
}
unoptimized
/>
</div>
......
......@@ -10,7 +10,7 @@ type CardImageProps = {
export function CardImage({ children, image, width, height }: CardImageProps) {
return (
<div className={`relative w-[${width}px] `}>
<div className={`relative w-[${width}px]`}>
<Image
src={image}
width={width}
......
'use client'
import Link from 'next/link'
import { ReactNode } from 'react'
......@@ -9,10 +11,12 @@ type CardRootProps = {
export function CardRoot({ children, link }: CardRootProps) {
return (
<Link
className="lowercase transform scale-95 hover:scale-100 transition-transform duration-300 drop-shadow"
className="lowercase transform scale-95 hover:scale-100 transition-transform duration-300 drop-shadow "
href={link}
>
<div className={`border border-gray-100 pl-0 rounded-lg max-w-[273px] `}>
<div
className={`border border-gray-100 pl-0 rounded-lg max-w-[273px] h-full`}
>
{children}
</div>
</Link>
......
......@@ -18,7 +18,6 @@ export function CarouselComponent() {
queryKey: ['courses'],
queryFn: getCourses,
})
console.log('🚀 ~ Admin ~ courses:', courses)
return (
<section>
......@@ -30,7 +29,7 @@ export function CarouselComponent() {
key={course.id}
className="sm:basis-1/2 md:basis-1/3 lg:basis-1/4 xl:basis-1/5 flex justify-center"
>
<Card.Root link={`curso/${course.id}`}>
<Card.Root link={`/curso/${course.id}`}>
<Card.Image
image={course.mobileBanner || ''}
width={240}
......
import { Calendar, Clock4, DollarSign, UserRound } from 'lucide-react'
import { CourseIdProps } from '@/api/courses'
import { format, parseISO } from 'date-fns'
import { toZonedTime } from 'date-fns-tz'
import { Calendar, Clock4, UserRound } from 'lucide-react'
type CourseProps = {
data?: CourseIdProps
}
export function CourseDetails(course: CourseProps) {
const formatDate = (dateString: string | undefined) => {
if (!dateString) return
const date = parseISO(dateString)
const utcDate = toZonedTime(date, 'UTC')
return format(utcDate, 'dd/MM/yyyy')
}
export function CourseDetails() {
return (
<ul className="flex flex-col gap-8">
<li className="flex items-center gap-4">
......@@ -11,7 +26,9 @@ export function CourseDetails() {
</div>
<div className="flex flex-col gap-2">
<span className="font-thin">Duração:</span>
<span className="text-xl">30 Hrs/Curso Rápido</span>
<span className="text-xl">
{course.data?.workload.toString()} Hrs/{course.data?.category.name}
</span>
</div>
</li>
<li className="flex items-center gap-4">
......@@ -22,7 +39,7 @@ export function CourseDetails() {
</div>
<div className="flex flex-col gap-2">
<span className="font-thin">Indicado para:</span>
<span className="text-xl">Estudantes</span>
<span className="text-xl">{course.data?.audience.name}</span>
</div>
</li>
<li className="flex items-center gap-4">
......@@ -33,10 +50,10 @@ export function CourseDetails() {
</div>
<div className="flex flex-col gap-2">
<span className="font-thin">Início:</span>
<span className="text-xl">Imediato</span>
<span className="text-xl">{formatDate(course.data?.startDate)}</span>
</div>
</li>
<li className="flex items-center gap-4">
{/* <li className="flex items-center gap-4">
<div className="rounded-full w-[60px] h-[60px] flex items-center justify-center bg-gradient-to-r from-green-700 to-green-50">
<div className="rounded-full flex items-center justify-center w-[57px] h-[57px] dark:bg-gray-900 bg-gray-50">
<DollarSign size={35} />
......@@ -46,7 +63,7 @@ export function CourseDetails() {
<span className="font-thin">Investimento:</span>
<span className="text-xl">R$200,00</span>
</div>
</li>
</li> */}
</ul>
)
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0, 0%, 25%;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0, 0%, 25%;
--card: 0 0% 100%;
--card-foreground: 0, 0%, 11%;
--card: 0 0% 100%;
--card-foreground: 0, 0%, 11%;
--popover: 0 0% 100%;
--popover-foreground: 0, 0%, 11%;
--popover: 0 0% 100%;
--popover-foreground: 0, 0%, 11%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 0, 0%, 11%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 0, 0%, 11%;
--radius: 0.5rem;
}
--radius: 0.5rem;
}
.dark {
--background: 0, 0%, 11%;
--foreground: 0 0% 98%;
.dark {
--background: 0, 0%, 11%;
--foreground: 0 0% 98%;
--card: 0, 0%, 11%;
--card-foreground: 0 0% 98%;
--card: 0, 0%, 11%;
--card-foreground: 0 0% 98%;
--popover: 0, 0%, 11%;
--popover-foreground: 0 0% 98%;
--popover: 0, 0%, 11%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
\ No newline at end of file
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
import { format, parseISO } from "date-fns"
import { toZonedTime } from "date-fns-tz"
export const formatDate = (dateString: string | undefined) => {
if (!dateString) return
const date = parseISO(dateString)
const utcDate = toZonedTime(date, 'UTC')
return format(utcDate, 'dd/MM/yyyy')
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment