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 fe958171d87e2c111077819e8b8e9f95ba3628c3..2b123a8585e93cac0c63a4bbe634c33020c9c0aa 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,14 +1,18 @@ import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Dispatch, SetStateAction, useEffect, useState} from "react"; +import { useEffect, useState} from "react"; import "./assets/css/styles.css"; -import { MapComponent } from "../../map/map"; +import { MapComponent, Position } from "../../map/map"; import { usePlace } from "../../../hooks/usePlace"; import { languaguesList } from "../../../constants/languages"; import { LoadingScreen } from "../../loading_screen/loading_screen"; import { MultipleImagesDropzone } from "../../multiple_images_dropzone/multiple_images_dropzone"; import { AvailableDays, availableDaysList, EmptyPlace, Place } from "../../../infraestructure/entities/place"; import { Category } from "../../../infraestructure/entities/category"; +import { Geocoding } from "../../geocoding/geocoding"; +import { APIProvider } from "@vis.gl/react-google-maps"; +import { REACT_APP_GOOGLE_API_KEY } from "../../../constants/api_keys"; +import { LoadingSpinner } from "../../loading_spinner/loading_spinner"; interface props { setWindowVisibility: (visibility: boolean) => void; @@ -38,7 +42,8 @@ export const AdminPanelPlaceRegister = ({setWindowVisibility, idTown, categories getPlaceById, onSubmitRegister, onSubmitUpdate, - clearErrors + clearErrors, + getValues } = usePlace(forceRenderList, setWindowVisibility); const [clickedCategories, setClickedCategories] = useState(new Array(categoriesList.length).fill(false)); const [isLoading, setIsLoading] = useState(false); @@ -46,6 +51,10 @@ export const AdminPanelPlaceRegister = ({setWindowVisibility, idTown, categories const [openHourInput, setOpenHourInput] = useState(''); const [closeHourInput, setCloseHourInput] = useState(''); + //Maps + const [position, setPosition] = useState(null); + const [ isSearching , setIsSearching] = useState(false); + const onClickCategory = (idCategory: number) => { const index = categoriesId.indexOf(idCategory); const indexList = categoriesList.findIndex(category => category.idCategory === idCategory); @@ -88,6 +97,8 @@ export const AdminPanelPlaceRegister = ({setWindowVisibility, idTown, categories setValue('closeAt', placeGetted.closeAt); setValue('available', placeGetted.available); setAvailableDays(placeGetted.available); + setPosition({latitude: Number(placeGetted.latitude), longitude: Number(placeGetted.longitude)}); + setValue('address', placeGetted.address); const clickedCategoriesBackup : boolean[] = []; categoriesList.forEach((category) => { if(placeGetted.categoriesId.indexOf(category.idCategory) > -1){ @@ -273,10 +284,15 @@ export const AdminPanelPlaceRegister = ({setWindowVisibility, idTown, categories
-
- +
+ + + {isSearching && } +
+ +
+

{errors.latitude?.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 48eeeb9e9cd1478f08c90795f35733d09e3340ed..b34ebca7d23ba3c29a7dbac5bf43e487e041b4fd 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 @@ -58,10 +58,18 @@ flex-direction: column; } -.map{ +.google_map{ height: 95%; width: 100%; padding: 10px; + display: flex; + flex-direction: column; + position: relative; +} + +.map_component{ + width: 100%; + flex-grow: 1; } .map_error_cnt{ diff --git a/web/src/components/geocoding/assets/css/styles.css b/web/src/components/geocoding/assets/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..dac47c5a3341b42e6e0677d8a105b103e4c02c92 --- /dev/null +++ b/web/src/components/geocoding/assets/css/styles.css @@ -0,0 +1,22 @@ +.g_map_cnt{ + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +.address_input_cnt{ + width: 100%; + display: flex; + flex-direction: column; +} + +.address_locate_cnt{ + width: 100%; + display: flex; + flex-direction: row-reverse; +} + +.address_locate_cnt input{ + flex-grow: 1; +} \ No newline at end of file diff --git a/web/src/components/geocoding/geocoding.tsx b/web/src/components/geocoding/geocoding.tsx new file mode 100644 index 0000000000000000000000000000000000000000..888f7ed7a7c70d92feacfd4983c17512c2a7b757 --- /dev/null +++ b/web/src/components/geocoding/geocoding.tsx @@ -0,0 +1,60 @@ +import { useMapsLibrary } from "@vis.gl/react-google-maps"; +import { useState, useEffect, Dispatch, SetStateAction, useRef } from "react"; +import { UseFormGetValues, UseFormRegister, FieldErrors } from "react-hook-form"; +import { Place } from "../../infraestructure/entities/place"; +import { Position } from "../map/map"; +import './assets/css/styles.css'; + +interface props{ + getValues: UseFormGetValues; + register: UseFormRegister; + errors: FieldErrors; + setPosition: Dispatch> + setLoading: Dispatch> +} + +export const Geocoding = ({ getValues, register, errors, setPosition, setLoading}: props) => { + const geocodingApiLoaded = useMapsLibrary('geocoding'); + const [geocodingService, setGeocodingService] = useState(); + const [geocodingResult, setGeocodingResult] = useState(); + + const getPositionByAddress = () => { + const address = getValues('address'); + if(!geocodingService || !address) return; + setLoading(true); + geocodingService?.geocode({address}, (results, status) => { + if(results && status=="OK"){ + setGeocodingResult(results[0]); + } + }); + setLoading(false); + } + + + useEffect(()=> { + if(!geocodingApiLoaded) return; + setGeocodingService(new window.google.maps.Geocoder()); + },[geocodingApiLoaded]) + + useEffect(() => { + if(!geocodingResult) return; + setPosition({latitude: geocodingResult.geometry.location.lat(), longitude: geocodingResult.geometry.location.lng()}) + }, [geocodingResult]) + + return ( +
+ +
+ + { + if(event.key === 'Enter'){ + getPositionByAddress(); + } + }} + /> +
+

{errors.address?.message}

+
+ ) +} \ No newline at end of file diff --git a/web/src/components/loading_spinner/assets/css/styles.css b/web/src/components/loading_spinner/assets/css/styles.css index bf6048a208fb291cf37eb1112d9dfb53c49baa6a..f690fb0c214307196812a2f24429de4348e759eb 100644 --- a/web/src/components/loading_spinner/assets/css/styles.css +++ b/web/src/components/loading_spinner/assets/css/styles.css @@ -1,5 +1,6 @@ .spinner{ z-index: 999; + position: absolute; width: 150px; padding: 20px; aspect-ratio: 1; @@ -13,5 +14,10 @@ -webkit-mask-composite: source-out; mask-composite: subtract; animation: l3 1s infinite linear; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; } @keyframes l3 {to{transform: rotate(1turn)}} \ No newline at end of file diff --git a/web/src/components/map/map.tsx b/web/src/components/map/map.tsx index 01fbedb4acc2ef054a98866c57eb32bcb5679c84..e88eecc085b19cdfc6924593249c1546210475d5 100644 --- a/web/src/components/map/map.tsx +++ b/web/src/components/map/map.tsx @@ -1,15 +1,14 @@ -import { useState, Dispatch, SetStateAction, useEffect } from "react"; -import { REACT_APP_GOOGLE_API_KEY } from "../../constants/api_keys"; +import { Dispatch, SetStateAction, useEffect } from "react"; import { UseFormClearErrors, UseFormSetValue } from "react-hook-form"; import { Place } from "../../infraestructure/entities/place"; -import { APIProvider, Map, Marker } from "@vis.gl/react-google-maps"; +import { Map, Marker, useMap } from "@vis.gl/react-google-maps"; interface props{ setValue: UseFormSetValue; setIsLoading: Dispatch>; clearErrors: UseFormClearErrors; - latitude?: number; - longitude?: number; + position: Position | null; + setPosition: Dispatch>; } export interface Position{ @@ -17,35 +16,39 @@ export interface Position{ longitude: number; } -export const MapComponent = ({setValue, setIsLoading, latitude, longitude, clearErrors}: props) => { - const [position, setPosition] = useState({latitude: 0.0, longitude: 0.0}); - const center = {lat: 23.687, lng: -102.74}; - useEffect(() => { - if(latitude && longitude){ - setPosition({latitude, longitude}); - } - }, [latitude]); +export const MapComponent = ({setValue, setIsLoading, position, setPosition, clearErrors}: props) => { + const defaultCenter = {lat: 23.687, lng: -102.74}; + const mapRef = useMap(); useEffect(() => { - setValue('latitude',position.latitude); - setValue('longitude',position.longitude); - clearErrors('latitude'); - clearErrors('longitude'); + if(position && position.latitude!==0 && position.longitude!==0){ + setValue('latitude',position.latitude); + setValue('longitude',position.longitude); + if(mapRef){ + mapRef.setCenter({lat: position.latitude, lng: position.longitude}); + mapRef.setZoom(16); + } + clearErrors('latitude'); + clearErrors('longitude'); + } },[position]); return ( - setIsLoading(false)}> -
- { +
+ { const lat = event.detail.latLng?.lat || 0.0; const lng = event.detail.latLng?.lng || 0.0; setPosition({latitude: lat, longitude: lng}); - }}> - - -
- + }} + + > + {position && } +
+
); } \ 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 7703c47fc7c2dcd1b1c72203f0e336cc7360f926..299676c58e61b8a8222115d7ee25e0d362b6803c 100644 --- a/web/src/data/datasources/prod/place_datasource.ts +++ b/web/src/data/datasources/prod/place_datasource.ts @@ -18,6 +18,7 @@ export class PlaceDatasourceProd implements PlaceDatasourceInf{ formToSend.append('longitude', String(form.longitude)); formToSend.append('openAt', String(form.openAt)); formToSend.append('closeAt', String(form.closeAt)); + formToSend.append('address', form.address); if(form.available === AvailableDays.CUSTOM){ formToSend.append('startDate', String(form.startDate)); @@ -77,6 +78,7 @@ export class PlaceDatasourceProd implements PlaceDatasourceInf{ formToSend.append('longitude', String(place.longitude)); formToSend.append('openAt', String(place.openAt)); formToSend.append('closeAt', String(place.closeAt)); + formToSend.append('address', place.address); if(place.available === AvailableDays.CUSTOM){ formToSend.append('startDate', String(place.startDate)); diff --git a/web/src/data/models/prod/PlaceModel.ts b/web/src/data/models/prod/PlaceModel.ts index 4adb0593327899751f16c08dc7089afbf2dab06a..5e4ea3e456073befd560a1646e5624033007bc20 100644 --- a/web/src/data/models/prod/PlaceModel.ts +++ b/web/src/data/models/prod/PlaceModel.ts @@ -15,6 +15,7 @@ export interface PlaceModel { closeAt: number; startDate?: Date; endDate?: Date; + address: string; } export const placeModelToEntity = (model: PlaceModel) =>{ @@ -47,7 +48,8 @@ export const placeModelToEntity = (model: PlaceModel) =>{ openAt: model.openAt, closeAt: model.closeAt, startDate: model.startDate, - endDate: model.endDate + endDate: model.endDate, + address: model.address } return place; } \ No newline at end of file diff --git a/web/src/hooks/usePlace.tsx b/web/src/hooks/usePlace.tsx index 69a8e095865c12430637b82fcbb1ccf192eb1ec6..550365e18c64ee08ad2821a4685d3af28139ffc5 100644 --- a/web/src/hooks/usePlace.tsx +++ b/web/src/hooks/usePlace.tsx @@ -22,14 +22,14 @@ const resolver: Resolver = async (data) => { } } - if(!data.openAt && data.openAt!=0){ + if(!data.openAt && data.openAt!==0){ errors.openAt = { type: "required", message: "La hora de apertura es requerida" }; } - if(!data.closeAt && data.closeAt!=0){ + if(!data.closeAt && data.closeAt!==0){ errors.closeAt = { type: "required", message: "La hora de cierre es requerida" @@ -64,6 +64,13 @@ const resolver: Resolver = async (data) => { } } + if(!data.address){ + errors.address = { + type: "required", + message: "Debe de ingresar la dirección al lugar" + } + } + for(var index = languaguesList.length-1; index>=0; index--){ if(!data.descriptions || !data.descriptions[index]){ errors.descriptions = { @@ -136,6 +143,7 @@ setIsWindowActive?: (visibility: boolean) => void) => { formState: {errors}, clearErrors, resetField, + getValues } = useForm({resolver}); const [errorMessage, setErrorMessage] = useState(""); const [languageDescriptionIndexSelected, setLanguageDescriptionIndexSelected] = useState(0); @@ -285,5 +293,6 @@ setIsWindowActive?: (visibility: boolean) => void) => { categoriesId, setCategoriesId, getPlaceById, + getValues }; } diff --git a/web/src/infraestructure/entities/place.ts b/web/src/infraestructure/entities/place.ts index 46fe46ba27aeb179f1ba940ce891acefcf353420..ca43cd0878319bf21a1843fda064e7ef1a12f835 100644 --- a/web/src/infraestructure/entities/place.ts +++ b/web/src/infraestructure/entities/place.ts @@ -12,6 +12,7 @@ export interface Place{ imagesList?: File[] | string[]; startDate?: Date; endDate?: Date; + address: string; } export enum AvailableDays { @@ -44,4 +45,5 @@ export const EmptyPlace : Place = { openAt: 0, closeAt: 0, available: AvailableDays.WEEKEND, + address: '' } \ No newline at end of file