Skip to content

Commit

Permalink
Merge pull request #29 from open-source-uc/23-crear-pagina-por-cada-s…
Browse files Browse the repository at this point in the history
…igla

creacion vista reseña de curso y ruta
  • Loading branch information
canija2000 authored Jan 21, 2025
2 parents 36c66c8 + d2213bb commit 69ff803
Show file tree
Hide file tree
Showing 11 changed files with 1,071 additions and 753 deletions.
33 changes: 10 additions & 23 deletions app/components/CursoRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,30 @@ import { useRouter } from "next/navigation";
type CursoRowProps = {
curso?: Curso;
isLogged?: boolean;
categoria: string;
};

export default function CursoRow({ curso, isLogged }: CursoRowProps) {
export default function CursoRow({ curso, isLogged, categoria }: CursoRowProps) {
const router = useRouter();
if (!curso) {
return null;
}


const handleRowClick = () => {
router.push(`/cursos/ARTS/${curso.nrc}`);
router.push(`/cursos/${categoria}/${curso.nrc}`);
};

return (
<tr
onClick={handleRowClick}
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700
hover:bg-slate-100 dark:hover:bg-gray-700 cursor-pointer"
className="cursor-pointer border-b bg-white hover:bg-slate-100 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700"
>
<td className="px-6 py-4 font-medium text-gray-900 text-center whitespace-nowrap dark:text-white">
{curso.nrc}
</td>
<td className="px-2 py-4 text-black dark:text-white text-center">
{curso.nombre_curso}
</td>
<td className="px-2 py-4 text-black dark:text-white text-center">
{curso.creditos}
</td>
<td className="px-6 py-4 text-cyan-600 font-bold text-center">
{curso.mas}
</td>
<td className="px-6 py-4 text-red-500 font-bold text-center">
{curso.menos}
</td>
<td className="px-6 py-4 text-black dark:text-white text-center">
{curso.promedio}
</td>
<td className="whitespace-nowrap px-6 py-4 text-center font-medium text-gray-900 dark:text-white">{curso.nrc}</td>
<td className="px-2 py-4 text-center text-black dark:text-white">{curso.nombre_curso}</td>
<td className="px-2 py-4 text-center text-black dark:text-white">{curso.creditos}</td>
<td className="px-6 py-4 text-center font-bold text-cyan-600">{curso.mas}</td>
<td className="px-6 py-4 text-center font-bold text-red-500">{curso.menos}</td>
<td className="px-6 py-4 text-center text-black dark:text-white">{curso.promedio}</td>
</tr>
);
}
84 changes: 84 additions & 0 deletions app/components/FormComment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { FormProvider, useForm } from "react-hook-form";
import { commentSchema, CommentForm } from "../schemas/comment";
import { zodResolver } from "@hookform/resolvers/zod";
import { ThumbsUp, ThumbsDown } from "lucide-react";
import InputForm from "./InputForm";
import ButtonLarge from "./ButtonLarge";

type Props = {};

const FormComment = ({}: Props) => {
const methods = useForm<CommentForm>({
resolver: zodResolver(commentSchema),
defaultValues: {
comment: "",
vote: undefined,
},
});

const onSubmit = (data: CommentForm) => {
// lógica para enviar el comentario a la API
console.log(data);
};
// html de https://flowbite.com/blocks/publisher/comments/
return (
<FormProvider {...methods}>
<div className="dark: mx-auto max-w-2xl bg-slate-100 px-4 py-8 dark:bg-gray-800 dark:text-white">
<form
onSubmit={methods.handleSubmit(onSubmit)}
className="mb-4 rounded-lg rounded-t-lg border border-gray-200 bg-white px-4 py-2 dark:border-gray-700 dark:bg-gray-900"
>
<div className="mb-4">
<label htmlFor="comment" className="sr-only">
Tu comentario
</label>
<textarea
{...methods.register("comment")}
id="comment"
rows={6}
className="w-full border-0 px-0 text-sm text-gray-900 focus:outline-none focus:ring-0 dark:bg-gray-900 dark:text-white dark:placeholder-gray-400"
placeholder="Escribe tu comentario ..."
/>
{methods.formState.errors.comment && (
<span className="text-sm text-red-500">{methods.formState.errors.comment.message}</span>
)}
</div>

<div className="mb-4 flex items-center space-x-4">
<span className="text-sm text-gray-600 dark:text-gray-400">Valora este curso</span>
<button
type="button"
onClick={() => methods.setValue("vote", "positive")}
className={`flex items-center space-x-2 rounded-lg p-2 transition-colors ${
methods.watch("vote") === "positive"
? "bg-green-100 text-green-600 dark:bg-green-800 dark:text-green-200"
: "hover:bg-gray-100 dark:hover:bg-gray-700"
}`}
>
<ThumbsUp size={20} />
</button>
<button
type="button"
onClick={() => methods.setValue("vote", "negative")}
className={`flex items-center space-x-2 rounded-lg p-2 transition-colors ${
methods.watch("vote") === "negative"
? "bg-red-100 text-red-600 dark:bg-red-800 dark:text-red-200"
: "hover:bg-gray-100 dark:hover:bg-gray-700"
}`}
>
<ThumbsDown size={20} />
</button>
</div>

{methods.formState.errors.vote && (
<span className="mb-4 block text-sm text-red-500">{methods.formState.errors.vote.message}</span>
)}

<ButtonLarge>Enviar comentario</ButtonLarge>
</form>
</div>
</FormProvider>
);
};

export default FormComment;
107 changes: 58 additions & 49 deletions app/components/TablaCursos.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,68 @@
import CursoRow from "./CursoRow";
import { Curso } from "../types";
import { useState, useMemo } from 'react';
import SearchBar from './SearchBar';

import { useState, useMemo } from "react";
import SearchBar from "./SearchBar";

type TablaCursosProps = {
cursos: Array<Curso>;
isLogged?: boolean;
cursos: Array<Curso>;
isLogged?: boolean;
categoria: string;
};

//
//

export default function TablaCursos({ cursos, isLogged }: TablaCursosProps) {
const [searchTerm, setSearchTerm] = useState('');
export default function TablaCursos({ cursos, isLogged, categoria }: TablaCursosProps) {
const [searchTerm, setSearchTerm] = useState("");

const filteredCourses = useMemo(() => {
return cursos.filter(course =>
course.nrc.toLowerCase().includes(searchTerm.toLowerCase()) ||
course.nombre_curso.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [searchTerm]);
const filteredCourses = useMemo(() => {
return cursos.filter(
(course) =>
course.nrc.toLowerCase().includes(searchTerm.toLowerCase()) ||
course.nombre_curso.toLowerCase().includes(searchTerm.toLowerCase()),
);
}, [searchTerm]);

return (
<div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-6 flex justify-center">
<SearchBar value={searchTerm} onChange={setSearchTerm} />
</div>
</div>

<div className="mx-5 rounded-lg">
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400 sticky top-0 z-10">
<tr>
<th scope="col" className="px-6 py-3 text-center">NRC</th>
<th scope="col" className="px-6 py-3 text-center">Nombre Curso</th>
<th scope="col" className="px-6 py-3 text-center">Créditos</th>
<th scope="col" className="px-6 py-3 text-center">+</th>
<th scope="col" className="px-6 py-3 text-center">-</th>
<th scope="col" className="px-6 py-3 text-center">Promedio</th>
</tr>
</thead>
<tbody>
{filteredCourses.length > 0
? filteredCourses.map((curso, index) => (
<CursoRow
key={index}
curso={curso}
isLogged={isLogged}
/>
))
: null}
</tbody>
</table>
</div>
return (
<div>
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div className="mb-6 flex justify-center">
<SearchBar value={searchTerm} onChange={setSearchTerm} />
</div>
);
}
</div>

<div className="mx-5 rounded-lg">
<table className="w-full text-left text-sm text-gray-500 rtl:text-right dark:text-gray-400">
<thead className="sticky top-0 z-10 bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="px-6 py-3 text-center">
NRC
</th>
<th scope="col" className="px-6 py-3 text-center">
Nombre Curso
</th>
<th scope="col" className="px-6 py-3 text-center">
Créditos
</th>
<th scope="col" className="px-6 py-3 text-center">
+
</th>
<th scope="col" className="px-6 py-3 text-center">
-
</th>
<th scope="col" className="px-6 py-3 text-center">
Promedio
</th>
</tr>
</thead>
<tbody>
{filteredCourses.length > 0
? filteredCourses.map((curso, index) => (
<CursoRow categoria={categoria} key={index} curso={curso} isLogged={isLogged} />
))
: null}
</tbody>
</table>
</div>
</div>
);
}
8 changes: 3 additions & 5 deletions app/components/TarjetaCurso.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function TarCurso({ titulo, cursos }: TarCursoProps) {
const router = useRouter();

const goToCurso = (nombreC: string) => {
// split '-'
// split '-'
nombreC = nombreC.split(" ")[0];
router.push(`/cursos/${nombreC}`);
};
Expand All @@ -23,9 +23,7 @@ export default function TarCurso({ titulo, cursos }: TarCursoProps) {
>
<div className="relative h-full w-full">
<div className="flex h-full w-full flex-col justify-end text-gray-800 dark:text-gray-200">
<h2 className="pb-4 text-center text-2xl font-semibold text-cyan-600 dark:text-cyan-400">
{titulo}
</h2>
<h2 className="pb-4 text-center text-2xl font-semibold text-cyan-600 dark:text-cyan-400">{titulo}</h2>
<hr className="m-3 border-t-2 border-gray-300 dark:border-gray-600" />
<div>
<ol className="list-outside list-decimal pl-5">
Expand All @@ -40,4 +38,4 @@ export default function TarCurso({ titulo, cursos }: TarCursoProps) {
</div>
</div>
);
}
}
101 changes: 93 additions & 8 deletions app/cursos/[nombre_curso]/[nrc]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,102 @@
"use client";

import { useParams } from 'next/navigation';
import FormComment from "@/app/components/FormComment";
import { Info } from "lucide-react";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";

type InfoCourse = {
nrc: string;
nombre_curso: string;
creditos: number;
mas: number;
menos: number;
promedio: number;
};

type RouteParams = {
nrc: string;
};

export default function DetalleCurso() {
const params = useParams();
const nrc = params.nrc;
const nombre_curso = params.name
const params = useParams() as RouteParams;
const [curso, setCurso] = useState<InfoCourse | undefined>(undefined);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isLogged, setIsLogged] = useState(true);

useEffect(() => {
const fetchInfo = async () => {
try {
const response = await fetch("../../../assets/infocursos.json");
if (!response.ok) {
throw new Error("No se encontró el curso ... :c");
}
const data: InfoCourse[] = await response.json();
const foundCourse = data.find((course) => course.nrc === params.nrc);
setCurso(foundCourse);
} catch (err) {
setError(err instanceof Error ? err.message : "Algo falló ");
} finally {
setLoading(false);
}
};

fetchInfo();
}, [params.nrc]);

if (loading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error: {error}</div>;
}

if (!curso) {
return <div>Course not found</div>;
}

return (
<div>
<h1>Detalle del curso</h1>
<p>NRC: {nrc}</p>
<p>Nombre del curso: {nombre_curso}</p>
<div className="flex justify-center">
<div className="m-5 w-6/12 rounded-lg bg-slate-100 dark:bg-gray-800">
<div className="m-3 mx-auto flex flex-col items-center dark:text-white">
<h1 className="p-4 text-2xl">{curso.nombre_curso}</h1>
</div>
<div className="p-4 dark:text-white">
<p>
<span className="font-bold">Créditos : </span>
{curso.creditos}
</p>
<p>
<span className="font-bold">NRC : </span> {curso.nrc}
</p>
<p>
<span className="font-bold">Puntaje promedio : </span> {curso.promedio}
</p>
<p>
<span className="font-bold">Reseñas positivas: </span> {curso.mas}
</p>
<p>
<span className="font-bold">Reseñas negativas : </span> {curso.menos}
</p>
</div>
<hr />
{isLogged && (
<div className="p-4 dark:text-white">
<h2>Deja una reseña</h2>
<FormComment />
</div>
)}

<div className="p-4 dark:text-white">
<h2>Reseñas</h2>
<div className="flex justify-center p-4">
<Info size="64" />
</div>
<p className="text-center">No hay reseñas aún</p>
</div>
</div>
</div>
);
}
Loading

0 comments on commit 69ff803

Please sign in to comment.