/**
* @module Pages
* @category Routes
*/
import React, { useEffect, useState } from "react";
import bee from "../assets/bee.png";
import beeDark from "../assets/beedark.png";
import panelFondo from "../assets/panelFondo.png";
import Board from "../components/Board";
import { useUserStore } from "../config/userStore";
import useAxiosStore from "../hooks/useAxios";
import { useProjectsStore } from "../config/projectsStore";
import SettingsIcon from "@mui/icons-material/Settings";
import AddIcon from "@mui/icons-material/Add";
import { useTheme } from "../context/ThemeContext";
import FormModal from "../modals/FormModal.jsx";
import * as Yup from "yup";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import { brown } from "@mui/material/colors";
import fotoCambiar from "../assets/margarita.png";
/**
* @page
* Componente UserProfile
*
* Este componente muestra la página de perfil del usuario, incluyendo su información personal,
* proyectos actuales y proyectos completados. También proporciona funcionalidad para editar el
* perfil del usuario y crear nuevos proyectos.
*
* @component
* @returns {JSX.Element} Página de perfil de usuario, mostrando sus proyectos actuales y los finalizados.
*/
const UserProfile = () => {
const {
isLoading,
actualProjects,
completedProjects,
setIsLoading,
setActualProjects,
setCompletedProjects,
} = useProjectsStore();
const { user, setUser } = useUserStore();
const { fetch: newFetch } = useAxiosStore();
const token = localStorage.getItem("token");
const { isDarkMode } = useTheme();
const [modalNewProjectOpen, setmodalNewProjectOpen] = useState(false);
const [modalEditProfileOpen, setmodalEditProfileOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState({
src: null,
file: null,
name: "",
});
const validationSchemaNewProject = Yup.object().shape({
name: Yup.string()
.trim()
.required("El campo 'Nombre proyecto' es obligatorio"),
description: Yup.string()
.trim()
.required("El campo 'Descripción' es obligatorio."),
dateIni: Yup.date()
.required("El campo 'Fecha' es obligatorio")
.min(new Date(), "La fecha debe ser posterior a la actual"),
dateEnd: Yup.date()
.required("El campo 'Fecha' es obligatorio")
.min(new Date(), "La fecha debe ser posterior a la actual"),
});
const validationSchemaEditProfile = Yup.object().shape({
name: Yup.string().trim().required("El campo 'Nombre' es obligatorio"),
email: Yup.string()
.trim()
.matches(
/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/,
"El formato del email no es válido"
)
.required("El campo 'Email' es obligatorio"),
});
// Obtiene los datos del usuario actual desde la API
async function getUser() {
try {
if (user) {
const response = await newFetch(
`${import.meta.env.VITE_BASE_API}usuarios/${user._id || user.id}`,
"GET",
null,
{ Authorization: `Bearer ${token}` }
);
if (response.error) throw new Error(response.error);
const userData = await response.data;
setUser(userData);
}
} catch (error) {
console.error("Error al obtener los datos del usuario:", error);
}
}
// Obtiene los proyectos del usuario desde la API y los categoriza como actuales o completados
async function getProjects() {
try {
if (user) {
const [adminProjects, collaboratorProjects] = await Promise.all([
newFetch(
import.meta.env.VITE_BASE_API +
`tableros/administrador/${user._id || user.id}`,
"GET",
null,
{ Authorization: `Bearer ${token}` }
),
newFetch(
import.meta.env.VITE_BASE_API +
`tableros/colaborador/${user._id || user.id}`,
"GET",
null,
{ Authorization: `Bearer ${token}` }
),
]);
if (adminProjects.error) throw new Error(adminProjects.error);
if (collaboratorProjects.error)
throw new Error(collaboratorProjects.error);
const allProjects = [
...adminProjects.data,
...collaboratorProjects.data,
];
const projectPromises = allProjects.map((project) =>
newFetch(
import.meta.env.VITE_BASE_API + `tableros/${project._id}/actual`,
"GET",
null,
{ Authorization: `Bearer ${token}` }
)
);
const projectStatuses = await Promise.all(projectPromises);
const newActualProjects = [];
const newCompletedProjects = [];
allProjects.forEach((project, index) => {
if (projectStatuses[index].data.actual) {
newActualProjects.push(project);
} else {
newCompletedProjects.push(project);
}
});
setActualProjects(newActualProjects);
setCompletedProjects(newCompletedProjects);
}
} catch (error) {
console.error("Error al cargar los proyectos", error);
} finally {
setIsLoading(false);
}
}
//Hook de efecto para obtener los datos del usuario y los proyectos al montar el componente
useEffect(() => {
async function fetchData() {
setIsLoading(true);
await getUser();
await getProjects();
}
if (user && token) {
fetchData();
}
}, [token, fetch]);
// Maneja el cambio de la imagen de perfil
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
const allowedTypes = [
"image/jpeg",
"image/png",
"image/jpg",
"image/webp",
];
if (!allowedTypes.includes(file.type)) {
console.log(
"Por favor, sube una imagen válida (JPEG, PNG, JPG o WEBP)."
);
return;
}
// Guardamos el archivo y el nombre del archivo directamente
setSelectedImage({
src: URL.createObjectURL(file), // Vista previa usando un objeto URL temporal
file, // El archivo original para enviarlo con FormData
name: file.name, // Nombre del archivo
});
}
};
// Maneja el envío del formulario de edición de perfil
const handleEditProfileSubmit = async (values) => {
try {
if (user) {
const formData = new FormData();
formData.append("nombre", values.name);
formData.append("email", values.email);
console.log(selectedImage);
if (selectedImage.file) {
formData.append("fotoPerfil", selectedImage.file);
}
const response = await newFetch(
`${import.meta.env.VITE_BASE_API}usuarios/${user._id || user.id}`,
"PUT",
formData,
{
Authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
}
);
if (response.error) {
throw new Error("Error al actualizar el perfil");
}
const updatedUser = await response.data;
setUser(updatedUser);
console.log("Perfil actualizado exitosamente:", updatedUser);
}
} catch (error) {
console.error("Error al actualizar el perfil:", error);
} finally {
setmodalEditProfileOpen(false);
}
};
if (isLoading) {
return <p>Cargando...</p>;
}
return (
<div className="contenedor__usuario">
<nav className="menu__usuario">
<ul className="usuario__lista">
<li className="lista__opcion">
<a href="#" onClick={() => setmodalNewProjectOpen(true)}>
<AddIcon
sx={{
"&:hover": {
color: "#FFDE81",
cursor: "pointer",
fontSize: 28,
},
}}
/>
CREAR NUEVO PROYECTO
</a>
</li>
<li className="lista__opcion">
<a href="#" onClick={() => setmodalEditProfileOpen(true)}>
<SettingsIcon
sx={{
"&:hover": {
color: "#FFDE81",
cursor: "pointer",
fontSize: 28,
},
}}
/>
CONFIGURACIÓN USUARIO
</a>
</li>
</ul>
</nav>
<section className="info__usuario">
<div className="user__container">
{/* Foto de perfil del usuario */}
{user && user.fotoPerfil ? (
<img className="images__user" src={user.fotoPerfil} alt="" />
) : (
<img
className="images__user"
src="https://cdn-icons-png.flaticon.com/512/6326/6326055.png"
alt=""
/>
)}
{/* Logo abejita */}
<img
className="images__logo"
src={isDarkMode ? beeDark : bee}
alt="Logo de WorkHive"
/>
</div>
{/* Nombre completo del usuario */}
<h1 className="usuario__nombre">{user && user.nombre}</h1>
</section>
<section className="contenedor__proyectos">
<Board
name="PROYECTOS ACTUALES"
type="inprogress"
panels={actualProjects}
/>
<Board
name="PROYECTOS FINALIZADOS"
type="done"
panels={completedProjects}
/>
</section>
{/* Modal para crear un nuevo proyecto */}
<FormModal
isOpen={modalNewProjectOpen}
onClose={() => setmodalNewProjectOpen(false)}
initialValues={{
name: "",
dateIni: "",
dateEnd: "",
description: "",
}}
validationSchema={validationSchemaNewProject}
onSubmit={async (values) => {
try {
const body = {
nombre: values.name,
descripcion: values.description,
fechaInicio: values.dateIni || null,
fechaFin: values.dateEnd || null,
administrador: user._id || user.id,
colaboradores: [],
};
const response = await newFetch(
import.meta.env.VITE_BASE_API + "tableros",
"POST",
JSON.stringify(body),
{
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
}
);
const data = await response.data;
if (response.error) {
throw new Error(data.message || "Error al crear el tablero");
}
console.log("Tablero creado exitosamente:", data);
getProjects();
} catch (error) {
console.error("Error al crear el proyecto:", error);
} finally {
setmodalNewProjectOpen(false);
}
}}
title="Añadir proyecto"
>
{({ values, handleChange, handleBlur, errors, touched }) => (
<>
<label htmlFor="name" className="formulario__label">
Nombre proyecto
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
className="formulario__input"
/>
{errors.name && touched.name && (
<p className="formulario__error">* {errors.name}</p>
)}
</label>
<label htmlFor="dateIni" className="formulario__label">
Fecha inicio
<input
type="date"
name="dateIni"
value={values.dateIni}
onChange={handleChange}
onBlur={handleBlur}
className="formulario__input"
/>
{errors.dateIni && touched.dateIni && (
<p className="formulario__error">* {errors.dateIni}</p>
)}
</label>
<label htmlFor="dateEnd" className="formulario__label">
Fecha fin
<input
type="date"
name="dateEnd"
value={values.dateEnd}
onChange={handleChange}
onBlur={handleBlur}
className="formulario__input"
/>
{errors.dateEnd && touched.dateEnd && (
<p className="formulario__error">* {errors.dateEnd}</p>
)}
</label>
<label htmlFor="description" className="formulario__label">
Descripción
<textarea
name="description"
value={values.description}
onChange={handleChange}
onBlur={handleBlur}
className="formulario__input"
/>
{errors.description && touched.description && (
<p className="formulario__error">* {errors.description}</p>
)}
</label>
</>
)}
</FormModal>
<FormModal
isOpen={modalEditProfileOpen}
onClose={() => setmodalEditProfileOpen(false)}
initialValues={{
name: user?.nombre || "",
email: user?.email || "",
}}
validationSchema={validationSchemaEditProfile}
onSubmit={handleEditProfileSubmit}
title="Editar perfil"
>
{({ values, handleChange, handleBlur, errors, touched }) => (
<>
<div className="formulario-perfil">
<div className="formulario-perfil__foto">
<img
src={selectedImage.src || user.fotoPerfil || fotoCambiar}
alt="Foto de perfil"
className="foto__imagen-redonda"
/>
<input
type="file"
id="fileInput"
className="foto__input-fichero"
onChange={handleImageChange}
/>
<label htmlFor="fileInput" className="foto__boton">
<AddPhotoAlternateIcon sx={{ color: brown[400] }} />
</label>
</div>
</div>
<label htmlFor="name" className="formulario__label">
Nombre
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
className="formulario__input"
/>
{errors.name && touched.name && (
<p className="formulario__error">* {errors.name}</p>
)}
</label>
<label htmlFor="email" className="formulario__label">
Email
<input
type="text"
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className="formulario__input"
/>
{errors.email && touched.email && (
<p className="formulario__error">* {errors.email}</p>
)}
</label>
</>
)}
</FormModal>
</div>
);
};
export default UserProfile;