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 dc04cd17f0009a36c2031c5096ccc554d5ecc188..7f5eda9a251735d4b0f9c8cc1766be7de15de11b 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 @@ -1,6 +1,6 @@ import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Dispatch, SetStateAction} from "react"; +import { Dispatch, SetStateAction, useState} from "react"; import "./assets/css/styles.css"; import { MapComponent } from "../../map/map"; import { usePlace } from "../../../hooks/usePlace"; @@ -8,14 +8,16 @@ import { languaguesList } from "../../../constants/languages"; import { LoadingScreen } from "../../loading_screen/loading_screen"; import { MultipleImagesDropzone } from "../../multiple_images_dropzone/multiple_images_dropzone"; import { AvailableDays, availableDaysList } from "../../../infraestructure/entities/place"; +import { Category } from "../../../infraestructure/entities/category"; interface props { setIsWindowActive: Dispatch>; + categoriesList: Category[]; idTown: number; forceRenderList: () => void; } -export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, forceRenderList}: props) => { +export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, categoriesList, forceRenderList}: props) => { const { register, handleSubmit, @@ -31,7 +33,33 @@ export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, forceRenderL availableDays, setAvailableDays, resetField, + categoriesId, + setCategoriesId, } = usePlace(idTown, forceRenderList, setIsWindowActive); + const [clickedCategories, setClickedCategories] = useState(new Array(categoriesList.length).fill(false)); + + const onClickCategory = (idCategory: number, indexList: number) => { + const index = categoriesId.indexOf(idCategory); + const clickedCategoriesBackup = clickedCategories.map((clickedCategory, index)=> { + if(index === indexList){ + return !clickedCategory; + }else{ + return clickedCategory; + } + }) + if(index > -1){ + setCategoriesId( + categoriesId.filter(cat => cat !== idCategory) + ); + }else{ + setCategoriesId( + [...categoriesId, + idCategory + ] + ) + } + setClickedCategories(clickedCategoriesBackup); + } return (
@@ -164,6 +192,29 @@ export const AdminPanelPlaceRegister = ({setIsWindowActive, idTown, forceRenderL

{errors.available?.message || errors.startDate?.message || errors.endDate?.message}

+
+
+ Categorías +
+
+
+ { + categoriesList.map((category, index) => { + return ( +
onClickCategory(category.idCategory, index)} + style={clickedCategories[index] ? {border: '2px solid blue'} : {border: '1px solid black'}} + > + {category.nameES} +
+ ); + }) + } +
+
+

{errors.categoriesId?.message}

+
+

{errors.imagesList?.message}

diff --git a/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css b/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css index 4f0b19f39d268b1e3b12ac86c432bca9e36d7ea6..2aefa5c06d6b4102fc5bf65e9f1a5a752e375a39 100644 --- a/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css +++ b/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css @@ -132,4 +132,28 @@ flex-direction: row; justify-content: center; align-items: center; +} + +.categories_cnt { + display: flex; + border: solid 1px black; + height: 80px; +} + +.categories_grid { + height: 100%; + overflow-x: auto; + display: grid; + grid-auto-flow: column; + grid-template-rows: auto auto; +} + +.category_item{ + display: flex; + justify-content: center; + align-items: center; + width: auto; + padding: 0 10px 0 10px; + margin: 4px; + cursor: pointer; } \ No newline at end of file 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 621f47a33e6e74eb6735c81e5b1a47dab51d7289..e3b5d087061675e5b30d51589053919392a8d12b 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 @@ -1,8 +1,10 @@ -import { Dispatch, SetStateAction, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { AdminPanelPlaceRegister } from "../admin_panel_place_register/admin_panel_place_register"; import "./assets/css/styles.css"; import { AdminPanelPlaceList } from "../admin_panel_place_list/admin_panel_place_list"; import { Town } from "../../../infraestructure/entities/town"; +import { useCategory } from "../../../hooks/useCategory"; +import { LoadingScreen } from "../../loading_screen/loading_screen"; interface props { isWindowActive: boolean; @@ -12,11 +14,22 @@ interface props { export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive, town}: props) => { const [renderCount, setRenderCount] = useState(0); + const { + categoriesList, + updateCategoriesList, + pending + } = useCategory(); const forceRenderList = () =>{ setRenderCount(prevCount => prevCount + 1); } + useEffect(()=>{ + updateCategoriesList(); + },[]); + + if(pending) return + return (
@@ -33,6 +46,7 @@ export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive, town}: isWindowActive && } diff --git a/web/src/components/admin_town_info/assets/css/styles.css b/web/src/components/admin_town_info/assets/css/styles.css index 69fd36bf152107334d4538fd3364e0f6de0e8554..729d3fe5e0d2a3f7801fd4f9ff34ba89bcd4af97 100644 --- a/web/src/components/admin_town_info/assets/css/styles.css +++ b/web/src/components/admin_town_info/assets/css/styles.css @@ -31,6 +31,11 @@ width: 50%; } +.town_image_cnt img{ + max-height: 100%; + max-width: 100%; +} + .info_fields_cnt{ width: 50%; height: 100%; diff --git a/web/src/components/confirmation_dialog_box/assets/css/styles.css b/web/src/components/confirmation_dialog_box/assets/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..7219572773c8603f025d3a7c05c3cda9acd66758 --- /dev/null +++ b/web/src/components/confirmation_dialog_box/assets/css/styles.css @@ -0,0 +1,35 @@ +.confirmation_dialog_wrap{ + position: absolute; + z-index: 99; + left: 0; + right: 0; + margin: auto; + width: 25vw; + display: flex; + flex-direction: column; + background: white; + border: solid 2px black; +} + +.confirmation_dialog_wrap .header{ + background: #ccc; +} + +.confirmation_dialog_wrap .body{ + display: flex; + flex-direction: column; + width: 100%; + align-items: center; + justify-content: center; +} + +.confirmation_dialog_wrap .buttons{ + display: flex; + justify-content: center; + align-items: center; +} + +.confirmation_dialog_wrap .buttons button{ + margin: 5px; + width: 60px; +} diff --git a/web/src/components/confirmation_dialog_box/confirmation_dialog.tsx b/web/src/components/confirmation_dialog_box/confirmation_dialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9cc9891721d140944e1fd70e7e4d1cc541d540ce --- /dev/null +++ b/web/src/components/confirmation_dialog_box/confirmation_dialog.tsx @@ -0,0 +1,40 @@ +import { Dispatch, SetStateAction } from 'react'; +import './assets/css/styles.css'; + +interface props{ + hangleToClose: () => void; + setAnswer?: Dispatch>; + title?: string; + message: string; +} + +export const ConfirmationDialog = ({hangleToClose, setAnswer, title, message}:props) => { + return ( +
+
+

{title || 'Confirmar acción'}

+
+
+
+

{message}

+
+ +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/sa_panel_category/sa_panel_category_list/assets/css/styles.css b/web/src/components/sa_panel_category/sa_panel_category_list/assets/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..9c12b79e7ae33723f9b993838f90376d49188123 --- /dev/null +++ b/web/src/components/sa_panel_category/sa_panel_category_list/assets/css/styles.css @@ -0,0 +1,19 @@ +.category_list_cnt{ + display: flex; + height: 100%; + width: 100%; +} + +.data_table{ + height: 100%; +} + +.bhFeAR{ + display: flex !important; + height: 100%; +} + +.rdt_TableBody{ + max-height: 100%; + overflow-y: auto; +} \ No newline at end of file diff --git a/web/src/components/sa_panel_category/sa_panel_category_list/sa_panel_category_list.tsx b/web/src/components/sa_panel_category/sa_panel_category_list/sa_panel_category_list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9d74d98673e11caad30d65daaeabe4718827397a --- /dev/null +++ b/web/src/components/sa_panel_category/sa_panel_category_list/sa_panel_category_list.tsx @@ -0,0 +1,102 @@ +import DataTable, { TableColumn } from 'react-data-table-component'; +import './assets/css/styles.css'; +import { LoadingSpinner } from '../../loading_spinner/loading_spinner'; +import { useCategory } from '../../../hooks/useCategory'; +import { Category } from '../../../infraestructure/entities/category'; +import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { ConfirmationDialog } from '../../confirmation_dialog_box/confirmation_dialog'; +import { toast } from 'react-toastify'; + +interface props { + isWindowActive: boolean; + setIsWindowActive: Dispatch>; +} + +export const SuperAdminPanelCategoryList = ({isWindowActive, setIsWindowActive}:props) => { + const { + categoriesList, + pending, + deleteCategory, + } = useCategory(); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [dialogMessage, setDialogMessage] = useState(''); + const [categoryDeleted, setCategoryDeleted] = useState(null); + const [deleteCategoryBool, setDeleteCategoryBool] = useState(false); + + const deleteSelectedCategory = (category: Category) => { + toast.promise( + deleteCategory(category),{ + pending: "Eliminando categoría...", + success: "La categoría se ha eliminado correctamente", + error: "No se pudo eliminar la categoría" + } + ) + } + + useEffect(() => { + if(deleteCategoryBool && categoryDeleted){ + deleteSelectedCategory(categoryDeleted); + setDeleteCategoryBool(false); + } + }, [deleteCategoryBool]); + + const handleDeleteSelectedCategory = (category: Category) => { + setDialogMessage(`¿Desea eliminar la categoría ${category.nameES}?`) + setCategoryDeleted(category); + setIsDialogOpen(true); + setIsWindowActive(true); + } + + const handleToClose = () => { + setIsWindowActive(false); + setIsDialogOpen(false) + } + + const columns : TableColumn[] = [ + { + name: "Identificador", + selector: row => row.idCategory || 0, + sortable: true + }, + { + name: "Nombre en español", + selector: row => row.nameES, + sortable: true + }, + { + name: "Nombre en Inglés", + selector: row => row.nameEN, + sortable: true + }, + { + name: "Acciones", + cell: (row) => { + return ( + { + if(!isWindowActive){ + handleDeleteSelectedCategory(row); + } + }} + /> + ); + } + } + ]; + + return ( +
+ + } + disabled={isWindowActive} + columns={columns} data={categoriesList} className="data_table"/> + { + isDialogOpen && + } +
+ ); +} \ No newline at end of file diff --git a/web/src/components/sa_panel_category/sa_panel_category_register/assets/css/styles.css b/web/src/components/sa_panel_category/sa_panel_category_register/assets/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..78b5e14d9f9c64a3ffee245dbfa218e7d09a85c0 --- /dev/null +++ b/web/src/components/sa_panel_category/sa_panel_category_register/assets/css/styles.css @@ -0,0 +1,75 @@ +*{ + user-select: none; +} + +.category_register_wrap{ + position: absolute; + z-index: 99; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + width: 25vw; + height: 30vh; + display: flex; + flex-direction: column; + background: white; + border: solid 2px black; +} + +.category_register_header{ + display: flex; + width: 100%; + align-items: center; + justify-content: center; + padding: 5px; + background: #ccc; + height: 15%; +} + +.category_register_close_btn{ + display: inline-block; + cursor: pointer; + position: absolute; + right: 5px; +} + +.category_register_body{ + height: 85%; + width: 100%; +} + +.category_register_body form{ + height: 100%; + display: flex; +} + +.category_register_body .inputs_container{ + height: 100%; + width: 100%; +} + +.category_register_body .input{ + width: 100%; + display: flex; + flex-direction: column; +} + +.category_register_body .input input{ + margin: 0px 30px 0px 30px; +} + +.category_register_body .input_header{ + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.error{ + color: red; + font-size: 12px; + padding: 0; + margin: 0; +} diff --git a/web/src/components/sa_panel_category/sa_panel_category_register/sa_panel_category_register.tsx b/web/src/components/sa_panel_category/sa_panel_category_register/sa_panel_category_register.tsx new file mode 100644 index 0000000000000000000000000000000000000000..675d0d68f5a8359a9e090b898e32cea712bae40c --- /dev/null +++ b/web/src/components/sa_panel_category/sa_panel_category_register/sa_panel_category_register.tsx @@ -0,0 +1,60 @@ +import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Dispatch, SetStateAction} from "react"; +import "./assets/css/styles.css"; +import { useCategory } from "../../../hooks/useCategory"; + +interface props { + handleClickToClose: () => void; + forceRenderList: () => void; +} + +export const SuperAdminPanelCategoryRegister = ({handleClickToClose, forceRenderList}: props) => { + const { + register, + handleSubmit, + errors, + onSubmit, + } = useCategory(forceRenderList, handleClickToClose); + + return ( +
+
+ Registra la categoría + handleClickToClose()}/> +
+
+
+
+
+
+ Nombre de la categoría(español) +
+ +

{errors.nameES?.message}

+
+ +
+
+ Nombre de la categoría(inglés) +
+ +

{errors.nameEN?.message}

+
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/sa_panel_category/sa_panel_category_screen/assets/css/styles.css b/web/src/components/sa_panel_category/sa_panel_category_screen/assets/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..5ead15cf6aee8c3193491f98020223b332d81327 --- /dev/null +++ b/web/src/components/sa_panel_category/sa_panel_category_screen/assets/css/styles.css @@ -0,0 +1,29 @@ +.panel_category_content{ + width: 100%; + max-height: 100%; + display: flex; + flex-direction: column; +} + +.panel_category_header{ + height: 7%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 5px; + background: gray; +} + +.panel_category_header .category_add_btn{ + display: inline-block; + cursor: pointer; + position: absolute; + right: 5px; +} + +.panel_category_body{ + height: 93%; + width: 100%; + background: white; +} \ No newline at end of file diff --git a/web/src/components/sa_panel_category/sa_panel_category_screen/sa_panel_category_screen.tsx b/web/src/components/sa_panel_category/sa_panel_category_screen/sa_panel_category_screen.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c5b27edd49f7f7f5f495b74ac892f6a90c5a276e --- /dev/null +++ b/web/src/components/sa_panel_category/sa_panel_category_screen/sa_panel_category_screen.tsx @@ -0,0 +1,49 @@ +import { Dispatch, SetStateAction, useState } from "react"; +import "./assets/css/styles.css"; +import { SuperAdminPanelCategoryRegister } from "../sa_panel_category_register/sa_panel_category_register"; +import { SuperAdminPanelCategoryList } from "../sa_panel_category_list/sa_panel_category_list"; + +interface props { + isWindowActive: boolean; + setIsWindowActive: Dispatch>; +} + +export const SuperAdminPanelCategoryScreen = ({isWindowActive,setIsWindowActive}: props) => { + const [renderCount, setRenderCount] = useState(0); + const [isRegisterWindowOpen, setIsRegisterWindowOpen] = useState(false); + + const handleClickToClose = () => { + setIsWindowActive(false); + setIsRegisterWindowOpen(false); + } + + const forceRenderList = () =>{ + setRenderCount(prevCount => prevCount + 1); + } + + return ( +
+
+ Administrar Categorías + +
+
+ { + isRegisterWindowOpen && + } + +
+
+ ); +} \ No newline at end of file diff --git a/web/src/constants/selected_panel.ts b/web/src/constants/selected_panel.ts index 3498fa3249bfa097ab440033138a7455a3927019..64626a333520adffe908cb89a096797362574057 100644 --- a/web/src/constants/selected_panel.ts +++ b/web/src/constants/selected_panel.ts @@ -1,5 +1,11 @@ export enum AdminSelectedPanel { - TOWN_INFO = 'town_info', + TOWN_INFO = "town_info", PLACES = "places", ACTIVITIES = "activities" +} + +export enum SuperAdminSelectedPanel { + TOWNS = "towns", + ADMINS = "admins", + CATEGORIES = "categories" } \ No newline at end of file diff --git a/web/src/data/datasources/prod/category_datasource.ts b/web/src/data/datasources/prod/category_datasource.ts new file mode 100644 index 0000000000000000000000000000000000000000..264be779ab801ed40dd733532955bb4c3e5f892d --- /dev/null +++ b/web/src/data/datasources/prod/category_datasource.ts @@ -0,0 +1,37 @@ +import axios from "axios"; +import { CategoryDatasourceInf } from "../../../infraestructure/datasources/category_datasource"; +import { Category, CategoryFormValues } from "../../../infraestructure/entities/category"; +import { CategoryModel } from "../../models/prod/CategoryModel"; +import { APIUrl } from "../../../constants/api_url"; + +export class CategoryDatasourceProd implements CategoryDatasourceInf{ + async registerCategory(form: CategoryFormValues): Promise { + await axios.post( + APIUrl + "/category", + { + nameES: form.nameES, + nameEN: form.nameEN + } + ); + } + + async getCategories(): Promise { + const {data: dataES} = await axios.get(APIUrl+'/category/ES'); + const {data: dataEN} = await axios.get(APIUrl+'/category/EN'); + const categories : Category[] = []; + for(let i=0; i { + await axios.delete(APIUrl + `/category/${category.idCategory}`) + } +} \ No newline at end of file diff --git a/web/src/data/datasources/prod/place_datasource.ts b/web/src/data/datasources/prod/place_datasource.ts index 353e38e745ea920ec63b57135c02dc569095a37e..938bdd97d89f10cce8b8c8a2e07513c036f0fb74 100644 --- a/web/src/data/datasources/prod/place_datasource.ts +++ b/web/src/data/datasources/prod/place_datasource.ts @@ -10,6 +10,7 @@ export class PlaceDatasourceProd implements PlaceDatasourceInf{ formToSend.append('available', form.available); formToSend.append('idTown', String(form.idTown)); formToSend.append('name', form.name); + formToSend.append('categoriesId', form.categoriesId); formToSend.append('descriptionES', form.descriptions?.[0] ?? ''); formToSend.append('descriptionEN', form.descriptions?.[1] ?? ''); formToSend.append('image', form.imagesList?.[0] ?? ''); @@ -60,6 +61,7 @@ export class PlaceDatasourceProd implements PlaceDatasourceInf{ latitude: value.latitude, longitude: value.longitude, name: value.name, + categoriesId: value.categoriesId, openAt: value.openAt, closeAt: value.closeAt } diff --git a/web/src/data/models/prod/CategoryModel.ts b/web/src/data/models/prod/CategoryModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..f16740feca3d91633a6475baa9037f2b01aa5637 --- /dev/null +++ b/web/src/data/models/prod/CategoryModel.ts @@ -0,0 +1,4 @@ +export interface CategoryModel{ + idCategory: number; + name: string; +} \ No newline at end of file diff --git a/web/src/data/models/prod/PlaceModel.ts b/web/src/data/models/prod/PlaceModel.ts index b201143a0ef58ce4c0a5c0b44717081ef5003a7b..c8a674d800304100bbc55c89129ceb0d227f6cb3 100644 --- a/web/src/data/models/prod/PlaceModel.ts +++ b/web/src/data/models/prod/PlaceModel.ts @@ -7,6 +7,7 @@ export interface PlaceModel { longitude: number; imageName: string; name: string; + categoriesId: string; openAt: number; closeAt: number; startDate?: Date; diff --git a/web/src/data/repositories/prod/category_repository.ts b/web/src/data/repositories/prod/category_repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab0055ebf05862a28fb74fc01613fc46cfd69053 --- /dev/null +++ b/web/src/data/repositories/prod/category_repository.ts @@ -0,0 +1,21 @@ +import { CategoryDatasourceInf } from "../../../infraestructure/datasources/category_datasource"; +import { Category, CategoryFormValues } from "../../../infraestructure/entities/category"; +import { CategoryRepositoryInf } from "../../../infraestructure/repositories/category_repository"; + +export class CategoryRepositoryProd implements CategoryRepositoryInf{ + constructor( + private datasource: CategoryDatasourceInf + ){} + + async registerCategory(form: CategoryFormValues): Promise { + return this.datasource.registerCategory(form); + } + + async getCategories(): Promise { + return this.datasource.getCategories(); + } + + async deleteCategory(category: Category): Promise { + return this.datasource.deleteCategory(category); + } +} \ No newline at end of file diff --git a/web/src/hooks/useCategory.tsx b/web/src/hooks/useCategory.tsx new file mode 100644 index 0000000000000000000000000000000000000000..16443f41d6a33b218f84cb0f5be2fc58ab280a72 --- /dev/null +++ b/web/src/hooks/useCategory.tsx @@ -0,0 +1,130 @@ +import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form"; +import { CategoryDatasourceProd } from "../data/datasources/prod/category_datasource" +import { CategoryRepositoryProd } from "../data/repositories/prod/category_repository"; +import { Category, CategoryFormValues } from "../infraestructure/entities/category"; +import axios, { AxiosError } from "axios"; +import { toast } from "react-toastify"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { showErrorAxios } from "../utils/Messages"; + +const categoryDatasource = new CategoryDatasourceProd(); +const categoryRepository = new CategoryRepositoryProd(categoryDatasource); + +const resolver : Resolver = async (data) => { + const errors: FieldErrors = {}; + + if(!data.nameES){ + errors.nameES = { + type: 'required', + message: 'El nombre en español de la categoría es requerido' + } + } + + + if(!data.nameEN){ + errors.nameEN = { + type: 'required', + message: 'El nombre en inglés de la categoría es requerido' + } + } + + return { + values: Object.keys(errors).length > 0 ? {} : data, + errors: errors + } +} + +export const useCategory = (forceRenderList?: () => void, handleClickToClose?: () => void) => { + const { + register, + handleSubmit, + setValue, + formState: {errors}, + clearErrors, + resetField, + } = useForm({resolver}); + const [errorMessage, setErrorMessage] = useState(""); + const [pending, setPending] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [categoriesList, setCategoriesList] = useState([]); + + const onSubmit : SubmitHandler = (data: CategoryFormValues) => { + const fetch = async () => { + try{ + await categoryRepository.registerCategory(data).then(() =>{ + if(forceRenderList && handleClickToClose){ + forceRenderList(); + handleClickToClose(); + } + }); + }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 updateCategoriesList = async () => { + setPending(true); + try{ + const categories = await categoryRepository.getCategories(); + setCategoriesList(categories); + setPending(false); + }catch(error: any){ + if(axios.isAxiosError(error)){ + error as AxiosError; + showErrorAxios(error); + } + setPending(false); + } + } + + useEffect(()=>{ + setIsLoading(true); + updateCategoriesList(); + setIsLoading(false); + },[]); + + const deleteCategory = async (category: Category) => { + setPending(true); + await categoryRepository.deleteCategory(category); + const categories = await categoryRepository.getCategories(); + setCategoriesList(categories); + setPending(false); + } + + return { + register, + handleSubmit, + errors, + onSubmit, + setValue, + clearErrors, + resetField, + updateCategoriesList, + categoriesList, + pending, + deleteCategory, + isLoading + }; +} \ No newline at end of file diff --git a/web/src/hooks/usePlace.tsx b/web/src/hooks/usePlace.tsx index 19d324994b1bcb08f62efe5e855df3c027adfd7f..f99696bf6cd9341ffb808cf5340c4f633bd92b30 100644 --- a/web/src/hooks/usePlace.tsx +++ b/web/src/hooks/usePlace.tsx @@ -57,6 +57,20 @@ const resolver: Resolver = async (data) => { }; } + if(!data.categoriesId){ + errors.categoriesId = { + type: "required", + message: "Debe seleccionar al menos una categoría" + } + }else{ + if(data.categoriesId.length < 1){ + errors.categoriesId = { + type: "required", + message: "Debe seleccionar al menos una categoría" + } + } + } + for(var index = languaguesList.length-1; index>=0; index--){ if(!data.descriptions || !data.descriptions[index]){ errors.descriptions = { @@ -137,6 +151,13 @@ setIsWindowActive?: Dispatch>) => { const [placeList, setPlaceList] = useState([]); const [isLoading, setIsLoading] = useState(false); const [pending, setPending] = useState(false); + const [categoriesId, setCategoriesId] = useState([]); + + + useEffect(()=> { + const categoriesIdString = categoriesId.join(','); + setValue('categoriesId',categoriesIdString,{shouldValidate: true}); + },[categoriesId]); useEffect(() => { setIsLoading(true); @@ -228,6 +249,8 @@ setIsWindowActive?: Dispatch>) => { resetField, updatePlacesByTown, placeList, - pending + pending, + categoriesId, + setCategoriesId, }; } diff --git a/web/src/hooks/useSuperadminHomePage.tsx b/web/src/hooks/useSuperadminHomePage.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5e0a7e45013b8cdf0800c4516f50442bbacc6530 --- /dev/null +++ b/web/src/hooks/useSuperadminHomePage.tsx @@ -0,0 +1,65 @@ +import { useEffect, useState } from "react"; +import { SuperAdminSelectedPanel } from "../constants/selected_panel"; +import { useAuth } from "../context/auth_context"; +import { useWindowShow } from "./useWindowShow"; +import { useAdmin } from "./useAdmin"; +import { UserRole } from "../constants/roles"; +import { useGetStatesList } from "./useGetStatesList"; +import axios, { AxiosError } from "axios"; +import { showErrorAxios } from "../utils/Messages"; + +export const useSuperAdminHomePage = () => { + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [collapsed, setCollapsed] = useState(true); + const {setIsWindowActive, isWindowActive} = useWindowShow(); + const [selectedPanel, setSelectedPanel] = useState(SuperAdminSelectedPanel.TOWNS); + const {user, logout} = useAuth(); + const {getAdminInfo} = useAdmin(); + const {getStates, statesList} = useGetStatesList(); + + useEffect(() => { + const checkAdminRole = async () => { + const admin = await getAdminInfo(); + if(admin){ + if(admin.role!==UserRole.SUPERADMIN){ + setError(true); + setErrorMessage("No estas autorizado para acceder a esta página") + setTimeout(() => { + logout(); + }, 5000); + return; + } + } + setIsLoading(false); + }; + checkAdminRole(); + + const getStatesList = async () => { + try{ + getStates(); + setIsLoading(false); + }catch(error: any){ + if(axios.isAxiosError(error)){ + error as AxiosError; + showErrorAxios(error); + } + } + } + getStatesList(); + },[user]); + + return { + collapsed, + isWindowActive, + selectedPanel, + setCollapsed, + setSelectedPanel, + setIsWindowActive, + isLoading, + error, + errorMessage, + statesList + }; +} \ No newline at end of file diff --git a/web/src/infraestructure/datasources/category_datasource.ts b/web/src/infraestructure/datasources/category_datasource.ts new file mode 100644 index 0000000000000000000000000000000000000000..d130c5c811db04d9ab5efe1c96d3d30eb070f2f3 --- /dev/null +++ b/web/src/infraestructure/datasources/category_datasource.ts @@ -0,0 +1,7 @@ +import { Category, CategoryFormValues } from "../entities/category"; + +export interface CategoryDatasourceInf{ + registerCategory(form: CategoryFormValues): Promise; + getCategories(): Promise; + deleteCategory(category: Category): Promise; +} \ No newline at end of file diff --git a/web/src/infraestructure/entities/category.ts b/web/src/infraestructure/entities/category.ts new file mode 100644 index 0000000000000000000000000000000000000000..b70666631144d704b91cd697f8e025296683b559 --- /dev/null +++ b/web/src/infraestructure/entities/category.ts @@ -0,0 +1,10 @@ +export interface Category{ + idCategory: number; + nameES: string; + nameEN: string; +} + +export interface CategoryFormValues{ + nameES: string; + nameEN: string; +} \ No newline at end of file diff --git a/web/src/infraestructure/entities/place.ts b/web/src/infraestructure/entities/place.ts index 67669276a9defa445c705a3fdb7086116a92f40e..bdbe0fb1a735c2976f402ea1d2b7f4598c8681fd 100644 --- a/web/src/infraestructure/entities/place.ts +++ b/web/src/infraestructure/entities/place.ts @@ -2,6 +2,7 @@ export interface Place{ idTown: number; idPlace? : number; name: string; + categoriesId: string; descriptions?: string[]; latitude: number; longitude: number; diff --git a/web/src/infraestructure/repositories/category_repository.ts b/web/src/infraestructure/repositories/category_repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9ae769c16c6b7bf34ddeb5c8477fb080b159f04 --- /dev/null +++ b/web/src/infraestructure/repositories/category_repository.ts @@ -0,0 +1,7 @@ +import { Category, CategoryFormValues } from "../entities/category"; + +export interface CategoryRepositoryInf{ + registerCategory(form: CategoryFormValues): Promise; + getCategories(): Promise; + deleteCategory(category: Category): Promise; +} \ No newline at end of file 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 b0a861ee857d9324fde4c4974a94d85e91961e5e..76717880669b872fd3d88d45f3aa519a65fe28c9 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 @@ -1,45 +1,49 @@ import { Menu, MenuItem, Sidebar } from "react-pro-sidebar" import './assets/styles/style.css'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faMonument, faUserTie } from "@fortawesome/free-solid-svg-icons"; -import { useEffect, useState } from "react"; +import { faMonument, faTags, faUserTie } from "@fortawesome/free-solid-svg-icons"; import { AdminPanelNavBar } from "../../../components/admin_panel_navbar/admin_navbar"; import { SidebarHeader } from "../../../components/sidebar_header/sidebar_header"; import { SuperadminPanelTownScreen } from "../../../components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen"; import { SuperadminPanelAdminScreen } from "../../../components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen"; -import { useGetStatesList } from "../../../hooks/useGetStatesList"; -import axios, { AxiosError } from "axios"; import { LoadingScreen } from "../../../components/loading_screen/loading_screen"; import { ToastContainer } from "react-toastify"; -import { showErrorAxios } from "../../../utils/Messages"; +import { ErrorWindow } from "../../../components/error_window/error_window"; +import { LoadingSpinner } from "../../../components/loading_spinner/loading_spinner"; +import { SuperAdminSelectedPanel } from "../../../constants/selected_panel"; +import { useSuperAdminHomePage } from "../../../hooks/useSuperadminHomePage"; +import { SuperAdminPanelCategoryScreen } from "../../../components/sa_panel_category/sa_panel_category_screen/sa_panel_category_screen"; export const SuperAdminHomePage = () => { - const [collapsed, setCollapsed] = useState(true); - const [windowActive, setWindowActive] = useState(false); - const [townPanel, setTownPanel] = useState(true); - const [isLoading, setIsLoading] = useState(true); - const {getStates, statesList} = useGetStatesList(); + const { + collapsed, + isWindowActive, + setCollapsed, + selectedPanel, + setSelectedPanel, + setIsWindowActive, + isLoading, + error, + errorMessage, + statesList, - useEffect(() => { - const getStatesList = async () => { - try{ - getStates(); - setIsLoading(false); - }catch(error: any){ - if(axios.isAxiosError(error)){ - error as AxiosError; - showErrorAxios(error); - } - } - } - getStatesList(); - }, []); + } = useSuperAdminHomePage(); + + if(isLoading) return ; + + if(error) return ( +
+ + Redirigiendo... + +
+ ); return (
{ - windowActive ? + isWindowActive ? setCollapsed(true) : setCollapsed(false) @@ -50,39 +54,50 @@ export const SuperAdminHomePage = () => { } - onClick={() => setTownPanel(true)} - disabled={windowActive}> + onClick={() => setSelectedPanel(SuperAdminSelectedPanel.TOWNS)} + disabled={isWindowActive}> Pueblos } - onClick={() => setTownPanel(false)} - disabled={windowActive}> + onClick={() => setSelectedPanel(SuperAdminSelectedPanel.ADMINS)} + disabled={isWindowActive}> Administradores + + } + onClick={() => setSelectedPanel(SuperAdminSelectedPanel.CATEGORIES)} + disabled={isWindowActive}> + Categorías +
- +
- {townPanel - ? - isLoading || statesList.length===0 - ? - - : - - : - - } + {(() => { + switch (selectedPanel) { + case SuperAdminSelectedPanel.TOWNS: + return + case SuperAdminSelectedPanel.ADMINS: + return + case SuperAdminSelectedPanel.CATEGORIES: + return + default: + return null; + } + })()}