From b155d650370edd1ecf93201172dd028050fbde76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 13:27:30 -0600 Subject: [PATCH 01/21] Se agrega una nueva prop a AdminPanelNavBar --- web/src/pages/home/super_admin_page/super_admin_home_page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/home/super_admin_page/super_admin_home_page.tsx b/web/src/pages/home/super_admin_page/super_admin_home_page.tsx index 76717880..1cfdbf55 100644 --- a/web/src/pages/home/super_admin_page/super_admin_home_page.tsx +++ b/web/src/pages/home/super_admin_page/super_admin_home_page.tsx @@ -73,7 +73,7 @@ export const SuperAdminHomePage = () => {
- +
{(() => { switch (selectedPanel) { -- GitLab From 9af1ebee271ab7e4d0c8b20fa1fde1d74435c4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:06:58 -0600 Subject: [PATCH 02/21] =?UTF-8?q?Se=20crea=20la=20l=C3=B3gica=20para=20com?= =?UTF-8?q?unicarse=20con=20la=20API=20y=20cambiar=20la=20contrase=C3=B1a?= =?UTF-8?q?=20de=20un=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasources/prod/admin_datasource.ts | 19 +++++++++++++++++-- .../repositories/prod/admin_repository.ts | 4 ++++ .../datasources/admin_datasource.ts | 1 + .../repositories/admin_repository.ts | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/web/src/data/datasources/prod/admin_datasource.ts b/web/src/data/datasources/prod/admin_datasource.ts index 26ed67fc..36e912ee 100644 --- a/web/src/data/datasources/prod/admin_datasource.ts +++ b/web/src/data/datasources/prod/admin_datasource.ts @@ -4,11 +4,12 @@ import { Admin, AdminFormValues } from "../../../infraestructure/entities/admin_ import { APIUrl } from "../../../constants/api_url"; import { AdminModel } from "../../models/prod/AdminModel"; import { UserRole } from "../../../constants/roles"; +import { API_ROUTE_ADMIN_CHANGE_PASSWORD, API_ROUTE_ADMIN_SIGNUP, API_ROUTE_ADMIN_WHOAMI } from "../../../constants/api_routes"; export class AdminDatasourceProd implements AdminDatasourceInf{ async registerAdmin(form: AdminFormValues): Promise { await axios.post( - APIUrl + "/admin/signup", + APIUrl + API_ROUTE_ADMIN_SIGNUP, { email: form.email, idTowm: form.townAdmin, @@ -20,7 +21,7 @@ export class AdminDatasourceProd implements AdminDatasourceInf{ } async getAdminInfo(token: string): Promise { - const {data} = await axios.get(APIUrl+'/admin/whoami',{ + const {data} = await axios.get(APIUrl + API_ROUTE_ADMIN_WHOAMI,{ headers: { 'Authorization': `Bearer ${token}` } @@ -34,4 +35,18 @@ export class AdminDatasourceProd implements AdminDatasourceInf{ } return admin; } + + async changePassword(token: string, prevPassword: string, newPassword: string): Promise { + await axios.post(APIUrl + API_ROUTE_ADMIN_CHANGE_PASSWORD, + { + prevPassword: prevPassword, + newPassword: newPassword + }, + { + headers: { + 'Authorization': `Bearer ${token}` + } + } + ); + } } \ No newline at end of file diff --git a/web/src/data/repositories/prod/admin_repository.ts b/web/src/data/repositories/prod/admin_repository.ts index 3f53c8c6..81862bff 100644 --- a/web/src/data/repositories/prod/admin_repository.ts +++ b/web/src/data/repositories/prod/admin_repository.ts @@ -14,4 +14,8 @@ export class AdminRepositoryProd implements AdminRepositoryInf{ async getAdminInfo(token: string): Promise { return this.datasource.getAdminInfo(token); } + + async changePassword(token: string, prevPassword: string, newPassword: string): Promise { + return this.datasource.changePassword(token, prevPassword, newPassword); + } } \ No newline at end of file diff --git a/web/src/infraestructure/datasources/admin_datasource.ts b/web/src/infraestructure/datasources/admin_datasource.ts index f167677b..c8d5eea7 100644 --- a/web/src/infraestructure/datasources/admin_datasource.ts +++ b/web/src/infraestructure/datasources/admin_datasource.ts @@ -3,4 +3,5 @@ import { Admin, AdminFormValues } from "../entities/admin_form_values"; export interface AdminDatasourceInf{ registerAdmin(form: AdminFormValues): Promise; getAdminInfo(token: string): Promise; + changePassword(token : string, prevPassword: string, newPassword: string): Promise; } \ No newline at end of file diff --git a/web/src/infraestructure/repositories/admin_repository.ts b/web/src/infraestructure/repositories/admin_repository.ts index 311bdfae..0fdd0615 100644 --- a/web/src/infraestructure/repositories/admin_repository.ts +++ b/web/src/infraestructure/repositories/admin_repository.ts @@ -3,4 +3,5 @@ import { Admin, AdminFormValues } from "../entities/admin_form_values"; export interface AdminRepositoryInf{ registerAdmin(form: AdminFormValues): Promise; getAdminInfo(token: string): Promise; + changePassword(token : string, prevPassword: string, newPassword: string): Promise; } \ No newline at end of file -- GitLab From bb945e682b04f2ceea1007a0c3bebd6b0cdbc7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:09:52 -0600 Subject: [PATCH 03/21] Se cambia la forma en que se abre la nueva ventana --- .../admin_panel_place_list.tsx | 6 +++--- .../admin_panel_place_register.tsx | 8 ++++---- .../admin_panel_place_screen.tsx | 14 ++++++++++---- .../components/admin_town_info/admin_town_info.tsx | 14 ++++++++++---- web/src/hooks/usePlace.tsx | 4 ++-- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx b/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx index 7ded0c77..94aa6d31 100644 --- a/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx +++ b/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx @@ -10,12 +10,12 @@ import { Dispatch, SetStateAction, useEffect } from 'react'; interface props{ idTown: number; isWindowActive: boolean; - setIsWindowActive: Dispatch>; + setWindowVisibility: (visibility: boolean) => void; setActualPlace: Dispatch>; setIsRegisterPane: Dispatch>; } -export const AdminPanelPlaceList = ({idTown, isWindowActive, setIsWindowActive, setActualPlace, setIsRegisterPane}: props) => { +export const AdminPanelPlaceList = ({idTown, isWindowActive, setWindowVisibility, setActualPlace, setIsRegisterPane}: props) => { const { placeList, pending, @@ -25,7 +25,7 @@ export const AdminPanelPlaceList = ({idTown, isWindowActive, setIsWindowActive, const handleEditSelectedCategory = (place: Place) => { setIsRegisterPane(false); setActualPlace(place); - setIsWindowActive(true); + setWindowVisibility(true); } useEffect(() => { diff --git a/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx b/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx index fff742bc..fe958171 100644 --- a/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx +++ b/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx @@ -11,7 +11,7 @@ import { AvailableDays, availableDaysList, EmptyPlace, Place } from "../../../in import { Category } from "../../../infraestructure/entities/category"; interface props { - setIsWindowActive: Dispatch>; + setWindowVisibility: (visibility: boolean) => void; categoriesList: Category[]; idTown: number; forceRenderList: () => void; @@ -19,7 +19,7 @@ interface props { form?: Place; } -export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, categoriesList, forceRenderList, isRegister, form}: props) => { +export const AdminPanelPlaceRegister = ({setWindowVisibility, idTown, categoriesList, forceRenderList, isRegister, form}: props) => { const { register, handleSubmit, @@ -39,7 +39,7 @@ export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, categoriesLi onSubmitRegister, onSubmitUpdate, clearErrors - } = usePlace(forceRenderList, setIsWindowActive); + } = usePlace(forceRenderList, setWindowVisibility); const [clickedCategories, setClickedCategories] = useState(new Array(categoriesList.length).fill(false)); const [isLoading, setIsLoading] = useState(false); const [actualPlace, setActualPlace] = useState(EmptyPlace); @@ -112,7 +112,7 @@ export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, categoriesLi
Registra el lugar setIsWindowActive(false)}/> + onClick={() => setWindowVisibility(false)}/>
{isLoading diff --git a/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx b/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx index d693e3d2..1fd72bb9 100644 --- a/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx +++ b/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx @@ -16,6 +16,7 @@ export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive, town}: const [renderCount, setRenderCount] = useState(0); const [isRegisterPane, setIsRegisterPane] = useState(true); const [actualPlace, setActualPlace] = useState(); + const [isPlaceRegisterWindowActive, setIsPlaceRegisterWindowActive] = useState(false); const { categoriesList, updateCategoriesList, @@ -26,6 +27,11 @@ export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive, town}: setIsWindowActive(false); } + const setWindowVisibility = (visibility: boolean) => { + setIsPlaceRegisterWindowActive(visibility); + setIsWindowActive(visibility); + } + useEffect(()=>{ updateCategoriesList(); },[]); @@ -39,7 +45,7 @@ export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive, town}: disabled={isWindowActive || !town} onClick={() => { setIsRegisterPane(true); - setIsWindowActive(true); + setWindowVisibility(true); }} > Registrar lugar @@ -47,8 +53,8 @@ export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive, town}:
{ - isWindowActive && }
diff --git a/web/src/components/admin_town_info/admin_town_info.tsx b/web/src/components/admin_town_info/admin_town_info.tsx index ed28ca88..22b72931 100644 --- a/web/src/components/admin_town_info/admin_town_info.tsx +++ b/web/src/components/admin_town_info/admin_town_info.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction} from "react"; +import { Dispatch, SetStateAction, useState} from "react"; import { Town } from "../../infraestructure/entities/town"; import "./assets/css/styles.css"; import { faEdit, faLanguage } from "@fortawesome/free-solid-svg-icons"; @@ -24,6 +24,12 @@ export const AdminTownInfo = ({updateTown, isWindowActive, setIsWindowActive, to forceRenderList, statesList } = useAdminTownInfo(updateTown); + const [isTownRegisterWindowActive, setIsTownRegisterWindowActive] = useState(false); + + const setWindowVisibility = (visibility: boolean) => { + setIsTownRegisterWindowActive(visibility); + setIsWindowActive(visibility); + } if(!town){ return ( @@ -35,10 +41,10 @@ export const AdminTownInfo = ({updateTown, isWindowActive, setIsWindowActive, to return (
- {isWindowActive && + {isTownRegisterWindowActive && { if(!isWindowActive){ - setIsWindowActive(true); + setWindowVisibility(true); } } } diff --git a/web/src/hooks/usePlace.tsx b/web/src/hooks/usePlace.tsx index ee51baac..69a8e095 100644 --- a/web/src/hooks/usePlace.tsx +++ b/web/src/hooks/usePlace.tsx @@ -1,7 +1,7 @@ import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form" import { Place, AvailableDays } from "../infraestructure/entities/place"; import { toast } from "react-toastify"; -import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import axios, { AxiosError } from "axios"; import { languaguesList } from "../constants/languages"; import { MIN_NUMBER_PLACE_IMAGES } from "../constants/images_nuber"; @@ -128,7 +128,7 @@ const resolver: Resolver = async (data) => { }; export const usePlace = (forceRenderList?: () => void, -setIsWindowActive?: Dispatch>) => { +setIsWindowActive?: (visibility: boolean) => void) => { const { register, handleSubmit, -- GitLab From 58298237c67caf78aaf6d6cae09d4e4a78b2570e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:12:25 -0600 Subject: [PATCH 04/21] =?UTF-8?q?Se=20crea=20una=20interface=20para=20los?= =?UTF-8?q?=20valores=20del=20formulario=20para=20cambiar=20contrase=C3=B1?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/infraestructure/entities/admin_form_values.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/src/infraestructure/entities/admin_form_values.ts b/web/src/infraestructure/entities/admin_form_values.ts index 256319ee..70720c33 100644 --- a/web/src/infraestructure/entities/admin_form_values.ts +++ b/web/src/infraestructure/entities/admin_form_values.ts @@ -15,4 +15,11 @@ export interface Admin { lastName: string; role: UserRole; idTown?: number; +} + +export interface AdminPasswordValues { + token: string; + prevPassword: string; + newPassword: string; + newPasswordConfirm: string; } \ No newline at end of file -- GitLab From bb1658a6ca84af438165d427e3ebe821b0d8a506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:12:55 -0600 Subject: [PATCH 05/21] Se elimina un campo innecesario --- web/src/infraestructure/entities/admin_form_values.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/infraestructure/entities/admin_form_values.ts b/web/src/infraestructure/entities/admin_form_values.ts index 70720c33..46117082 100644 --- a/web/src/infraestructure/entities/admin_form_values.ts +++ b/web/src/infraestructure/entities/admin_form_values.ts @@ -18,7 +18,6 @@ export interface Admin { } export interface AdminPasswordValues { - token: string; prevPassword: string; newPassword: string; newPasswordConfirm: string; -- GitLab From b1922251977163a454802494b01ce57707ae9ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:13:17 -0600 Subject: [PATCH 06/21] Se agrega una prop faltante en un componente --- web/src/pages/home/admin_page/admin_home_page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/home/admin_page/admin_home_page.tsx b/web/src/pages/home/admin_page/admin_home_page.tsx index 0180f304..e0626b77 100644 --- a/web/src/pages/home/admin_page/admin_home_page.tsx +++ b/web/src/pages/home/admin_page/admin_home_page.tsx @@ -72,7 +72,7 @@ export const AdminHomePage = () => {
- +
{(() => { switch (selectedPanel) { -- GitLab From 59dd0547ca3985e2aa146c4445d577cf19772ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:22:25 -0600 Subject: [PATCH 07/21] Se crea un archivo de constantes que almacenan las rutas en la API --- web/src/constants/api_routes.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 web/src/constants/api_routes.ts diff --git a/web/src/constants/api_routes.ts b/web/src/constants/api_routes.ts new file mode 100644 index 00000000..c08a130b --- /dev/null +++ b/web/src/constants/api_routes.ts @@ -0,0 +1,6 @@ +const API_ROUTE_ADMIN = '/admin'; +export const API_ROUTE_ADMIN_SIGNUP = API_ROUTE_ADMIN+ '/signup'; +export const API_ROUTE_ADMIN_SIGNIN = API_ROUTE_ADMIN+ '/signin'; +export const API_ROUTE_ADMIN_CHANGE_PASSWORD = API_ROUTE_ADMIN+ '/change-password'; +export const API_ROUTE_ADMIN_WHOAMI = API_ROUTE_ADMIN+ '/whoami'; +const API_ROUTE_STATE = '/state'; \ No newline at end of file -- GitLab From b763bf99612519013fcb9769b5d34a3bfa1e9a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:22:58 -0600 Subject: [PATCH 08/21] =?UTF-8?q?Se=20agrega=20la=20ventana=20para=20cambi?= =?UTF-8?q?ar=20la=20contrase=C3=B1a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_panel_navbar/admin_navbar.tsx | 121 +++++++++++- .../assets/styles/style.css | 184 +++++++++++++----- web/src/hooks/useAdminChangePassword.tsx | 88 +++++++++ 3 files changed, 339 insertions(+), 54 deletions(-) create mode 100644 web/src/hooks/useAdminChangePassword.tsx diff --git a/web/src/components/admin_panel_navbar/admin_navbar.tsx b/web/src/components/admin_panel_navbar/admin_navbar.tsx index 0dad5f8d..81e61e3d 100644 --- a/web/src/components/admin_panel_navbar/admin_navbar.tsx +++ b/web/src/components/admin_panel_navbar/admin_navbar.tsx @@ -1,16 +1,42 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faSignOut, faUser } from "@fortawesome/free-solid-svg-icons"; +import { faEye, faEyeSlash, faSignOut, faUser, faWindowClose } from "@fortawesome/free-solid-svg-icons"; import { Link } from "react-router-dom"; import './assets/styles/style.css'; import { UserRole } from "../../constants/roles"; import { useUserData } from "../../hooks/useUserData"; +import { Dispatch, SetStateAction, useState } from "react"; +import { useAdminChangePassword } from "../../hooks/useAdminChangePassword"; +import { usePasswoordVisibility } from "../../hooks/usePasswordVisibility"; interface props{ - windowActive: boolean; + isWindowActive: boolean; + setIsWindowActive: Dispatch>; } -export const AdminPanelNavBar = ({windowActive}:props) => { +export const AdminPanelNavBar = ({isWindowActive, setIsWindowActive}:props) => { const {user, handleLogout, setToggle, toggle, userData} = useUserData(); + const [changePasswordWindowActive, setChangePasswordWindowActive] = useState(false); + const setChangePasswordWindowVisibility = (visibility: boolean) => { + setIsWindowActive(visibility); + setChangePasswordWindowActive(visibility); + } + const {register, handleSubmit, errors, onSubmit} = useAdminChangePassword(setChangePasswordWindowVisibility); + const { + values: valuesPrevPassword, + handleClickShowPassword: handleClickShowPrevPassword, + handleMouseDownPassword: handleMouseDownPrevPassword + } = usePasswoordVisibility(); + const { + values: valuesNewPassword, + handleClickShowPassword: handleClickShowNewPassword, + handleMouseDownPassword: handleMouseDownNewPassword + } = usePasswoordVisibility(); + const { + values: valuesNewPasswordConfirm, + handleClickShowPassword: handleClickShowNewPasswordConfirm, + handleMouseDownPassword: handleMouseDownNewPasswordConfirm + } = usePasswoordVisibility(); + if(!user ){ return null; @@ -25,14 +51,14 @@ export const AdminPanelNavBar = ({windowActive}:props) => {
{ - windowActive + isWindowActive ? setToggle(false) : setToggle(!toggle) }} style={ - windowActive + isWindowActive ? {cursor: "auto"} : @@ -48,9 +74,9 @@ export const AdminPanelNavBar = ({windowActive}:props) => {

- + {setChangePasswordWindowVisibility(true); setToggle(false);}} className="sub-menu-link"> -

Editar cuenta

+

Cambiar contraseña

@@ -61,6 +87,87 @@ export const AdminPanelNavBar = ({windowActive}:props) => {
}
+ { + changePasswordWindowActive && +
+
+ Cambio de contraseña + setChangePasswordWindowVisibility(false)}/> +
+ +
+
+
+ +
+ + +
+

{errors.prevPassword?.message}

+
+ +
+ +
+ + +
+

{errors.newPassword?.message}

+
+ +
+ +
+ + +
+

{errors.newPasswordConfirm?.message}

+
+ +
+ +
+
+
+
+ }
); } \ No newline at end of file diff --git a/web/src/components/admin_panel_navbar/assets/styles/style.css b/web/src/components/admin_panel_navbar/assets/styles/style.css index 6665a077..e8d2af3f 100644 --- a/web/src/components/admin_panel_navbar/assets/styles/style.css +++ b/web/src/components/admin_panel_navbar/assets/styles/style.css @@ -1,91 +1,181 @@ :root { - --shadow: 0px 2px 8px 0px gray; + --shadow: 0px 2px 8px 0px gray; } .navbar{ - height: 6vh; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: var(--shadow); + height: 6vh; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: var(--shadow); } .profile{ - position: absolute; - right: 20px; - user-select: none; + position: absolute; + right: 20px; + user-select: none; } .user-pic{ - width: 30px; - border-radius: 50%; - background: white; + width: 30px; + border-radius: 50%; + background: white; } .profile-sub-menu-wrap{ - position: absolute; - top: 100%; - right: 10%; - min-width: 300px; - max-width: 300px; - overflow: hidden; - z-index: 1000; - transition: max-height 0.5s; + position: absolute; + top: 100%; + right: 10%; + min-width: 300px; + max-width: 300px; + overflow: hidden; + z-index: 1000; + transition: max-height 0.5s; } .sub-menu{ - background: white; - padding: 10px; - margin: 10px; + background: white; + padding: 10px; + margin: 10px; } .user-info{ - display: flex; - align-items: center; + display: flex; + align-items: center; } .user-info img{ - width: 60px; - margin-right: 15px; - border-radius: 50%; + width: 60px; + margin-right: 15px; + border-radius: 50%; } .user-info h3{ - font-weight: 500; + font-weight: 500; } .sub-menu hr{ - border: 0; - height: 1px; - width: 100%; - background: #ccc ; - margin: 15px 0 10px; + border: 0; + height: 1px; + width: 100%; + background: #ccc ; + margin: 15px 0 10px; } .sub-menu-link{ - display: flex; - align-items: center; - text-decoration: none; - color: #525252; + display: flex; + align-items: center; + text-decoration: none; + color: #525252; } .sub-menu-link:hover .sub-menu-link-icon{ - transform: scale(1.1); + transform: scale(1.1); } .sub-menu-link:hover p{ - font-weight: 600; + font-weight: 600; } .sub-menu-link p{ - user-select: none; + user-select: none; } .sub-menu-link .sub-menu-link-icon{ - width: 20px; - background: #E5E5E5; - border-radius: 50%; - padding: 8px; - margin-right: 15px; + width: 20px; + background: #E5E5E5; + border-radius: 50%; + padding: 8px; + margin-right: 15px; +} + +.change-password-window { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + width: 30vw; + height: 40vh; + background: green; + display: flex; + flex-direction: column; + z-index: 5; +} + +.change-password-window .header { + display: flex; + width: 100%; + align-items: center; + justify-content: center; + padding: 5px; +} + +.change-password-window .header .close_btn{ + display: inline-block; + cursor: pointer; + height: 5%; + position: absolute; + right: 5px; +} + +.change-password-window .content { + background: white; + width: 100%; + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.change-password-window .content form{ + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.change-password-window .content form .input_cnt{ + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 5px 0; +} + +.change-password-window .content form .input_cnt input{ + width: 80%; + padding: 5px 20px; + border: 1px solid lightgray; + border-radius: 5px; +} + +.error{ + color: red; + font-size: 12px; + padding: 0; + margin: 0; +} + +.change-password-window .content form .input_cnt .submit_btn{ + width: 30%; +} + +.change-password-window .content form .input_cnt .password_cnt{ + position: relative; + width: 80%; +} + +.change-password-window .content form .input_cnt .password_cnt input{ + width: 100%; + padding-left: 20px; + padding-right: 30px; +} + +.change-password-window .content form .input_cnt .password_cnt .visibility-button{ + position: absolute; + top: 50%; + right: 5px; + transform: translateY(-50%); + cursor: pointer; } \ No newline at end of file diff --git a/web/src/hooks/useAdminChangePassword.tsx b/web/src/hooks/useAdminChangePassword.tsx new file mode 100644 index 00000000..4fab188f --- /dev/null +++ b/web/src/hooks/useAdminChangePassword.tsx @@ -0,0 +1,88 @@ +import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form"; +import { AdminDatasourceProd } from "../data/datasources/prod/admin_datasource"; +import { AdminRepositoryProd } from "../data/repositories/prod/admin_repository"; +import { AdminPasswordValues } from "../infraestructure/entities/admin_form_values"; +import { useState } from "react"; +import axios, { AxiosError } from "axios"; +import { toast } from "react-toastify"; + +const adminDatasource = new AdminDatasourceProd(); +const adminRepository = new AdminRepositoryProd(adminDatasource); + +const resolver: Resolver = async (data) => { + const errors: FieldErrors = {}; + + if (!data.prevPassword) { + errors.prevPassword = { + type: "required", + message: "Se requiere la contraseña actual" + }; + } + + if (!data.newPassword) { + errors.newPassword = { + type: "required", + message: "Se requiere una nueva contraseña" + }; + }else if(data.newPasswordConfirm!==data.newPassword){ + errors.newPasswordConfirm = { + type: "validate", + message: "Las contraseñas deben coincidir" + }; + } + + if (!data.newPasswordConfirm) { + errors.newPasswordConfirm = { + type: "required", + message: "Se requiere confirmar la nueva contraseña" + }; + } + + return { + values: Object.keys(errors).length > 0 ? {} : data, + errors: errors, + }; +}; + +export const useAdminChangePassword = (setChangePasswordWindowVisibility: (visibility: boolean) => void) => { + const { + register, + handleSubmit, + formState: {errors}, + setError + } = useForm({resolver}); + const [errorMessage, setErrorMessage] = useState(''); + + const onSubmit: SubmitHandler = (data: AdminPasswordValues) => { + const fetch = async () => { + try{ + const token = localStorage.getItem('token') || ''; + await adminRepository.changePassword(token, data.prevPassword, data.newPassword); + setChangePasswordWindowVisibility(false); + }catch(error: any){ + if(axios.isAxiosError(error)){ + error as AxiosError; + switch(error.code){ + case(axios.AxiosError.ERR_BAD_REQUEST): + setErrorMessage("Acceso no autorizado"); + setError('prevPassword', {type: 'validate', message: 'Contraseña incorrecta'}) + break; + case(axios.AxiosError.ERR_NETWORK): + setErrorMessage("Conexión con el servidor fallida"); + break; + } + } + throw new Error(); + } + } + toast.promise( + fetch(),{ + pending: "Actualizando contraseña...", + success: "La contraseña se ha actualizado", + error: errorMessage + } + ) + } + + return {register, handleSubmit, errors, onSubmit}; +} \ No newline at end of file -- GitLab From f3d5ab70589ac1cfc4673611d1a039b8efb4465e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:50:57 -0600 Subject: [PATCH 09/21] Se corrige error que no se muestra el nombre de la categoria en ingles --- web/src/data/datasources/prod/category_datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/data/datasources/prod/category_datasource.ts b/web/src/data/datasources/prod/category_datasource.ts index 1204701d..41271b55 100644 --- a/web/src/data/datasources/prod/category_datasource.ts +++ b/web/src/data/datasources/prod/category_datasource.ts @@ -23,7 +23,7 @@ export class CategoryDatasourceProd implements CategoryDatasourceInf{ }); const {data: dataEN} = await axios.get(APIUrl+'/category/', { params: { - lang: 'ES' + lang: 'EN' } }); const categories : Category[] = []; -- GitLab From 523c9b525785e4b92073560fb27c8bab9d69bfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Mon, 19 Aug 2024 18:51:59 -0600 Subject: [PATCH 10/21] Se cambia la forma de actualizar la lista de categorias despues que se agrega o se elimina --- web/src/hooks/useCategory.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web/src/hooks/useCategory.tsx b/web/src/hooks/useCategory.tsx index 16443f41..183e2f31 100644 --- a/web/src/hooks/useCategory.tsx +++ b/web/src/hooks/useCategory.tsx @@ -53,6 +53,7 @@ export const useCategory = (forceRenderList?: () => void, handleClickToClose?: ( try{ await categoryRepository.registerCategory(data).then(() =>{ if(forceRenderList && handleClickToClose){ + updateCategoriesList(); forceRenderList(); handleClickToClose(); } @@ -106,11 +107,8 @@ export const useCategory = (forceRenderList?: () => void, handleClickToClose?: ( },[]); const deleteCategory = async (category: Category) => { - setPending(true); await categoryRepository.deleteCategory(category); - const categories = await categoryRepository.getCategories(); - setCategoriesList(categories); - setPending(false); + await updateCategoriesList(); } return { -- GitLab From fe2b9d6b7b95ec3ac17595d5b7fd7e8076b67e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:24:43 -0600 Subject: [PATCH 11/21] Se crea la entidad que guarda los valores de un punto de interes --- web/src/infraestructure/entities/poi.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 web/src/infraestructure/entities/poi.ts diff --git a/web/src/infraestructure/entities/poi.ts b/web/src/infraestructure/entities/poi.ts new file mode 100644 index 00000000..cdbfcb7e --- /dev/null +++ b/web/src/infraestructure/entities/poi.ts @@ -0,0 +1,21 @@ +export interface PointOfInterest { + idPoint?: number; + idPlace: number; + name: string; + image: File | string; + contentEN: string; + contentES: string; + directionsEN: string; + directionsES: string; +} + +export const EmptyPointOfInterest: PointOfInterest = { + idPoint: 0, + idPlace: 0, + name: '', + image: '', + contentEN: '', + contentES: '', + directionsEN: '', + directionsES: '' +} \ No newline at end of file -- GitLab From 66a8d620b4832699be40ef027f89bc57de027e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:25:17 -0600 Subject: [PATCH 12/21] Se agrega las rutas de la API para los puntos de interes y los lugares --- web/src/constants/api_routes.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/constants/api_routes.ts b/web/src/constants/api_routes.ts index c08a130b..79d533d5 100644 --- a/web/src/constants/api_routes.ts +++ b/web/src/constants/api_routes.ts @@ -3,4 +3,6 @@ export const API_ROUTE_ADMIN_SIGNUP = API_ROUTE_ADMIN+ '/signup'; export const API_ROUTE_ADMIN_SIGNIN = API_ROUTE_ADMIN+ '/signin'; export const API_ROUTE_ADMIN_CHANGE_PASSWORD = API_ROUTE_ADMIN+ '/change-password'; export const API_ROUTE_ADMIN_WHOAMI = API_ROUTE_ADMIN+ '/whoami'; -const API_ROUTE_STATE = '/state'; \ No newline at end of file +const API_ROUTE_STATE = '/state'; +export const API_ROUTE_POINT = '/point'; +export const API_ROUTE_PLACE = '/place'; \ No newline at end of file -- GitLab From 4f89ad17a0e4f5a5b74c6b58e5d46822bdb8ef06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:25:51 -0600 Subject: [PATCH 13/21] Se renombra un enumerado para el panel seleccionado de administrador --- web/src/constants/selected_panel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/constants/selected_panel.ts b/web/src/constants/selected_panel.ts index 64626a33..0c2ef85c 100644 --- a/web/src/constants/selected_panel.ts +++ b/web/src/constants/selected_panel.ts @@ -1,7 +1,7 @@ export enum AdminSelectedPanel { TOWN_INFO = "town_info", PLACES = "places", - ACTIVITIES = "activities" + POINT_OF_INTEREST = "point_of_interest" } export enum SuperAdminSelectedPanel { -- GitLab From 2c913837f47523f330222edad1eb52b2f1320278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:26:50 -0600 Subject: [PATCH 14/21] Se modifica para utilizar un actualizador de estado --- web/src/components/image_dropzone/image_dropzone.tsx | 12 +++++------- .../sa_panel_town_register.tsx | 9 ++++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/web/src/components/image_dropzone/image_dropzone.tsx b/web/src/components/image_dropzone/image_dropzone.tsx index fab94e8f..278a5b71 100644 --- a/web/src/components/image_dropzone/image_dropzone.tsx +++ b/web/src/components/image_dropzone/image_dropzone.tsx @@ -1,18 +1,16 @@ import { ToastContainer, toast } from 'react-toastify'; -import './assets/css/styles.css' import { useDropzone } from "react-dropzone"; -import { Dispatch, SetStateAction, useState } from 'react'; +import './assets/css/styles.css'; +import { Dispatch, SetStateAction } from 'react'; import "react-toastify/dist/ReactToastify.css"; -import { UseFormSetValue } from 'react-hook-form'; -import { Town } from '../../infraestructure/entities/town'; interface props { - setValue : UseFormSetValue; + setImage: Dispatch>; preview : string | ArrayBuffer | null; setPreview: Dispatch>; } -export const ImageDropzone = ({setValue, preview, setPreview}: props) => { +export const ImageDropzone = ({setImage, preview, setPreview}: props) => { const MAX_SIZE = 10485760; const {getRootProps, getInputProps} = useDropzone( { @@ -37,7 +35,7 @@ export const ImageDropzone = ({setValue, preview, setPreview}: props) => { acceptedFiles.forEach((file)=>{ const preview = URL.createObjectURL(file); - setValue('imageURL',file,{shouldValidate: true}); + setImage(file); setPreview(preview); }); } diff --git a/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx b/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx index 8c70412d..f7726b29 100644 --- a/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx +++ b/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx @@ -31,6 +31,7 @@ export const SuperadminPanelTownRegister = ({setWindowActive, statesList, forceR const [spanishDescription, setSpanishDescription] = useState(""); const [englishDescription, setEnglishDescription] = useState(""); const [preview, setPreview] = useState(null); + const [image, setImage] = useState(null); useEffect(()=> { if(!isRegister && form){ @@ -53,6 +54,12 @@ export const SuperadminPanelTownRegister = ({setWindowActive, statesList, forceR } },[]) + useEffect(() => { + if(image){ + setValue('imageURL', image, {shouldValidate: true}); + } + },[image]); + return (
@@ -137,7 +144,7 @@ export const SuperadminPanelTownRegister = ({setWindowActive, statesList, forceR
Fotografía representativa del pueblo
- +

{errors.imageURL?.message}

-- GitLab From 7d3b4b0e3e9b0ce2194f2b7ebec25edf9e0d7a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:27:37 -0600 Subject: [PATCH 15/21] =?UTF-8?q?Se=20crea=20el=20modeo=20para=20obtener?= =?UTF-8?q?=20un=20punto=20de=20interes=20y=20se=20crea=20un=20m=C3=A9todo?= =?UTF-8?q?=20para=20convertirlo=20a=20entidad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/data/models/prod/POIModel.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 web/src/data/models/prod/POIModel.ts diff --git a/web/src/data/models/prod/POIModel.ts b/web/src/data/models/prod/POIModel.ts new file mode 100644 index 00000000..603b8bd4 --- /dev/null +++ b/web/src/data/models/prod/POIModel.ts @@ -0,0 +1,24 @@ +import { PointOfInterest } from "../../../infraestructure/entities/poi"; + +export interface POIModel { + idPoint: number; + idPlace: number; + name: string; + imageName: string; + content: string; + directions: string; +} + +export const POIModelToEntity = (model: POIModel) =>{ + const poi: PointOfInterest = { + idPoint : model.idPoint, + idPlace : model.idPlace, + name: model.name, + image: model.imageName, + contentES: model.content, + contentEN: model.content, + directionsES: model.directions, + directionsEN: model.directions + } + return poi; +} \ No newline at end of file -- GitLab From d5e46ac91294e4c932736e3a42b475a76f1cbf2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:28:26 -0600 Subject: [PATCH 16/21] =?UTF-8?q?Se=20crea=20la=20l=C3=B3gica=20para=20cre?= =?UTF-8?q?ar=20y=20obtener=20puntos=20de=20inter=C3=A9s=20a=20trav=C3=A9s?= =?UTF-8?q?=20de=20llamadas=20a=20la=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasources/prod/poi_datasource.ts | 59 +++++++++++++++++++ .../data/repositories/prod/poi_repository.ts | 21 +++++++ .../datasources/poi_repository.ts | 7 +++ .../repositories/poi_repository.ts | 7 +++ 4 files changed, 94 insertions(+) create mode 100644 web/src/data/datasources/prod/poi_datasource.ts create mode 100644 web/src/data/repositories/prod/poi_repository.ts create mode 100644 web/src/infraestructure/datasources/poi_repository.ts create mode 100644 web/src/infraestructure/repositories/poi_repository.ts diff --git a/web/src/data/datasources/prod/poi_datasource.ts b/web/src/data/datasources/prod/poi_datasource.ts new file mode 100644 index 00000000..bf000199 --- /dev/null +++ b/web/src/data/datasources/prod/poi_datasource.ts @@ -0,0 +1,59 @@ +import axios from "axios"; +import { PoiDatasourceInf } from "../../../infraestructure/datasources/poi_repository"; +import { PointOfInterest } from "../../../infraestructure/entities/poi"; +import { APIUrl } from "../../../constants/api_url"; +import { API_ROUTE_PLACE, API_ROUTE_POINT } from "../../../constants/api_routes"; +import { POIModel, POIModelToEntity } from "../../models/prod/POIModel"; + +export class POIDatasourceProd implements PoiDatasourceInf{ + async registerPoint(form: PointOfInterest): Promise { + const formToSend = new FormData(); + formToSend.append('idPlace', String(form.idPlace)); + formToSend.append('name', form.name); + formToSend.append('image', form.image); + formToSend.append('contentEN', form.contentEN); + formToSend.append('contentES', form.contentES); + formToSend.append('directionsEN', form.directionsEN); + formToSend.append('directionsES', form.directionsES); + + const headers = { + 'Content-Type': 'multipart/form-data' + }; + + await axios.post(APIUrl + API_ROUTE_POINT, formToSend,{headers}); + } + + async getPOIsByPlace(idPlace: number): Promise { + const {data: dataES} = await axios.get(APIUrl + API_ROUTE_PLACE + `/${idPlace}` + API_ROUTE_POINT, { + params: { + lang: 'ES' + } + }); + + const poiList = dataES.map((dataESModel) => { + return POIModelToEntity(dataESModel); + }) + + return poiList; + } + + async getPOIById(idPoint: number): Promise { + const {data: dataES} = await axios.get(APIUrl + API_ROUTE_POINT + `/${idPoint}`, { + params: { + lang: 'ES' + } + }); + + const {data: dataEN} = await axios.get(APIUrl + API_ROUTE_POINT + `/${idPoint}`, { + params: { + lang: 'EN' + } + }); + + const poi: PointOfInterest = POIModelToEntity(dataES); + poi.contentEN = dataEN.content; + poi.directionsEN = dataEN.directions; + + return poi; + } +} \ No newline at end of file diff --git a/web/src/data/repositories/prod/poi_repository.ts b/web/src/data/repositories/prod/poi_repository.ts new file mode 100644 index 00000000..48991f04 --- /dev/null +++ b/web/src/data/repositories/prod/poi_repository.ts @@ -0,0 +1,21 @@ +import { PoiDatasourceInf } from "../../../infraestructure/datasources/poi_repository"; +import { PointOfInterest } from "../../../infraestructure/entities/poi"; +import { PoiRepositoryInf } from "../../../infraestructure/repositories/poi_repository"; + +export class POIRepositoryProd implements PoiRepositoryInf{ + constructor( + private datasouce: PoiDatasourceInf + ){} + + async registerPoint(form: PointOfInterest): Promise { + return this.datasouce.registerPoint(form); + } + + async getPOIById(idPoint: number): Promise { + return this.datasouce.getPOIById(idPoint); + } + + async getPOIsByPlace(idPlace: number): Promise { + return this.datasouce.getPOIsByPlace(idPlace); + } +} \ No newline at end of file diff --git a/web/src/infraestructure/datasources/poi_repository.ts b/web/src/infraestructure/datasources/poi_repository.ts new file mode 100644 index 00000000..20ec7db6 --- /dev/null +++ b/web/src/infraestructure/datasources/poi_repository.ts @@ -0,0 +1,7 @@ +import { PointOfInterest } from "../entities/poi"; + +export interface PoiDatasourceInf { + registerPoint(form: PointOfInterest): Promise; + getPOIsByPlace(idPlace: number): Promise; + getPOIById(idPoint: number): Promise; +} \ No newline at end of file diff --git a/web/src/infraestructure/repositories/poi_repository.ts b/web/src/infraestructure/repositories/poi_repository.ts new file mode 100644 index 00000000..3ef41a00 --- /dev/null +++ b/web/src/infraestructure/repositories/poi_repository.ts @@ -0,0 +1,7 @@ +import { PointOfInterest } from "../entities/poi"; + +export interface PoiRepositoryInf { + registerPoint(form: PointOfInterest): Promise; + getPOIsByPlace(idPlace: number): Promise; + getPOIById(idPoint: number): Promise; +} \ No newline at end of file -- GitLab From a46a5a313a387b59447c18a1a5783102874fad06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:29:00 -0600 Subject: [PATCH 17/21] =?UTF-8?q?Se=20crea=20el=20hook=20que=20guarda=20la?= =?UTF-8?q?=20l=C3=B3gica=20para=20crear,=20validar=20y=20obtener=20puntos?= =?UTF-8?q?=20de=20interes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/hooks/usePointOfInterest.tsx | 179 +++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 web/src/hooks/usePointOfInterest.tsx diff --git a/web/src/hooks/usePointOfInterest.tsx b/web/src/hooks/usePointOfInterest.tsx new file mode 100644 index 00000000..c65d7f46 --- /dev/null +++ b/web/src/hooks/usePointOfInterest.tsx @@ -0,0 +1,179 @@ +import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form"; +import { POIDatasourceProd } from "../data/datasources/prod/poi_datasource"; +import { POIRepositoryProd } from "../data/repositories/prod/poi_repository"; +import { PointOfInterest } from "../infraestructure/entities/poi"; +import { useState } from "react"; +import axios, { AxiosError } from "axios"; +import { toast } from "react-toastify"; +import { languaguesList } from "../constants/languages"; +import { showErrorAxios } from "../utils/Messages"; + +const POIDatasouce = new POIDatasourceProd(); +const POIRepository = new POIRepositoryProd(POIDatasouce); + +const resolver: Resolver = async (data) => { + const errors: FieldErrors = {}; + + if(!data.idPlace){ + errors.idPlace = { + type : "required", + message : "Debe seleccionar el lugar al que pertenece el punto de interés" + } + } + + if(!data.name){ + errors.name = { + type: "required", + message: "El nombre del punto de interés es requerido" + }; + } + + if(!data.contentEN){ + errors.contentEN = { + type: "required", + message: "La descripción del punto de interés en inglés es requerida" + }; + } + + if(!data.contentES){ + errors.contentES = { + type: "required", + message: "La descripción del punto de interés en español es requerida" + }; + } + + if(!data.directionsEN){ + errors.directionsEN = { + type: "required", + message: "Las direcciones del punto de interés en inglés son requeridas" + }; + } + + if(!data.directionsES){ + errors.directionsES = { + type: "required", + message: "Las direcciones del punto de interés en español son requeridas" + }; + } + + + if(!data.image){ + errors.image = { + type: "required", + message: "Debe de haber al menos 1 imagen representativa del lugar" + } + } + + return { + values: Object.keys(errors).length > 0 ? {} : data, + errors: errors + }; +}; + +export const usePointOfInterest = (forceRenderList?: () => void, +setIsWindowActive?: (visibility: boolean) => void) => { + const { + register, + handleSubmit, + setValue, + formState: {errors}, + clearErrors, + resetField, + } = useForm({resolver}); + const [errorMessage, setErrorMessage] = useState(""); + const [languageDescriptionIndexSelected, setLanguageDescriptionIndexSelected] = useState(0); + const [languageDirectionsIndexSelected, setLanguageDirectionsIndexSelected] = useState(0); + const [descriptions, setDescriptions] = useState(new Array(languaguesList.length).fill("")); + const [directions, setDirections] = useState(new Array(languaguesList.length).fill("")); + const [poiList, setPoiList] = useState([]); + const [pending, setPending] = useState(false); + + + const onSubmitRegister : SubmitHandler = (data: PointOfInterest) => { + const fetch = async () => { + try{ + await POIRepository.registerPoint(data).then(() =>{ + if(forceRenderList && setIsWindowActive){ + setTimeout(() => { + forceRenderList(); + setIsWindowActive(false); + }, 1000); + } + }); + }catch(error: any){ + if(axios.isAxiosError(error)){ + error as AxiosError; + switch(error.code){ + case(axios.AxiosError.ERR_BAD_REQUEST): + setErrorMessage("Acceso no autorizado"); + break; + case(axios.AxiosError.ERR_NETWORK): + setErrorMessage("Conexión con el servidor fallida"); + break; + default: + setErrorMessage(error.message); + break; + } + } + throw new Error(); + } + } + toast.promise( + fetch(),{ + pending: "Subiendo datos...", + success: "Los datos se han subido correctamente", + error: errorMessage + } + ) + } + + const updatePOIByPlace = async (idPlace: number) => { + setPending(true); + try{ + const POIs = await POIRepository.getPOIsByPlace(idPlace); + setPoiList(POIs); + setPending(false); + }catch(error: any){ + if(axios.isAxiosError(error)){ + error as AxiosError; + showErrorAxios(error); + } + setPending(false); + } + } + + const getPointById = async (idPoint: number): Promise => { + try{ + const point = await POIRepository.getPOIById(idPoint); + return point; + }catch(error: any){ + if(axios.isAxiosError(error)){ + error as AxiosError; + showErrorAxios(error); + } + } + return null; + } + + return { + register, + handleSubmit, + errors, + onSubmitRegister, + setValue, + languageDescriptionIndexSelected, + setLanguageDescriptionIndexSelected, + languageDirectionsIndexSelected, + setLanguageDirectionsIndexSelected, + descriptions, + directions, + setDirections, + setDescriptions, + clearErrors, + resetField, + poiList, + pending, + getPointById, + updatePOIByPlace + }; +} -- GitLab From adf2c6e5c22f2543bf8617130a1984a8acc17696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:29:43 -0600 Subject: [PATCH 18/21] =?UTF-8?q?Se=20crea=20el=20componente=20y=20sus=20e?= =?UTF-8?q?stilos=20para=20visualizar=20la=20lista=20de=20puntos=20de=20in?= =?UTF-8?q?ter=C3=A9s=20de=20un=20lugar=20especifico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_panel_poi_list.tsx | 111 ++++++++++++++++++ .../assets/css/styles.css | 34 ++++++ 2 files changed, 145 insertions(+) create mode 100644 web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx create mode 100644 web/src/components/admin_panel_poi/admin_panel_poi_list/assets/css/styles.css diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx b/web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx new file mode 100644 index 00000000..0ec33afc --- /dev/null +++ b/web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx @@ -0,0 +1,111 @@ +import DataTable, { TableColumn } from 'react-data-table-component'; +import { usePlace } from '../../../hooks/usePlace'; +import './assets/css/styles.css'; +import { LoadingSpinner } from '../../loading_spinner/loading_spinner'; +import { faEdit } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { usePointOfInterest } from '../../../hooks/usePointOfInterest'; +import { PointOfInterest } from '../../../infraestructure/entities/poi'; + +interface props{ + idTown: number; + isWindowActive: boolean; + setWindowVisibility: (visibility: boolean) => void; + setActualPoint: Dispatch>; + setIsRegisterPane: Dispatch>; +} + +export const AdminPanelPoiList = ({idTown, isWindowActive}: props) => { + const [isLoading, setIsLoading] = useState(false); + const { + placeList, + updatePlacesByTown + } = usePlace(); + + const { + pending, + updatePOIByPlace, + poiList + } = usePointOfInterest(); + + const columns : TableColumn[] = [ + { + name: "Identificador", + selector: row => row.idPoint || -1, + sortable: true + }, + { + name: "Nombre", + selector: row => row.name, + sortable: true + }, + { + name: "Descripción", + selector: row => row.contentES, + sortable: true + }, + { + name: "Acciones", + cell: (row) => { + return ( + + ); + } + } + ]; + + useEffect(() => { + setIsLoading(true); + updatePlacesByTown(idTown); + setIsLoading(false); + },[]); + + const refreshList = (idPlace: number) => { + updatePOIByPlace(idPlace) + }; + + + if(isLoading) return + + return ( +
+
+ Lugar + +
+
+ + } + columns={columns} data={poiList} selectableRows className="data_table" + /> +
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_list/assets/css/styles.css b/web/src/components/admin_panel_poi/admin_panel_poi_list/assets/css/styles.css new file mode 100644 index 00000000..a3154b01 --- /dev/null +++ b/web/src/components/admin_panel_poi/admin_panel_poi_list/assets/css/styles.css @@ -0,0 +1,34 @@ +.poi_list_cnt{ + display: flex; + height: 100%; + width: 100%; + flex-direction: column; +} + +.poi_list_header{ + display: flex; + width: 100%; + height: 8%; + justify-content: center; + align-items: center; +} + +.poi_list_body{ + display: flex; + width: 100%; + flex-grow: 1; +} + +.data_table{ + height: 100%; +} + +.bhFeAR{ + display: flex !important; + height: 100%; +} + +.rdt_TableBody{ + max-height: 100%; + overflow-y: auto; +} \ No newline at end of file -- GitLab From 1ba88c49c7cb45e22946a7801d4bcf2b7b9fda48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Luna=20Hern=C3=A1ndez?= <42101656@uaz.edu.mx> Date: Thu, 22 Aug 2024 18:30:27 -0600 Subject: [PATCH 19/21] =?UTF-8?q?Se=20crea=20el=20componente=20y=20los=20e?= =?UTF-8?q?stilos=20para=20el=20panel=20para=20agregar=20un=20punto=20de?= =?UTF-8?q?=20inter=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_panel_poi_register.tsx | 207 ++++++++++++++++++ .../assets/css/styles.css | 137 ++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx create mode 100644 web/src/components/admin_panel_poi/admin_panel_poi_register/assets/css/styles.css diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx b/web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx new file mode 100644 index 00000000..d8f653e1 --- /dev/null +++ b/web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx @@ -0,0 +1,207 @@ +import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useState} from "react"; +import "./assets/css/styles.css"; +import { languaguesList } from "../../../constants/languages"; +import { LoadingScreen } from "../../loading_screen/loading_screen"; +import { usePointOfInterest } from "../../../hooks/usePointOfInterest"; +import { EmptyPointOfInterest, PointOfInterest } from "../../../infraestructure/entities/poi"; +import { ImageDropzone } from "../../image_dropzone/image_dropzone"; +import { usePlace } from "../../../hooks/usePlace"; + +interface props { + setWindowVisibility: (visibility: boolean) => void; + idTown: number; + forceRenderList: () => void; + isRegister: boolean; + form?: PointOfInterest; +} + +export const AdminPanelPoiRegister = ({setWindowVisibility, idTown,forceRenderList, isRegister, form}: props) => { + const { + register, + errors, + setDescriptions, + setDirections, + descriptions, + directions, + setLanguageDescriptionIndexSelected, + handleSubmit, + onSubmitRegister, + getPointById, + setValue, + languageDescriptionIndexSelected, + languageDirectionsIndexSelected, + setLanguageDirectionsIndexSelected, + } = usePointOfInterest(forceRenderList, setWindowVisibility); + const [isLoading, setIsLoading] = useState(false); + const [actualPOI, setActualPOI] = useState(EmptyPointOfInterest); + const [preview, setPreview] = useState(null); + const [image, setImage] = useState(null); + const { + placeList, + updatePlacesByTown + } = usePlace(); + + useEffect(() => { + if(image){ + setValue('image', image, {shouldValidate: true}); + } + },[image]); + + useEffect(() => { + setIsLoading(true); + const fetchData = async () => { + await updatePlacesByTown(idTown); + if (!isRegister && form) { + const pointGetted = await getPointById(form.idPoint || 0); + if(pointGetted){ + setActualPOI(pointGetted); + setValue('idPoint', pointGetted.idPlace); + setValue('name', pointGetted.name); + setValue('contentEN', pointGetted.contentEN); + setValue('contentES', pointGetted.contentES); + setValue('directionsEN', pointGetted.directionsEN); + setValue('directionsES', pointGetted.directionsES); + } + } + }; + fetchData(); + setIsLoading(false); + },[]); + + return ( +
+
+ Registra el punto de interés + setWindowVisibility(false)}/> +
+
+ {isLoading + ? + + : +
+
+
+
+ Nombre del punto de interés +
+ +

{errors.name?.message}

+
+ +
+
+ Descripción del punto de interés + +
+ { + languaguesList.map((language, index) => { + if(index===languageDescriptionIndexSelected){ + return ( +