26 Commits

Author SHA1 Message Date
jalvarado c10a3c4b8e Funcion ver datos profe 2024-10-15 17:55:28 -06:00
jalvarado bbb453bc6c new design 2024-10-15 17:37:18 -06:00
TheManus88 44d5c8c60c last changes 2024-08-30 16:32:51 -06:00
TioSam77 b9bc44b921 Cambios finales 1 2024-08-29 20:10:57 -06:00
IO c428cfcee4 add assets and add 2 columns and return to filter 2024-08-28 00:38:25 -06:00
IO 996f6dc438 scroll none 2024-08-18 23:33:36 -06:00
IO 4493a0834c url 2024-08-18 23:17:31 -06:00
IO e74619ae19 default image 2024-08-18 23:16:01 -06:00
IO 05b8abab76 fixed imaged update and register teacher 2024-08-18 22:05:47 -06:00
IO a919023851 merge 2024-08-18 21:02:01 -06:00
IO b9179d6c37 style 2024-08-18 20:49:21 -06:00
IO f5c1ba8d66 transition card teachers 2024-08-16 10:34:09 -06:00
IO 197c035356 currentPage 2024-08-13 22:43:20 -06:00
IO b7a0f48905 Merge branch 'Sam' of https://repositorio.acatlan.unam.mx/mcervantesag/front_directorio into Lino 2024-08-13 22:24:25 -06:00
TioSam77 d576242bce Limite foto 2024-08-13 22:17:39 -06:00
IO fe697f015b fixed name img 2024-08-13 22:13:31 -06:00
IO f8650adc93 fixed style and name image 2024-08-13 22:08:29 -06:00
IO f6878085ee add image 2024-08-13 21:34:00 -06:00
TheManus88 7256791a25 add changes 2024-08-13 01:47:45 -06:00
TheManus88 2b86b0e02c Merge remote-tracking branch 'origin/Sam' into development 2024-08-13 01:46:20 -06:00
TioSam77 a56e589812 Lista Perfil Profesores 2024-08-12 18:56:38 -06:00
IO 0ccd68674e url 2024-08-12 01:24:16 -06:00
IO 4884eaca64 fixing style 2024-08-12 01:06:03 -06:00
TioSam77 d72959ddc2 Cambio de color de Buscar 2024-08-10 09:41:05 -06:00
TioSam77 8020d9671b Adaptaciones front 1 2024-07-30 17:33:23 -06:00
TioSam77 3513fcb693 Cambios front 1 2024-07-27 17:49:11 -06:00
32 changed files with 1070 additions and 431 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
REACT_APP_API_URL=http://192.168.100.7:3000 REACT_APP_API_URL=https://venus.acatlan.unam.mx/directorio_test
REACT_APP_PORT=3001 REACT_APP_PORT=3001
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+2 -64
View File
@@ -109,17 +109,6 @@
z-index: 1000; z-index: 1000;
} }
.container-fluid {
display: flex;
justify-content: center;
align-items: center;
margin: 0 !important;
}
.form-control {
border-color: #002b7a !important;
}
.form-group { .form-group {
margin-bottom: 1em; margin-bottom: 1em;
} }
@@ -238,24 +227,6 @@ label {
font-size: 0.9em; font-size: 0.9em;
} }
button {
width: 100%;
padding: 0.6em;
font-size: 1em;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 12px !important;
cursor: pointer;
box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
button:hover {
background-color: #0056b3;
box-shadow: 0 6px 12px 4px rgba(0, 0, 0, 0.2);
}
.main-content { .main-content {
flex: 1; flex: 1;
display: flex; display: flex;
@@ -315,42 +286,9 @@ input[type="password"] {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border-radius: 5px !important; border-radius: 5px !important;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1),
0 2px 4px 1px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease;
} }
input[type="text"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus,
input[type="password"]:focus {
box-shadow: 0 0 8px 2px rgba(0, 123, 255, 0.5);
outline: none;
}
button[type="submit"] {
width: 100%;
padding: 0.75em;
border: none;
border-radius: 1em;
background-color: #002b7a;
color: #fff;
font-size: 0.9em;
cursor: pointer;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
button[type="submit"]:hover {
background-color: #0056b3;
box-shadow: 0 6px 12px 4px rgba(0, 0, 0, 0.2);
}
button[type="submit"]:disabled {
background-color: #007bff;
cursor: not-allowed;
opacity: 0.6;
}
.delete-botones { .delete-botones {
background-image: url("https://cdn.icon-icons.com/icons2/1791/PNG/512/trashcan1_114647.png"); background-image: url("https://cdn.icon-icons.com/icons2/1791/PNG/512/trashcan1_114647.png");
filter: invert(1); filter: invert(1);
@@ -396,7 +334,7 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
min-width: 100vw; background-color: #f5f5f7;
} }
h2.text-warning { h2.text-warning {
@@ -423,7 +361,7 @@ p .text-center .mt-3 {
justify-content: space-evenly; justify-content: space-evenly;
width: 400px; width: 400px;
max-width: 90vw; max-width: 90vw;
margin: 10px; margin: 10px auto;
} }
.cargando { .cargando {
+3 -2
View File
@@ -26,10 +26,11 @@ import Login from "./components/Login/LoginForm";
import { PrivateRoutes, PublicRoutes } from "./models/routes"; import { PrivateRoutes, PublicRoutes } from "./models/routes";
import Guard from "./guards/route.guard"; import Guard from "./guards/route.guard";
import Id from "./guards/id.guard"; import Id from "./guards/id.guard";
import HomeView from "./pages/Home/HomeView";
const UserGuardedRoute = ({ element, permissions, allowedId }) => { const UserGuardedRoute = ({ element, permissions, allowedId }) => {
const { id_usuario } = useParams(); const { id_usuario } = useParams();
return permissions === 1 || id_usuario == allowedId ? (//double equal is necessary return permissions === 1 || id_usuario == allowedId ? ( //double equal is necessary
element element
) : ( ) : (
<Pagina404 /> <Pagina404 />
@@ -59,7 +60,7 @@ const App = () => {
path={`${PublicRoutes.DATAPROFESOR}:id_profesor`} path={`${PublicRoutes.DATAPROFESOR}:id_profesor`}
element={<PerfilProfesor />} element={<PerfilProfesor />}
/> />
<Route path={PublicRoutes.FILTER} element={<FilterProfesor />} /> <Route path={PublicRoutes.FILTER} element={<HomeView />} />
{!permissions && ( //only when you don't have permissions, can you pass {!permissions && ( //only when you don't have permissions, can you pass
<Route path={PublicRoutes.LOGIN} element={<Login />} /> <Route path={PublicRoutes.LOGIN} element={<Login />} />
Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+20 -17
View File
@@ -1,22 +1,18 @@
.card {
max-width: 90vw;
width: 400px; .transition{
height: 175px; opacity: 0;
margin: auto; transform: translateY(20px);
padding: 0.5em; transition: opacity 0.5s ease-out, transform 0.5s ease-out;
border-radius: 1em !important;
background: white !important;
box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: row !important;
justify-content: left;
text-align: center;
transition: box-shadow 0.3s ease, transform 0.3s ease;
} }
.card:hover { .transition.show {
box-shadow: 0 8px 16px 4px rgba(0, 0, 0, 0.2); display: flex;
transform: translateY(-5px); justify-content: center;
width: 40vw;
min-width: 340px;
opacity: 1;
transform: translateY(0);
} }
.card-content { .card-content {
@@ -37,6 +33,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
max-width: 130px;
} }
@@ -56,4 +53,10 @@
.status-label.inactive { .status-label.inactive {
background-color: red; background-color: red;
}
.profesores-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
} }
@@ -3,13 +3,19 @@ import "../Card.css";
import { PrivateRoutes, PublicRoutes } from "../../../models/routes"; import { PrivateRoutes, PublicRoutes } from "../../../models/routes";
import { useDeleteTeacher } from "../../../services/teacher.services"; import { useDeleteTeacher } from "../../../services/teacher.services";
const CardProfesor = ({ profesor, edificioNombre, permissions }) => { const API_URL = process.env.REACT_APP_API_URL;
const CardProfesor = ({
profesor,
edificioNombre,
permissions,
currentPage,
}) => {
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const [confirmText, setConfirmText] = useState(""); const [confirmText, setConfirmText] = useState("");
const { deleteTeacher } = useDeleteTeacher(); const { deleteTeacher } = useDeleteTeacher();
const defaultImage = const defaultImage =
"https://static.vecteezy.com/system/resources/thumbnails/020/765/399/small_2x/default-profile-account-unknown-icon-black-silhouette-free-vector.jpg"; "https://static.vecteezy.com/system/resources/thumbnails/020/765/399/small_2x/default-profile-account-unknown-icon-black-silhouette-free-vector.jpg";
const fotoProfesor = profesor.fotografia || defaultImage;
const handleDeleteClick = () => { const handleDeleteClick = () => {
setShowDialog(true); setShowDialog(true);
@@ -28,20 +34,28 @@ const CardProfesor = ({ profesor, edificioNombre, permissions }) => {
}; };
const handleViewProfesor = () => { const handleViewProfesor = () => {
localStorage.setItem("lastPage", currentPage);
window.location.href = `${PublicRoutes.DATAPROFESOR}${profesor.id_profesor}`; window.location.href = `${PublicRoutes.DATAPROFESOR}${profesor.id_profesor}`;
}; };
const handleImageError = (e) => {
e.target.src = defaultImage;
};
return ( return (
<div className="card m-1"> <div className="card m-1">
<div className="image"> <div className="image">
{fotoProfesor && fotoProfesor.startsWith("http") ? ( <img
<img src={
src={fotoProfesor} profesor.fotografia && profesor.fotografia.startsWith("http")
className="imgProfe" ? profesor.fotografia
alt={profesor.nombre} : `${API_URL}/imagenes/${profesor.fotografia}`
onClick={handleViewProfesor} }
/> className="imgProfe"
) : null} alt={profesor.nombre}
onClick={handleViewProfesor}
onError={handleImageError}
/>
{permissions ? ( {permissions ? (
<div <div
@@ -29,9 +29,4 @@
align-items: stretch; align-items: stretch;
} }
.form-control,
.btn {
width: 100%;
margin: 5px 0;
}
} }
@@ -16,6 +16,9 @@ function FilterProfesor() {
const categorias = useCategoria(); const categorias = useCategoria();
const edificios = useEdificio(); const edificios = useEdificio();
const [filters, setFilters] = useState({}); const [filters, setFilters] = useState({});
const [currentPage, setCurrentPage] = useState(
Number(localStorage.getItem("lastPage")) || 1
);
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
@@ -25,86 +28,22 @@ function FilterProfesor() {
categoria, categoria,
edificio, edificio,
}); });
setCurrentPage(1);
}; };
return ( return (
<div className="main"> <div>
<nav className="navbar filter" style={{ backgroundColor: "#292d2f" }}> <nav className="navbar filter" style={{ backgroundColor: "#292d2f" }}>
<div className="container-fluid justify-content-center"> <div className="container-fluid justify-content-center">
<form
className="d-flex align-items-center"
role="search"
onSubmit={handleSubmit}
>
<input
className="form-control me-2"
type="search"
placeholder="Nombre"
name="nombre"
value={nombre}
onChange={(e) => setNombre(e.target.value)}
aria-label="Nombre"
/>
<select
id="adscripcion"
value={adscripcion}
onChange={(e) => setAdscripcion(e.target.value)}
className="form-control me-2"
>
<option value="">Adscripcion</option>
{adscripciones.map((adscripcion) => (
<option
key={adscripcion.id_adscripcion}
value={adscripcion.id_adscripcion}
>
{adscripcion.adscripcion}
</option>
))}
</select>
<select
id="categoria"
value={categoria}
onChange={(e) => setCategoria(e.target.value)}
className="form-control me-2"
>
<option value="">Categoria</option>
{categorias.map((categoria) => (
<option
key={categoria.id_categoria}
value={categoria.id_categoria}
>
{categoria.categoria}
</option>
))}
</select>
<select
id="edificio"
value={edificio}
onChange={(e) => setEdificio(e.target.value)}
className="form-control me-2"
>
<option value="">Edificio</option>
{edificios.map((edificio) => (
<option key={edificio.id_edificio} value={edificio.id_edificio}>
{edificio.edificio}
</option>
))}
</select>
<button
className="btn me-2"
type="submit"
style={{ background: "#1e3c70", color: "#fff" }}
>
Buscar
</button>
</form>
</div> </div>
</nav> </nav>
<Mapeo filters={filters} edificios={edificios} /> <Mapeo
filters={filters}
edificios={edificios}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
/>
</div> </div>
); );
} }
+80 -26
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import "../../../App.css"; import "../../../App.css";
import CardProfesor from "./CardProfesor"; import CardProfesor from "./CardProfesor";
import Guard from "../../../guards/route.guard"; import Guard from "../../../guards/route.guard";
@@ -8,8 +8,8 @@ import {
} from "../../../services/teacher.services"; } from "../../../services/teacher.services";
import { PrivateRoutes } from "../../../models/routes"; import { PrivateRoutes } from "../../../models/routes";
const Mapeo = ({ filters, edificios }) => { const Mapeo = ({ filters, edificios, currentPage, setCurrentPage }) => {
const [currentPage, setCurrentPage] = useState(1); const [shownCards, setShownCards] = useState([]);
const permissions = Guard(); const permissions = Guard();
const { const {
@@ -18,6 +18,7 @@ const Mapeo = ({ filters, edificios }) => {
loading: loadingData, loading: loadingData,
error: errorData, error: errorData,
} = useTeachers(currentPage, filters); } = useTeachers(currentPage, filters);
const { const {
profesores: profesoresActivesData, profesores: profesoresActivesData,
totalPages: totalActivePages, totalPages: totalActivePages,
@@ -25,6 +26,50 @@ const Mapeo = ({ filters, edificios }) => {
error: errorActives, error: errorActives,
} = useTeachersActives(currentPage, filters); } = useTeachersActives(currentPage, filters);
const { profesores, totalPages } = permissions
? { profesores: profesoresData, totalPages: totalDataPages }
: { profesores: profesoresActivesData, totalPages: totalActivePages };
const [maxPagesToShow, setMaxPagesToShow] = useState(10);
useEffect(() => {
const updateMaxPagesToShow = () => {
if (window.innerWidth < 650) {
setMaxPagesToShow(5);
} else {
setMaxPagesToShow(10);
}
};
window.addEventListener("resize", updateMaxPagesToShow);
updateMaxPagesToShow();
return () => window.removeEventListener("resize", updateMaxPagesToShow);
}, []);
useEffect(() => {
const storedPage = localStorage.getItem("lastPage");
if (storedPage) {
localStorage.removeItem("lastPage");
}
}, [setCurrentPage]);
useEffect(() => {
if (!loadingData && !loadingActives && profesores) {
setShownCards([]);
profesores.forEach((_, index) => {
setTimeout(() => {
setShownCards((prev) => [...prev, index]);
}, index * 120);
});
}
}, [loadingData, loadingActives, profesores, currentPage]);
useEffect(() => {
setTimeout(() => {
window.scrollTo({ top: 0, behavior: "smooth" });
}, 100);
}, [currentPage]);
if (loadingData || loadingActives) { if (loadingData || loadingActives) {
return ( return (
<div className="perfil-profesor"> <div className="perfil-profesor">
@@ -49,10 +94,6 @@ const Mapeo = ({ filters, edificios }) => {
); );
} }
const { profesores, totalPages } = permissions
? { profesores: profesoresData, totalPages: totalDataPages }
: { profesores: profesoresActivesData, totalPages: totalActivePages };
const handleNextPage = () => { const handleNextPage = () => {
if (currentPage < totalPages) { if (currentPage < totalPages) {
setCurrentPage((prevPage) => prevPage + 1); setCurrentPage((prevPage) => prevPage + 1);
@@ -67,7 +108,6 @@ const Mapeo = ({ filters, edificios }) => {
const renderPageNumbers = () => { const renderPageNumbers = () => {
const pageNumbers = []; const pageNumbers = [];
const maxPagesToShow = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
@@ -103,10 +143,10 @@ const Mapeo = ({ filters, edificios }) => {
}; };
return ( return (
<div className="main-content"> <div className="">
{permissions ? ( {permissions ? (
<div className="containerButton"> <div className="containerButton">
<button className="blueButton" onClick={handleRegresar}> <button className="btn btn-primary" onClick={handleRegresar}>
Regresar Regresar
</button> </button>
<button className="blueButton" onClick={handleCrear}> <button className="blueButton" onClick={handleCrear}>
@@ -114,22 +154,36 @@ const Mapeo = ({ filters, edificios }) => {
</button> </button>
</div> </div>
) : null} ) : null}
<div className="container border">
{profesores.map((profesor) => { <div className="row">
const edificio = edificios.find( {profesores.map((profesor, index) => (
(e) => e.id_edificio === profesor.id_edificio <div className="col-4">{profesor.nombre}</div>
); ))}
const edificioNombre = edificio ? edificio.edificio : "Desconocido"; </div>
return ( </div>
<CardProfesor {/* <div className="profesores-container">
key={profesor.id_profesor} {profesores.map((profesor, index) => {
profesor={profesor} const edificio = edificios.find(
edificioNombre={edificioNombre} (e) => e.id_edificio === profesor.id_edificio
permissions={permissions} );
/> const edificioNombre = edificio ? edificio.edificio : "Desconocido";
); return (
})} <div
key={profesor.id_profesor}
className={`card-wrapper transition ${
shownCards.includes(index) ? "show" : ""
}`}
>
<CardProfesor
profesor={profesor}
edificioNombre={edificioNombre}
permissions={permissions}
currentPage={currentPage}
/>
</div>
);
})}
</div> */}
<nav aria-label="Page navigation example"> <nav aria-label="Page navigation example">
<ul className="pagination justify-content-center"> <ul className="pagination justify-content-center">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}> <li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import "../../../App.css"; import "../../../App.css";
import CardUsuario from "./CardUsuario"; import CardUsuario from "./CardUsuario";
import { PrivateRoutes } from "../../../models/routes"; import { PrivateRoutes } from "../../../models/routes";
@@ -8,6 +8,25 @@ const MapeoUsuario = ({ filters }) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const { usuarios, totalPages } = useUsers(currentPage, filters); const { usuarios, totalPages } = useUsers(currentPage, filters);
const [maxPagesToShow, setMaxPagesToShow] = useState(10);
useEffect(() => {
const updateMaxPagesToShow = () => {
if (window.innerWidth < 650) {
setMaxPagesToShow(5);
} else {
setMaxPagesToShow(10);
}
};
window.addEventListener("resize", updateMaxPagesToShow);
updateMaxPagesToShow();
return () => window.removeEventListener("resize", updateMaxPagesToShow);
}, []);
if (totalPages === 0) { if (totalPages === 0) {
return ( return (
<div className="perfil-profesor"> <div className="perfil-profesor">
@@ -36,7 +55,6 @@ const MapeoUsuario = ({ filters }) => {
const renderPageNumbers = () => { const renderPageNumbers = () => {
const pageNumbers = []; const pageNumbers = [];
const maxPagesToShow = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
@@ -74,10 +92,10 @@ const MapeoUsuario = ({ filters }) => {
return ( return (
<div className="main-content"> <div className="main-content">
<div className="containerButton"> <div className="containerButton">
<button className="blueButton" onClick={handleRegresar}> <button className="btn btn-primary" onClick={handleRegresar}>
Regresar Regresar
</button> </button>
<button className="blueButton" onClick={handleCrear}> <button className="btn btn-primary" onClick={handleCrear}>
Crear Usuario Crear Usuario
</button> </button>
</div> </div>
@@ -46,7 +46,7 @@ function NavUsuario() {
<button <button
className="btn me-2" className="btn me-2"
type="submit" type="submit"
style={{ background: "#1e3c70", color: "#fff" }} style={{ background: "#D59F0F", color: "#fff" }}
> >
Buscar Buscar
</button> </button>
+160
View File
@@ -0,0 +1,160 @@
import React, { useEffect, useState } from "react";
import {
useAdscripcion,
useCategoria,
useEdificio,
} from "../services/auth.services";
import { useSearchParams } from "react-router-dom";
export default function Filter() {
const adscripciones = useAdscripcion();
const categorias = useCategoria();
const edificios = useEdificio();
const [searchParams, setSearchParams] = useSearchParams();
const [filtro, setFiltro] = useState({
nombre: "",
adscripcion: "",
categoria: "",
edificio: "",
});
useEffect(() => {
const nombre = searchParams.get("nombre") || "";
const adscripcion = searchParams.get("adscripcion") || "";
const categoria = searchParams.get("categoria") || "";
const edificio = searchParams.get("edificio") || "";
setFiltro({ nombre, adscripcion, categoria, edificio });
}, [searchParams]);
const handleOnSubmit = (e) => {
e.preventDefault();
const queryParams = {};
if (filtro.nombre) queryParams.nombre = filtro.nombre;
if (filtro.adscripcion) queryParams.adscripcion = filtro.adscripcion;
if (filtro.categoria) queryParams.categoria = filtro.categoria;
if (filtro.edificio) queryParams.edificio = filtro.edificio;
// Actualizar los parámetros de la URL
setSearchParams(queryParams);
};
const handleChange = (e) => {
const { id, value } = e.target;
setFiltro({
...filtro,
[id]: value,
});
};
const handleClear = () => {
// Limpiar los filtros en el estado
setFiltro({
nombre: "",
adscripcion: "",
categoria: "",
edificio: "",
});
// Limpiar los parámetros de la URL
setSearchParams({});
};
return (
<form
onSubmit={handleOnSubmit}
className="m-2 p-2 position-sticky"
style={{ top: "10px" }}
>
<p className="text-start m-0 fs-3">Filtro</p>
<div className="mb-3">
<label htmlFor="nombre" className="form-label mb-0">
Nombre
</label>
<input
type="text"
className="form-control"
id="nombre"
value={filtro.nombre}
onChange={handleChange}
/>
</div>
<div className="mb-3">
<label htmlFor="adscripcion" className="form-label mb-0">
Adscripción
</label>
<select
id="adscripcion"
value={filtro.adscripcion}
onChange={handleChange}
className="form-control me-2"
>
<option value="">Selecciona Adscripción</option>
{adscripciones.map((adscripcion) => (
<option
key={adscripcion.id_adscripcion}
value={adscripcion.id_adscripcion}
>
{adscripcion.adscripcion}
</option>
))}
</select>
</div>
<div className="mb-3">
<label htmlFor="categoria" className="form-label mb-0">
Categoría
</label>
<select
id="categoria"
value={filtro.categoria}
onChange={handleChange}
className="form-control me-2"
>
<option value="">Selecciona Categoría</option>
{categorias.map((categoria) => (
<option key={categoria.id_categoria} value={categoria.id_categoria}>
{categoria.categoria}
</option>
))}
</select>
</div>
<div className="mb-3">
<label htmlFor="edificio" className="form-label mb-0">
Edificio
</label>
<select
id="edificio"
value={filtro.edificio}
onChange={handleChange}
className="form-control me-2"
>
<option value="">Selecciona Edificio</option>
{edificios.map((edificio) => (
<option key={edificio.id_edificio} value={edificio.id_edificio}>
{edificio.edificio}
</option>
))}
</select>
</div>
<div className="d-flex flex-column gap-2 justify-content-between">
<button type="submit" className="btn btn-primary w-45">
Filtrar
</button>
<button
type="button"
onClick={handleClear}
className="btn btn-secondary w-45"
>
Limpiar Filtro
</button>
</div>
</form>
);
}
+4 -10
View File
@@ -1,23 +1,17 @@
import React from "react"; import React from "react";
import "./style.css"; import "./style.css";
import logo1 from "../assets/img/escudo-b2.png"; // Ajusta la ruta si es necesario
import logo2 from "../assets/img/logo_unam-b.png"; // Ajusta la ruta si es necesario
const Header = () => { const Header = () => {
return ( return (
<header className="header"> <header className="header">
<a href="https://www.unam.mx/"> <a href="https://www.unam.mx/">
<img <img className="logo" alt="Logo 1" src={logo1} />
className="logo"
alt="Logo 1"
src="https://www.unam.mx/sites/all/themes/unam/logo.png"
/>
</a> </a>
<a href="https://www.acatlan.unam.mx/"> <a href="https://www.acatlan.unam.mx/">
<img <img className="logo" alt="Logo 2" src={logo2} />
className="logo"
alt="Logo 2"
src="https://www.acatlan.unam.mx/img/logo_fesa.png"
/>
</a> </a>
</header> </header>
); );
+12 -4
View File
@@ -20,12 +20,12 @@ const LoginForm = () => {
}; };
return ( return (
<div className="main-content"> <div className="flex-grow-1 d-flex flex-column justify-content-center align-items-center">
<h1 className="text-warning">Bienvenido al directorio de profesores</h1> <h1 className="text-warning">Bienvenido al directorio de profesores</h1>
<h2 className="m-3">Iniciar sesión</h2> <h2 className="m-3">Iniciar sesión</h2>
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="login-form" className="bg-white p-4 rounded shadow-sm"
style={{ background: "#f9f9f9", borderRadius: "1em" }} style={{ background: "#f9f9f9", borderRadius: "1em" }}
> >
<div className="input-group"> <div className="input-group">
@@ -62,12 +62,20 @@ const LoginForm = () => {
Mantener la sesión Mantener la sesión
</label> </label>
</div> </div>
<button type="submit" disabled={loading}> <button
type="submit"
disabled={loading}
className="btn btn-primary w-100"
>
Ingresar Ingresar
</button> </button>
{loading && <p>Cargando...</p>} {loading && <p>Cargando...</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
</form> </form>
{error && (
<div className="alert alert-danger mt-3" role="alert">
{error}
</div>
)}
</div> </div>
); );
}; };
+14 -8
View File
@@ -1,5 +1,4 @@
.nav-link .bg-warning { .nav-link {
background-color: #002b7a !important;
color: #212529 !important; color: #212529 !important;
} }
@@ -42,14 +41,21 @@
margin-left: auto; margin-left: auto;
} }
.red{ .red {
background-color: rgb(193, 17, 17); background-color: rgb(193, 17, 17);
color: white; color: white;
text-decoration: none; text-decoration: none;
} }
.container{ .notButton {
border: 2px solid rgb(77, 77, 77); text-align: start;
border-radius: 8px; background: none;
margin: 10px; border: none;
} box-shadow: none;
}
.notButton:hover{
background: none;
border: none;
box-shadow: none;
}
+12 -12
View File
@@ -92,47 +92,47 @@ const Menu = () => {
<div className="container"> <div className="container">
<li className="nav-item"> <li className="nav-item">
<a <button
className="nav-link" className="nav-link notButton"
onClick={() => onClick={() =>
(window.location.href = PrivateRoutes.CREARPROFESOR) (window.location.href = PrivateRoutes.CREARPROFESOR)
} }
> >
Crear Profesor Crear Profesor
</a> </button>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a <button
className="nav-link" className="nav-link notButton"
onClick={() => (window.location.href = PublicRoutes.FILTER)} onClick={() => (window.location.href = PublicRoutes.FILTER)}
> >
Editar Profesor Editar Profesor
</a> </button>
</li> </li>
</div> </div>
{permissions === 1 && ( {permissions === 1 && (
<div className="container"> <div className="container">
<li className="nav-item"> <li className="nav-item">
<a <button
className="nav-link" className="nav-link notButton"
onClick={() => onClick={() =>
(window.location.href = PrivateRoutes.CREARUSUARIO) (window.location.href = PrivateRoutes.CREARUSUARIO)
} }
> >
Crear Usuario Crear Usuario
</a> </button>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a <button
className="nav-link" className="nav-link notButton"
onClick={() => onClick={() =>
(window.location.href = PrivateRoutes.FILTERUSUARIO) (window.location.href = PrivateRoutes.FILTERUSUARIO)
} }
> >
Editar Usuario Editar Usuario
</a> </button>
</li> </li>
</div> </div>
)} )}
+288
View File
@@ -0,0 +1,288 @@
import React, { useEffect, useState } from "react";
import { useSearchParams, useNavigate } from "react-router-dom";
import Guard from "../guards/route.guard";
import { PrivateRoutes, PublicRoutes } from "../models/routes";
import { useDeleteTeacher } from "../services/teacher.services";
import "bootstrap/dist/css/bootstrap.min.css";
const API_URL = process.env.REACT_APP_API_URL;
export default function Profesores() {
const [showDialog, setShowDialog] = useState(false);
const [confirmText, setConfirmText] = useState("");
const [profesorToDelete, setProfesorToDelete] = useState(null); // Profesor que se va a eliminar
const { deleteTeacher } = useDeleteTeacher();
const permissions = Guard();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [profesores, setProfesores] = useState([]);
const [loading, setLoading] = useState(true);
const [totalPages, setTotalPages] = useState(1);
const [currentPage, setCurrentPage] = useState(1);
const fetchProfesores = async (filters) => {
const validFilters = Object.fromEntries(
Object.entries(filters).filter(([_, value]) => value)
);
const queryParams = new URLSearchParams(validFilters);
setLoading(true);
try {
const res = await (
await fetch(`${API_URL}/profesor/page?${queryParams}&limit=20`)
).json();
setProfesores(res.profesores);
setTotalPages(res.totalPages);
setCurrentPage(Number(filters.page) || 1);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
const nombre = searchParams.get("nombre");
const adscripcion = searchParams.get("adscripcion");
const categoria = searchParams.get("categoria");
const edificio = searchParams.get("edificio");
const page = searchParams.get("page") || "1";
fetchProfesores({ nombre, adscripcion, categoria, edificio, page });
}, [searchParams]);
const handlePageChange = (pageNumber) => {
const newParams = new URLSearchParams(searchParams);
newParams.set("page", pageNumber);
navigate(`?${newParams.toString()}`);
};
// Lógica para controlar las 5 páginas visibles
const getPageNumbers = () => {
const maxPagesToShow = 5;
let startPage, endPage;
if (totalPages <= maxPagesToShow) {
startPage = 1;
endPage = totalPages;
} else {
if (currentPage <= Math.ceil(maxPagesToShow / 2)) {
startPage = 1;
endPage = maxPagesToShow;
} else if (currentPage + Math.floor(maxPagesToShow / 2) >= totalPages) {
startPage = totalPages - maxPagesToShow + 1;
endPage = totalPages;
} else {
startPage = currentPage - Math.floor(maxPagesToShow / 2);
endPage = currentPage + Math.floor(maxPagesToShow / 2);
}
}
return Array.from(
{ length: endPage - startPage + 1 },
(_, index) => startPage + index
);
};
const handleEditClick = (id) => {
window.location.href = `${PrivateRoutes.EDITARPROFESOR}${id}`;
};
const handleViewProfesor = (id) => {
localStorage.setItem("lastPage", currentPage);
window.location.href = `${PublicRoutes.DATAPROFESOR}${id}`;
};
const handleConfirmDelete = async () => {
if (confirmText.toLowerCase() === "aceptar" && profesorToDelete) {
await deleteTeacher(profesorToDelete);
setShowDialog(false);
setConfirmText("");
setProfesorToDelete(null); // Resetear el profesor a eliminar
fetchProfesores({ page: currentPage }); // Refrescar la lista después de eliminar
}
};
const handleDeleteClick = (id) => {
setProfesorToDelete(id); // Guardar el profesor que se va a eliminar
setShowDialog(true); // Mostrar el modal
};
return (
<div className="my-2 p-2 position-sticky">
{loading ? (
<p>Cargando profesores...</p>
) : (
<>
{profesores.length > 0 ? (
<div className="row">
{profesores.map((profesor, index) => (
<div
key={index}
className="col-12 col-md-6 col-xl-4 mb-4"
style={{ cursor: "pointer" }}
onClick={() => handleViewProfesor(profesor.id_profesor)}
>
<div className="card h-100 shadow-sm">
<div className="card-body">
<div className="d-flex">
<img
src={
profesor.fotografia &&
profesor.fotografia.startsWith("http")
? profesor.fotografia
: `${API_URL}/imagenes/${profesor.fotografia}`
}
alt={profesor.nombre}
className="img-fluid rounded"
style={{
width: "80px",
height: "130px",
objectFit: "cover",
}}
onError={(e) => {
e.target.src = "/default-image.png"; // Imagen por defecto si no se carga
}}
/>
<div className="ms-3">
<h5 className="card-title mb-1">{profesor.nombre}</h5>
<strong className="text-muted">Ubicacion</strong>
<p className="mb-1 text-muted text-start">
{profesor.ubicacion}
</p>
<strong className="text-muted">Correo</strong>
<p className="text-break text-muted text-start m-0">
{profesor.correo_pcp}
</p>
</div>
</div>
</div>
{permissions && (
<div className="card-footer d-flex gap-2">
<button
className="btn btn-primary"
onClick={() => handleEditClick(profesor.id_profesor)}
>
Editar
</button>
<button
className="btn btn-danger"
onClick={() =>
handleDeleteClick(profesor.id_profesor)
}
>
Borrar
</button>
</div>
)}
</div>
</div>
))}
</div>
) : (
<p>No se encontraron profesores.</p>
)}
{/* Modal de confirmación */}
{showDialog && (
<div
className="modal fade show"
tabIndex="-1"
style={{ display: "block" }}
role="dialog"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Confirmar Eliminación</h5>
<button
type="button"
className="btn-close"
onClick={() => setShowDialog(false)}
></button>
</div>
<div className="modal-body">
<p>
Para eliminar al profesor, escribe{" "}
<strong>"aceptar"</strong> y confirma.
</p>
<input
type="text"
className="form-control"
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
placeholder="Escribe 'aceptar' para confirmar"
/>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={() => setShowDialog(false)}
>
Cancelar
</button>
<button
type="button"
className="btn btn-danger"
onClick={handleConfirmDelete}
disabled={confirmText.toLowerCase() !== "aceptar"}
>
Confirmar Eliminación
</button>
</div>
</div>
</div>
</div>
)}
{/* Paginación con Bootstrap */}
<nav className="d-flex justify-content-center mt-3">
<ul className="pagination">
<li className={`page-item ${currentPage === 1 && "disabled"}`}>
<button
className="page-link"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
Anterior
</button>
</li>
{getPageNumbers().map((pageNumber) => (
<li
key={pageNumber}
className={`page-item ${
pageNumber === currentPage ? "active" : ""
}`}
>
<button
className="page-link"
onClick={() => handlePageChange(pageNumber)}
>
{pageNumber}
</button>
</li>
))}
<li
className={`page-item ${
currentPage === totalPages && "disabled"
}`}
>
<button
className="page-link"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Siguiente
</button>
</li>
</ul>
</nav>
</>
)}
</div>
);
}
@@ -7,7 +7,8 @@ import {
useEdificio, useEdificio,
useUploadImage, useUploadImage,
} from "../../../services/auth.services"; } from "../../../services/auth.services";
import { useRegisterTeacher } from "../../../services/teacher.services"; import { useRegisterTeacher, useUpdateTeacherImage } from "../../../services/teacher.services";
import { PublicRoutes } from "../../../models/routes";
const FormProfesor = () => { const FormProfesor = () => {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
@@ -24,6 +25,7 @@ const FormProfesor = () => {
const [fotoBase64, setFotoBase64] = useState(""); const [fotoBase64, setFotoBase64] = useState("");
const [lineasInvestigacion, setLineasInvestigacion] = useState([]); const [lineasInvestigacion, setLineasInvestigacion] = useState([]);
const [proyectosAcademicos, setProyectosAcademicos] = useState([]); const [proyectosAcademicos, setProyectosAcademicos] = useState([]);
const [datosAcademicos,setDatosAcademicos] = useState([]);
const [id_usuario] = useState("1"); const [id_usuario] = useState("1");
const [num_trabajador, setNum_trabajador] = useState(""); const [num_trabajador, setNum_trabajador] = useState("");
const [rfc, setRfc] = useState(""); const [rfc, setRfc] = useState("");
@@ -37,15 +39,21 @@ const FormProfesor = () => {
const [mostrar, setMostrar] = useState(true); const [mostrar, setMostrar] = useState(true);
const [nombre, setNombre] = useState(""); const [nombre, setNombre] = useState("");
const [grado_maximo, setGrado_maximo] = useState(""); const [grado_maximo, setGrado_maximo] = useState("");
const [grados_obtenidos, setGrados_obtenidos] = useState("");
const { registerTeacher } = useRegisterTeacher(); const { registerTeacher } = useRegisterTeacher();
const { updateTeacherImage } = useUpdateTeacherImage();
const { uploadImage } = useUploadImage(); const { uploadImage } = useUploadImage();
const handlePageChange = (newPage) => { const handlePageChange = (newPage) => {
setPage(newPage); setPage(newPage);
}; };
const handleGrados_obtenidosChange = (index, value) => {
const newGrados = [...datosAcademicos];
newGrados[index].grados_obtenidos = value;
setDatosAcademicos(newGrados);
};
const handleLineaInvestigacionChange = (index, value) => { const handleLineaInvestigacionChange = (index, value) => {
const newLineas = [...lineasInvestigacion]; const newLineas = [...lineasInvestigacion];
newLineas[index].lineas_inv_html = value; newLineas[index].lineas_inv_html = value;
@@ -58,6 +66,10 @@ const FormProfesor = () => {
setProyectosAcademicos(newProyectos); setProyectosAcademicos(newProyectos);
}; };
const addGrados_obtenidos = () => {
setDatosAcademicos([...datosAcademicos, { grados_obtenidos: "" }]);
};
const addLineaInvestigacion = () => { const addLineaInvestigacion = () => {
setLineasInvestigacion([...lineasInvestigacion, { lineas_inv_html: "" }]); setLineasInvestigacion([...lineasInvestigacion, { lineas_inv_html: "" }]);
}; };
@@ -66,6 +78,11 @@ const FormProfesor = () => {
setProyectosAcademicos([...proyectosAcademicos, { proyecto_html: "" }]); setProyectosAcademicos([...proyectosAcademicos, { proyecto_html: "" }]);
}; };
const deleteGrados_obtenidos = (index) => {
const newGrados = datosAcademicos.filter((_, i) => i !== index);
setDatosAcademicos(newGrados);
};
const deleteLineaInvestigacion = (index) => { const deleteLineaInvestigacion = (index) => {
const newLineas = lineasInvestigacion.filter((_, i) => i !== index); const newLineas = lineasInvestigacion.filter((_, i) => i !== index);
setLineasInvestigacion(newLineas); setLineasInvestigacion(newLineas);
@@ -89,7 +106,7 @@ const FormProfesor = () => {
return false; return false;
} }
if (!grado_maximo || !grados_obtenidos || !correo_pcp) { if (!grado_maximo || !correo_pcp) {
alert( alert(
"Por favor, complete todos los campos requeridos en Datos Académicos." "Por favor, complete todos los campos requeridos en Datos Académicos."
); );
@@ -116,19 +133,15 @@ const FormProfesor = () => {
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
if (!validateForm()) { if (!validateForm()) {
return; return;
} }
try { try {
let imageUrl = ""; let imageUrl = "";
if (fotoBase64) { const id_profesor = await registerTeacher({
imageUrl = await uploadImage(fotoBase64, nombre);
}
const data = {
nombre, nombre,
id_usuario, id_usuario,
num_trabajador, num_trabajador,
@@ -147,16 +160,23 @@ const FormProfesor = () => {
activo, activo,
lineasInvestigacion, lineasInvestigacion,
proyectosAcademicos, proyectosAcademicos,
datosAcademicos: [ datosAcademicos: datosAcademicos.map(dato => ({
{ ...dato,
grado_maximo, grado_maximo: grado_maximo || dato.grado_maximo
grados_obtenidos, })),
},
],
fotografia: imageUrl, fotografia: imageUrl,
}; });
registerTeacher(data); if (fotoBase64) {
imageUrl = await uploadImage(fotoBase64, id_profesor);
}
if (imageUrl) {
await updateTeacherImage(id_profesor, imageUrl);
}
window.location.href = PublicRoutes.FILTER;
} catch (error) { } catch (error) {
if (error.response && error.response.status === 403) { if (error.response && error.response.status === 403) {
if (error.response.data.message === "RFC already exists") { if (error.response.data.message === "RFC already exists") {
@@ -178,6 +198,7 @@ const FormProfesor = () => {
} }
} }
}; };
const onDrop = useCallback((acceptedFiles) => { const onDrop = useCallback((acceptedFiles) => {
const file = acceptedFiles[0]; const file = acceptedFiles[0];
@@ -204,7 +225,7 @@ const FormProfesor = () => {
return ( return (
<div className="form-container"> <div className="form-container">
<div className="containerButton"> <div className="containerButton">
<button className="blueButton" onClick={handleRegresar}> <button className="btn btn-primary" onClick={handleRegresar}>
Regresar Regresar
</button> </button>
</div> </div>
@@ -305,19 +326,6 @@ const FormProfesor = () => {
/> />
</div> </div>
<div className="form-group">
<label>
Grados Obtenidos <span className="required">*</span>
</label>
<input
type="text"
name="grados_obtenidos"
required
value={grados_obtenidos}
onChange={(e) => setGrados_obtenidos(e.target.value)}
/>
</div>
<div className="form-group"> <div className="form-group">
<label> <label>
Correo Pcpuma <span className="required">*</span> Correo Pcpuma <span className="required">*</span>
@@ -496,19 +504,56 @@ const FormProfesor = () => {
)} )}
{page === 4 && ( {page === 4 && (
<div className="form-section">
<h2>Grados obtenidos</h2>
<div className="form-group">
{datosAcademicos.map((grado, index) => (
<div key={index} className="input-with-delete">
<label>
Grado obtenido {index + 1}
<span className="required">*</span>
</label>
<input
type="text"
name={`grados_obtenidos_${index}`}
value={grado.grados_obtenidos}
onChange={(e) =>
handleGrados_obtenidosChange(index, e.target.value)
}
/>
<button
type="button"
className="btn btn-danger"
onClick={() => deleteGrados_obtenidos(index)}
></button>
</div>
))}
</div>
<button
type="button"
className="boton"
onClick={addGrados_obtenidos}
>
Agregar Grados de investigacion
</button>
</div>
)}
{page === 5 && (
<div className="form-section"> <div className="form-section">
<h2>Lineas de Investigacion</h2> <h2>Lineas de Investigacion</h2>
<div className="form-group"> <div className="form-group">
{lineasInvestigacion.map((linea, index) => ( {lineasInvestigacion.map((linea, index) => (
<div key={index} className="input-with-delete"> <div key={index} className="input-with-delete">
<label> <label>
Lineas de Investigacion {index + 1} Linea de Investigacion {index + 1}
<span className="required">*</span> <span className="required">*</span>
</label> </label>
<input <input
type="text" type="text"
name={`lineas_inv_${index}`} name={`lineas_inv_${index}`}
value={linea.lineas_inv} value={linea.lineas_inv_html}
onChange={(e) => onChange={(e) =>
handleLineaInvestigacionChange(index, e.target.value) handleLineaInvestigacionChange(index, e.target.value)
} }
@@ -532,7 +577,7 @@ const FormProfesor = () => {
</div> </div>
)} )}
{page === 5 && ( {page === 6 && (
<div className="form-section"> <div className="form-section">
<h2>Proyectos Académicos</h2> <h2>Proyectos Académicos</h2>
<div className="form-group"> <div className="form-group">
@@ -545,7 +590,7 @@ const FormProfesor = () => {
<input <input
type="text" type="text"
name={`proyecto_${index}`} name={`proyecto_${index}`}
value={proyecto.proyecto} value={proyecto.proyecto_html}
onChange={(e) => onChange={(e) =>
handleProyectoAcademicoChange(index, e.target.value) handleProyectoAcademicoChange(index, e.target.value)
} }
@@ -570,7 +615,7 @@ const FormProfesor = () => {
)} )}
<div className="button-container"> <div className="button-container">
{page === 5 && ( {page === 6 && (
<button type="submit" className="small-button"> <button type="submit" className="small-button">
Registrar Registrar
</button> </button>
@@ -628,7 +673,15 @@ const FormProfesor = () => {
5 5
</button> </button>
</li> </li>
<li className={`page-item ${page === 5 ? "disabled" : ""}`}> <li className={`page-item ${page === 6 ? "active" : ""}`}>
<button
className="page-link buttons"
onClick={() => handlePageChange(6)}
>
6
</button>
</li>
<li className={`page-item ${page === 6 ? "disabled" : ""}`}>
<button <button
className="page-link nextButtons" className="page-link nextButtons"
onClick={() => handlePageChange(page + 1)} onClick={() => handlePageChange(page + 1)}
@@ -642,4 +695,4 @@ const FormProfesor = () => {
); );
}; };
export default FormProfesor; export default FormProfesor;
@@ -30,7 +30,7 @@ const FormEditProfesor = () => {
const [fotoBase64, setFotoBase64] = useState(""); const [fotoBase64, setFotoBase64] = useState("");
const [lineasInvestigacion, setLineasInvestigacion] = useState([]); const [lineasInvestigacion, setLineasInvestigacion] = useState([]);
const [proyectosAcademicos, setProyectosAcademicos] = useState([]); const [proyectosAcademicos, setProyectosAcademicos] = useState([]);
const [datosAcademicos, setDatosAcademicos] = useState([]);
const [num_trabajador, setNum_trabajador] = useState(""); const [num_trabajador, setNum_trabajador] = useState("");
const [rfc, setRfc] = useState(""); const [rfc, setRfc] = useState("");
const [homoclave, setHomoclave] = useState(""); const [homoclave, setHomoclave] = useState("");
@@ -44,8 +44,6 @@ const FormEditProfesor = () => {
const [nombre, setNombre] = useState(""); const [nombre, setNombre] = useState("");
const [grado_maximo, setGrado_maximo] = useState(""); const [grado_maximo, setGrado_maximo] = useState("");
const [grados_obtenidos, setGrados_obtenidos] = useState("");
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const { teacher } = useTeacher(id_profesor); const { teacher } = useTeacher(id_profesor);
const { updateTeacher } = useUpdateTeacher(); const { updateTeacher } = useUpdateTeacher();
@@ -65,6 +63,7 @@ const FormEditProfesor = () => {
setActivo(teacher.activo); setActivo(teacher.activo);
setLineasInvestigacion(teacher.lineasInvestigacion); setLineasInvestigacion(teacher.lineasInvestigacion);
setProyectosAcademicos(teacher.proyectosAcademicos); setProyectosAcademicos(teacher.proyectosAcademicos);
setDatosAcademicos(teacher.datosAcademicos);
setNum_trabajador(teacher.num_trabajador); setNum_trabajador(teacher.num_trabajador);
setRfc(teacher.rfc); setRfc(teacher.rfc);
setHomoclave(teacher.homoclave); setHomoclave(teacher.homoclave);
@@ -75,11 +74,16 @@ const FormEditProfesor = () => {
setCorreo_per(teacher.correo_per); setCorreo_per(teacher.correo_per);
setUbicacion(teacher.ubicacion); setUbicacion(teacher.ubicacion);
setGrado_maximo(teacher.datosAcademicos[0]?.grado_maximo || ""); setGrado_maximo(teacher.datosAcademicos[0]?.grado_maximo || "");
setGrados_obtenidos(teacher.datosAcademicos[0]?.grados_obtenidos || "");
setOldFoto(teacher.fotografia); setOldFoto(teacher.fotografia);
} }
}, [teacher]); }, [teacher]);
const handleGrados_obtenidosChange = (index, value) => {
const newGrados = [...datosAcademicos];
newGrados[index].grados_obtenidos = value;
setDatosAcademicos(newGrados);
};
const handleLineaInvestigacionChange = (index, value) => { const handleLineaInvestigacionChange = (index, value) => {
const newLineas = [...lineasInvestigacion]; const newLineas = [...lineasInvestigacion];
newLineas[index].lineas_inv_html = value; newLineas[index].lineas_inv_html = value;
@@ -92,6 +96,10 @@ const FormEditProfesor = () => {
setProyectosAcademicos(newProyectos); setProyectosAcademicos(newProyectos);
}; };
const addGrados_obtenidos = () => {
setDatosAcademicos([...datosAcademicos, { grados_obtenidos: "" }]);
};
const addLineaInvestigacion = () => { const addLineaInvestigacion = () => {
setLineasInvestigacion([...lineasInvestigacion, { lineas_inv_html: "" }]); setLineasInvestigacion([...lineasInvestigacion, { lineas_inv_html: "" }]);
}; };
@@ -100,6 +108,11 @@ const FormEditProfesor = () => {
setProyectosAcademicos([...proyectosAcademicos, { proyecto_html: "" }]); setProyectosAcademicos([...proyectosAcademicos, { proyecto_html: "" }]);
}; };
const deleteGrados_obtenidos = (index) => {
const newGrados = datosAcademicos.filter((_, i) => i !== index);
setDatosAcademicos(newGrados);
};
const deleteLineaInvestigacion = (index) => { const deleteLineaInvestigacion = (index) => {
const newLineas = lineasInvestigacion.filter((_, i) => i !== index); const newLineas = lineasInvestigacion.filter((_, i) => i !== index);
setLineasInvestigacion(newLineas); setLineasInvestigacion(newLineas);
@@ -123,9 +136,14 @@ const FormEditProfesor = () => {
try { try {
if (fotoBase64) { if (fotoBase64) {
imageUrl = await updateImage(fotoBase64, nombre); imageUrl = await updateImage(fotoBase64, id_profesor);
} }
const updatedDatosAcademicos = datosAcademicos.map((dato) => ({
...dato,
grado_maximo: grado_maximo || dato.grado_maximo,
}));
const profesorData = { const profesorData = {
...profesor, ...profesor,
nombre, nombre,
@@ -145,12 +163,7 @@ const FormEditProfesor = () => {
activo, activo,
lineasInvestigacion, lineasInvestigacion,
proyectosAcademicos, proyectosAcademicos,
datosAcademicos: [ datosAcademicos: updatedDatosAcademicos,
{
grado_maximo,
grados_obtenidos,
},
],
fotografia: imageUrl || profesor.fotografia, fotografia: imageUrl || profesor.fotografia,
}; };
@@ -195,7 +208,7 @@ const FormEditProfesor = () => {
return ( return (
<div className="form-container"> <div className="form-container">
<div className="containerButton"> <div className="containerButton">
<button className="blueButton" onClick={handleRegresar}> <button className="btn btn-primary" onClick={handleRegresar}>
Regresar Regresar
</button> </button>
</div> </div>
@@ -284,27 +297,21 @@ const FormEditProfesor = () => {
<div className="form-section"> <div className="form-section">
<h2>Datos Académicos</h2> <h2>Datos Académicos</h2>
<div className="form-group"> <div className="form-group">
<label> {datosAcademicos.length > 0 && datosAcademicos[0].grado_maximo ? ( // Verifica que haya al menos un dato y que tenga grado_maximo
Grado Maximo <span className="required">*</span> <div>
</label> <label>
<input Grado Máximo <span className="required">*</span>
type="text" </label>
name="grado_maximo" <input
value={grado_maximo} type="text"
onChange={(e) => setGrado_maximo(e.target.value)} name="grado_maximo"
/> value={datosAcademicos[0].grado_maximo}
</div> onChange={(e) => setGrado_maximo(e.target.value)}
/>
<div className="form-group"> </div>
<label> ) : (
Grados Obtenidos <span className="required">*</span> <div>No hay grado máximo registrado.</div>
</label> )}
<input
type="text"
name="grados_obtenidos"
value={grados_obtenidos}
onChange={(e) => setGrados_obtenidos(e.target.value)}
/>
</div> </div>
<div className="form-group"> <div className="form-group">
@@ -496,6 +503,44 @@ const FormEditProfesor = () => {
)} )}
{page === 4 && ( {page === 4 && (
<div className="form-section">
<h2>Grados obtenidos</h2>
<div className="form-group">
{datosAcademicos.map((grado, index) => (
<div key={index} className="input-with-delete">
<label>
Grado obtenido {index + 1}
<span className="required">*</span>
</label>
<input
type="text"
name={`grados_obtenidos_${index}`}
value={grado.grados_obtenidos}
onChange={(e) =>
handleGrados_obtenidosChange(index, e.target.value)
}
/>
<button
type="button"
className="delete-botones"
onClick={() => deleteGrados_obtenidos(index)}
></button>
</div>
))}
</div>
<div className="d-flex justify-content-center">
<button
type="button"
className="btn btn-primary"
onClick={addGrados_obtenidos}
>
Agregar Grados de investigacion
</button>
</div>
</div>
)}
{page === 5 && (
<div className="form-section"> <div className="form-section">
<h2>Lineas de Investigacion</h2> <h2>Lineas de Investigacion</h2>
<div className="form-group"> <div className="form-group">
@@ -532,7 +577,7 @@ const FormEditProfesor = () => {
</div> </div>
)} )}
{page === 5 && ( {page === 6 && (
<div className="form-section"> <div className="form-section">
<h2>Proyectos Académicos</h2> <h2>Proyectos Académicos</h2>
<div className="form-group"> <div className="form-group">
@@ -572,69 +617,76 @@ const FormEditProfesor = () => {
<button type="submit" className="btn btn-primary boton"> <button type="submit" className="btn btn-primary boton">
Guardar modificacion Guardar modificacion
</button> </button>
</form> </form>
<nav aria-label="Page navigation example"> <nav aria-label="Page navigation example">
<ul className="pagination justify-content-center"> <ul className="pagination justify-content-center">
<li className={`page-item ${page === 1 ? "disabled" : ""}`}> <li className={`page-item ${page === 1 ? "disabled" : ""}`}>
<button <button
className="page-link prevButtons" className="page-link prevButtons"
onClick={() => handlePageChange(page - 1)} onClick={() => handlePageChange(page - 1)}
> >
Anterior Anterior
</button> </button>
</li> </li>
<li className={`page-item ${page === 1 ? "active" : ""}`}> <li className={`page-item ${page === 1 ? "active" : ""}`}>
<button <button
className="page-link buttons" className="page-link buttons"
onClick={() => handlePageChange(1)} onClick={() => handlePageChange(1)}
> >
1 1
</button> </button>
</li> </li>
<li className={`page-item ${page === 2 ? "active" : ""}`}> <li className={`page-item ${page === 2 ? "active" : ""}`}>
<button <button
className="page-link buttons" className="page-link buttons"
onClick={() => handlePageChange(2)} onClick={() => handlePageChange(2)}
> >
2 2
</button> </button>
</li> </li>
<li className={`page-item ${page === 3 ? "active" : ""}`}> <li className={`page-item ${page === 3 ? "active" : ""}`}>
<button <button
className="page-link buttons" className="page-link buttons"
onClick={() => handlePageChange(3)} onClick={() => handlePageChange(3)}
> >
3 3
</button> </button>
</li> </li>
<li className={`page-item ${page === 4 ? "active" : ""}`}> <li className={`page-item ${page === 4 ? "active" : ""}`}>
<button <button
className="page-link buttons" className="page-link buttons"
onClick={() => handlePageChange(4)} onClick={() => handlePageChange(4)}
> >
4 4
</button> </button>
</li> </li>
<li className={`page-item ${page === 5 ? "active" : ""}`}> <li className={`page-item ${page === 5 ? "active" : ""}`}>
<button <button
className="page-link buttons" className="page-link buttons"
onClick={() => handlePageChange(5)} onClick={() => handlePageChange(5)}
> >
5 5
</button> </button>
</li> </li>
<li className={`page-item ${page === 5 ? "disabled" : ""}`}> <li className={`page-item ${page === 6 ? "active" : ""}`}>
<button <button
className="page-link nextButtons" className="page-link buttons"
onClick={() => handlePageChange(page + 1)} onClick={() => handlePageChange(6)}
> >
Siguiente 6
</button> </button>
</li> </li>
</ul> <li className={`page-item ${page === 6 ? "disabled" : ""}`}>
</nav> <button
className="page-link nextButtons"
onClick={() => handlePageChange(page + 1)}
>
Siguiente
</button>
</li>
</ul>
</nav>
</div> </div>
); );
}; };
@@ -3,13 +3,14 @@
align-items: center; align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
margin-bottom: 20px; margin-bottom: 20px;
gap: 20px;
} }
.foto { .foto {
width: auto; width: auto;
max-width: 40vw; max-width: 40vw;
height: 250px; height: 250px;
max-height: 25vh; max-height: 50vh;
border-radius: 1em; border-radius: 1em;
object-fit: cover; object-fit: cover;
} }
@@ -27,7 +28,7 @@
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5px; border-radius: 5px;
background-color: white; background-color: white;
margin: 20px auto; margin: 10px auto;
width: 800px; width: 800px;
max-width: 90vw; max-width: 90vw;
} }
@@ -38,6 +39,11 @@
align-items: start; align-items: start;
} }
.list{
margin-left: 20px;
list-style: circle;
}
.datos-academicos, .datos-academicos,
.lineas-investigacion, .lineas-investigacion,
@@ -13,6 +13,8 @@ import {
useTeacher, useTeacher,
} from "../../../services/teacher.services"; } from "../../../services/teacher.services";
const API_URL = process.env.REACT_APP_API_URL;
const defaultImage = const defaultImage =
"https://static.vecteezy.com/system/resources/thumbnails/020/765/399/small_2x/default-profile-account-unknown-icon-black-silhouette-free-vector.jpg"; "https://static.vecteezy.com/system/resources/thumbnails/020/765/399/small_2x/default-profile-account-unknown-icon-black-silhouette-free-vector.jpg";
@@ -73,18 +75,47 @@ const PerfilProfesor = () => {
window.history.back(); window.history.back();
}; };
const splitTextToListItems = (text) => {
const parts = text.split(".").filter((part) => part.trim() !== "");
return parts.map((part, index) => {
const formattedPart = part.trim() + (index < parts.length - 1 ? "." : "");
return (
<li key={index} className="list">
{formattedPart}
</li>
);
});
};
const handleImageError = (e) => {
e.target.src = defaultImage;
};
return ( return (
<div className="form-container"> <div className="form-container">
<div className="containerButton"> <div className="containerButton">
<button className="blueButton" onClick={handleRegresar}> <button className="btn btn-primary" onClick={handleRegresar}>
Regresar Regresar
</button> </button>
</div> </div>
<div className="perfil-profesor"> <div className="perfil-profesor">
<div className="foto-nombre"> <div className="foto-nombre">
{fotoProfesor && fotoProfesor.startsWith("http") ? ( {fotoProfesor && fotoProfesor.startsWith("http") ? (
<img src={fotoProfesor} alt={profesor.nombre} className="foto" /> <img
) : null} src={fotoProfesor}
alt={profesor.nombre}
className="foto"
onError={handleImageError}
/>
) : (
<img
src={`${API_URL}/imagenes/${profesor.fotografia}`}
alt={profesor.nombre}
className="foto"
onError={handleImageError}
/>
)}
<div className="info"> <div className="info">
<div className="nombre"> <div className="nombre">
@@ -120,22 +151,34 @@ const PerfilProfesor = () => {
<h4>Datos académicos</h4> <h4>Datos académicos</h4>
</div> </div>
<ul> <ul>
{profesor.datosAcademicos.map((datosAcademicos, index) => ( <li>
<React.Fragment key={index}> <strong>Grado Máximo:</strong>
<li key={`grado_maximo_${index}`}> {profesor.datosAcademicos.length > 0 ? (
<strong>Grado Máximo:</strong> profesor.datosAcademicos[0].grado_maximo ? (
{datosAcademicos.grado_maximo <>{profesor.datosAcademicos[0].grado_maximo}</>
? datosAcademicos.grado_maximo ) : (
: "NO encontrado"} <>No hay Grado Máximo registrado.</>
</li> )
<li key={`grados_obtenidos_${index}`}> ) : (
<strong>Grados Obtenidos:</strong> <li>No hay Datos Académicos registrados.</li>
{datosAcademicos.grados_obtenidos )}
? datosAcademicos.grados_obtenidos </li>
: "NO encontrado"}
</li> <strong>Grados Obtenidos:</strong>
</React.Fragment> <ul>
))} {profesor.datosAcademicos.length > 0 ? (
profesor.datosAcademicos.map((item, index) =>
item.grados_obtenidos ? (
<li key={`grado_obtenido_${index}`}>
{splitTextToListItems(item.grados_obtenidos)}
</li>
) : null
)
) : (
<li>No hay Grados registrados.</li>
)}
</ul>
<li> <li>
<strong>Categoría:</strong> <strong>Categoría:</strong>
{categoriaProfesor ? categoriaProfesor.categoria : "No ubicado"} {categoriaProfesor ? categoriaProfesor.categoria : "No ubicado"}
@@ -154,12 +197,16 @@ const PerfilProfesor = () => {
<h4>Líneas de investigación</h4> <h4>Líneas de investigación</h4>
</div> </div>
<ul> <ul>
<strong>Investigaciones:</strong>
{profesor.lineasInvestigacion.length > 0 ? ( {profesor.lineasInvestigacion.length > 0 ? (
profesor.lineasInvestigacion.map((linea, index) => ( profesor.lineasInvestigacion.map((linea, index) => (
<li key={index}> <div key={index}>
<strong>Investigacion {index + 1}:</strong> <ul>
{linea.lineas_inv_html ? linea.lineas_inv_html : "NO encontrado"} {linea.lineas_inv_html
</li> ? splitTextToListItems(linea.lineas_inv_html)
: null}
</ul>
</div>
)) ))
) : ( ) : (
<li>No hay líneas de investigación registradas.</li> <li>No hay líneas de investigación registradas.</li>
@@ -172,12 +219,16 @@ const PerfilProfesor = () => {
<h4>Proyectos académicos</h4> <h4>Proyectos académicos</h4>
</div> </div>
<ul> <ul>
<strong>Proyectos:</strong>
{profesor.proyectosAcademicos.length > 0 ? ( {profesor.proyectosAcademicos.length > 0 ? (
profesor.proyectosAcademicos.map((proyecto, index) => ( profesor.proyectosAcademicos.map((proyecto, index) => (
<li key={index}> <div key={index}>
<strong>Proyecto {index + 1}:</strong> <ul>
{proyecto.proyecto_html ? proyecto.proyecto_html : "NO encontrado"} {proyecto.proyecto_html
</li> ? splitTextToListItems(proyecto.proyecto_html)
: null}
</ul>
</div>
)) ))
) : ( ) : (
<li>No hay proyectos académicos registrados.</li> <li>No hay proyectos académicos registrados.</li>
@@ -207,7 +258,7 @@ const PerfilProfesor = () => {
</div> </div>
{showDialog && ( {showDialog && (
<div className="dialog"> <div className="dialog" style={{ position: "fixed" }}>
<p> <p>
¿Deseas eliminar al profesor <strong>{profesor.nombre}</strong>? ¿Deseas eliminar al profesor <strong>{profesor.nombre}</strong>?
</p> </p>
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import "../../../App.css"; import "../../../App.css";
import { PrivateRoutes } from "../../../models/routes";
import { useUser, useUpdateUser } from "../../../services/user.services"; import { useUser, useUpdateUser } from "../../../services/user.services";
const MDUser = () => { const MDUser = () => {
+6 -3
View File
@@ -4,12 +4,14 @@
align-items: center; align-items: center;
background-color: #1e3c70 !important; background-color: #1e3c70 !important;
padding: 10px; padding: 10px;
padding-left: 40px;
padding-right: 40px;
text-align: center; text-align: center;
} }
.logo { .logo {
width: 300px; width: 50px;
max-width: 40vw; max-width: 10vw;
} }
.footer { .footer {
@@ -20,9 +22,10 @@
text-align: center; text-align: center;
bottom: 0; bottom: 0;
margin-top: auto; margin-top: auto;
padding: 10px;
} }
.footer .container { .footer .container {
max-width: 1200px; max-width: 90%;
margin: 0 auto; margin: 0 auto;
} }
-1
View File
@@ -7,7 +7,6 @@ const Id = () => {
const jwtPermissions = () => { const jwtPermissions = () => {
const jwtToken = localStorage.getItem("Token"); const jwtToken = localStorage.getItem("Token");
if (!jwtToken) { if (!jwtToken) {
console.error("No se encontró el token en localStorage");
return; return;
} }
-1
View File
@@ -7,7 +7,6 @@ const Guard = () => {
const jwtPermissions = () => { const jwtPermissions = () => {
const jwtToken = localStorage.getItem("Token"); const jwtToken = localStorage.getItem("Token");
if (!jwtToken) { if (!jwtToken) {
console.error("No se encontró el token en localStorage");
return; return;
} }
+3
View File
@@ -11,3 +11,6 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace; monospace;
} }
*{
scrollbar-width: none;
}
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
import Filter from "../../components/Filter";
import Profesores from "../../components/Profesores";
export default function HomeView() {
return (
<div className="container-fluid">
<div className="row">
<aside className="col-12 col-lg-3">
<Filter />
</aside>
<main className="col-12 col-lg-9">
<Profesores />
</main>
</div>
</div>
);
}
+2 -2
View File
@@ -77,7 +77,7 @@ export const useUploadImage = () => {
} }
const result = await response.json(); const result = await response.json();
return `${API_URL}${result.imageUrl}`; return result.imageUrl;
} catch (error) { } catch (error) {
throw error; throw error;
} }
@@ -112,7 +112,7 @@ export const useUpdateImage = () => {
} }
const result = await response.json(); const result = await response.json();
return `${API_URL}${result.imageUrl}`; return result.imageUrl;
} catch (error) { } catch (error) {
throw error; throw error;
} }
+54 -22
View File
@@ -117,34 +117,37 @@ export const useTeachers = (currentPage, filters) => {
export const useRegisterTeacher = () => { export const useRegisterTeacher = () => {
const token = useToken(); const token = useToken();
const registerTeacher = (teacherData) => { const registerTeacher = async (teacherData) => {
return fetch(`${API_URL}/profesor`, { try {
method: "POST", const response = await fetch(`${API_URL}/profesor`, {
headers: { method: "POST",
"Content-Type": "application/json", headers: {
Authorization: `Bearer ${token}`, "Content-Type": "application/json",
}, Authorization: `Bearer ${token}`,
body: JSON.stringify(teacherData), },
}) body: JSON.stringify(teacherData),
.then((response) => {
if (!response.ok) {
console.error("Error:");
}
return response.json();
})
.then((data) => {
console.log("Success:", data);
window.location.href = PublicRoutes.FILTER;
})
.catch((error) => {
console.error("Error:", error);
throw error;
}); });
if (!response.ok) {
console.error("Error:");
throw new Error("Error registering teacher");
}
const data = await response.json();
console.log("Success:", data);
return data.id_profesor;
} catch (error) {
console.error("Error:", error);
throw error;
}
}; };
return { registerTeacher }; return { registerTeacher };
}; };
export const useTeacher = (id_profesor) => { export const useTeacher = (id_profesor) => {
const [teacher, setTeacher] = useState(null); const [teacher, setTeacher] = useState(null);
@@ -196,3 +199,32 @@ export const useUpdateTeacher = () => {
return { updateTeacher }; return { updateTeacher };
}; };
export const useUpdateTeacherImage = () => {
const token = useToken();
const updateTeacherImage = async (id_profesor, imageUrl) => {
try {
const response = await fetch(`${API_URL}/profesor/${id_profesor}/image`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ fotografia: imageUrl }),
});
if (!response.ok) {
console.error("Error updating teacher image");
throw new Error("Error updating teacher image");
}
console.log("Image URL updated successfully");
} catch (error) {
console.error("Error:", error);
throw error;
}
};
return { updateTeacherImage };
};