From c70bfc6c2c2f870bf7ab84cdc1c1cb5c4994c784 Mon Sep 17 00:00:00 2001 From: Fabricio Pagliarini Date: Fri, 29 Nov 2024 00:42:10 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Adiciona=20l=C3=B3gica=20de=20pagin?= =?UTF-8?q?a=20de=20favoritos,=20prefer=C3=AAncias=20de=20usu=C3=A1rio=20e?= =?UTF-8?q?=20se=C3=A7=C3=A3o=20de=20ajuda?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/detalhes/page.tsx | 17 +++- app/favoritos/page.tsx | 115 +++++++++++++++++++++++++- components/Filters/index.tsx | 46 +++++++---- components/Header/index.tsx | 11 ++- components/PreferencesModal/index.tsx | 75 ++++++++++++++++- domains/Places/types.ts | 1 + 6 files changed, 241 insertions(+), 24 deletions(-) diff --git a/app/detalhes/page.tsx b/app/detalhes/page.tsx index 438c348..57cbd6a 100644 --- a/app/detalhes/page.tsx +++ b/app/detalhes/page.tsx @@ -7,7 +7,7 @@ import useCoordinates from "@/hooks/useCoordinates"; import dynamic from "next/dynamic"; import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; -import { FaMapPin } from "react-icons/fa"; +import { FaMapPin, FaStar } from "react-icons/fa"; const PlaceDetailsPage: React.FC = () => { const [selectedPlace, setSelectedPlace] = useState(); @@ -92,6 +92,21 @@ const PlaceDetailsPage: React.FC = () => { ) )} + + {loading || !placeDetails || !selectedPlace ? ( +
+ ) : ( +
+
+ + Avaliações +
+ +

+ Esta funcionalidade estará disponível em breve! +

+
+ )} ); }; diff --git a/app/favoritos/page.tsx b/app/favoritos/page.tsx index 95249c0..0bc356b 100644 --- a/app/favoritos/page.tsx +++ b/app/favoritos/page.tsx @@ -1,8 +1,117 @@ +"use client"; +import Filter from "@/components/Filters"; +import NavbarSearch from "@/components/NavbarSearch"; +import PlaceCard from "@/components/PlaceCard"; +import { useAuth } from "@/contexts/AuthContext"; +import { useFavorites } from "@/contexts/FavoritePlacesContext"; +import { usePlacesContext } from "@/contexts/PlacesContext"; +import { Card, CardBody } from "@nextui-org/react"; +import { useEffect, useMemo, useState } from "react"; +import { FaSearch } from "react-icons/fa"; + export default function FavoritesPage() { + const [searchValue, setSearchValue] = useState(""); + + const { user, isAnonymous } = useAuth(); + const { + places, + filteredPlaces, + loading: placesLoading, + setFilteredPlaces, + } = usePlacesContext(); + const { + favorites, + fetchFavorites, + isLoading: favoritesLoading, + } = useFavorites(); + + useEffect(() => { + if (favorites.length === 0 && user && !isAnonymous) + fetchFavorites(user?.internalId); + }, [favorites, fetchFavorites, isAnonymous, user]); + + useEffect(() => { + const removeAccents = (str: string) => { + return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + }; + + const searchTerm = removeAccents(searchValue.trim().toLowerCase()); + + const matchingPlaces = places.filter((place) => { + const placeNameNormalized = removeAccents(place.name.toLowerCase()); + const placeDescriptionNormalized = removeAccents( + place.description.toLowerCase() + ); + + return ( + placeNameNormalized.includes(searchTerm) || + placeDescriptionNormalized.includes(searchTerm) + ); + }); + + setFilteredPlaces(matchingPlaces); + }, [searchValue, places, setFilteredPlaces]); + + const handleSearch = (value: string) => { + setSearchValue(value); + }; + + const userFavoritePlaces = useMemo(() => { + return filteredPlaces.filter((p) => favorites.some((f) => f === p.id)); + }, [favorites, filteredPlaces]); + return ( -
-

Seus locais favoritos

-

Sim

+
+ +
+
+

Seus locais favoritos

+
+ +
+
+
+ {favoritesLoading || placesLoading + ? Array.from({ length: 9 }).map((_, index) => ( + +
+ +
+
+
+
+
+
+
+
+
+ )) + : userFavoritePlaces.map((place) => ( + f === place.id)} + /> + ))} +
+ + {!favoritesLoading && !placesLoading && filteredPlaces.length === 0 && ( +
+ + + Nenhum resultado encontrado, tente pesquisar novamente{" "} + + + +
+ )} +
); } diff --git a/components/Filters/index.tsx b/components/Filters/index.tsx index 5c89ace..d00cbcc 100644 --- a/components/Filters/index.tsx +++ b/components/Filters/index.tsx @@ -1,34 +1,48 @@ import React, { useState, useEffect } from "react"; import { FaFilter } from "react-icons/fa"; -import { usePlacesContext } from "@/contexts/PlacesContext"; // Importando o context +import { usePlacesContext } from "@/contexts/PlacesContext"; interface FilterProps { className?: string; // Permite passar className como prop } const Filter: React.FC = ({ className }) => { - const [selectedType, setSelectedType] = useState(""); // Estado para o tipo selecionado - const { places, setFilteredPlaces } = usePlacesContext(); // Acessa o context + const [selectedType, setSelectedType] = useState(""); + const [selectedVisibility, setSelectedVisibility] = useState(""); + const { places, setFilteredPlaces } = usePlacesContext(); - // Função que será chamada quando o filtro de tipo mudar - const handleFilterChange = (e: React.ChangeEvent) => { + const handleTypeFilterChange = (e: React.ChangeEvent) => { const value = e.target.value; setSelectedType(value); }; - // Filtra os lugares com base no tipo selecionado + // Função que será chamada quando o filtro de visibilidade mudar + const handleVisibilityFilterChange = ( + e: React.ChangeEvent + ) => { + const value = e.target.value; + setSelectedVisibility(value); + }; + useEffect(() => { - if (selectedType === "") { - setFilteredPlaces(places); // Se nenhum tipo for selecionado, mostra todos - } else { - const filtered = places.filter((place) => + let filtered = places; + + if (selectedType) { + filtered = filtered.filter((place) => place.type.includes( selectedType as "restaurant" | "atraction" | "route" ) ); - setFilteredPlaces(filtered); } - }, [selectedType, places, setFilteredPlaces]); + + if (selectedVisibility) { + filtered = filtered.filter( + (place) => place.popularity === selectedVisibility + ); + } + + setFilteredPlaces(filtered); + }, [selectedType, selectedVisibility, places, setFilteredPlaces]); return (
@@ -36,13 +50,12 @@ const Filter: React.FC = ({ className }) => { Filtros - {/* Filtro de tipo */}
+ + setSelectValue(["small"])} + /> + setSelectValue(["medium"])} + /> + setSelectValue(["large"])} + /> + setSelectValue(["all"])} + /> + + + setSliderValue(Number(value))} + label={`Mostrar locais em um raio de ${sliderValue} km`} + /> + diff --git a/domains/Places/types.ts b/domains/Places/types.ts index 019e2ce..43d902d 100644 --- a/domains/Places/types.ts +++ b/domains/Places/types.ts @@ -6,6 +6,7 @@ export type Place = { description: string; image: string; type: PlaceType[]; + popularity: string; }; interface CarouselImage {