Skip to content

Commit

Permalink
Merge pull request #14 from fabriciopgl/feature/1
Browse files Browse the repository at this point in the history
✨ Adiciona lógica de pagina de favoritos, preferências de usuário e s…
  • Loading branch information
fabriciopgl authored Nov 29, 2024
2 parents b39b3cf + c70bfc6 commit cffe2d2
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 24 deletions.
17 changes: 16 additions & 1 deletion app/detalhes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Place>();
Expand Down Expand Up @@ -92,6 +92,21 @@ const PlaceDetailsPage: React.FC = () => {
</div>
)
)}

{loading || !placeDetails || !selectedPlace ? (
<div className="w-full h-[100px] bg-gray-200 animate-pulse rounded-lg mb-8"></div>
) : (
<div className="w-full h-[100px] mb-8">
<div className="flex items-center space-x-2 mb-4">
<FaStar className="text-yellow-400 text-3xl" />
<span className="text-4xl font-bold">Avaliações</span>
</div>

<p className="text-lg mb-8 text-justify">
Esta funcionalidade estará disponível em breve!
</p>
</div>
)}
</div>
);
};
Expand Down
115 changes: 112 additions & 3 deletions app/favoritos/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="container mx-auto p-4">
<h1 className="text-4xl font-bold mb-4">Seus locais favoritos</h1>
<p>Sim</p>
<div className="container mx-auto flex p-4">
<Filter className="hidden md:block w-1/6 p-4 border-r border-gray-300" />
<div className="flex-1 p-4">
<div className="flex justify-between items-center">
<h1 className="text-4xl font-bold">Seus locais favoritos</h1>
<div className="w-1/3">
<NavbarSearch onSearch={handleSearch} />
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-5 pt-8">
{favoritesLoading || placesLoading
? Array.from({ length: 9 }).map((_, index) => (
<Card
key={index}
className="w-80 h-90 mx-auto shadow-lg animate-pulse"
>
<div className="bg-gray-300 h-[250px] w-full object-cover rounded-t-lg"></div>
<CardBody className="flex flex-col h-full p-4">
<div className="bg-gray-300 h-6 w-3/4 mb-4 rounded"></div>
<div className="bg-gray-300 h-4 w-full mb-4 rounded"></div>
<div className="bg-gray-300 h-4 w-5/6 rounded mb-4 flex-1"></div>
<div className="flex flex-row justify-between items-center gap-2">
<div className="bg-gray-300 h-10 w-full rounded-lg"></div>
<div className="bg-gray-300 h-10 w-10 rounded-full"></div>
</div>
</CardBody>
</Card>
))
: userFavoritePlaces.map((place) => (
<PlaceCard
key={place.id}
id={place.id}
name={place.name}
imageUrl={place.image}
description={place.description}
favorite={favorites?.some((f) => f === place.id)}
/>
))}
</div>

{!favoritesLoading && !placesLoading && filteredPlaces.length === 0 && (
<div className="flex justify-center items-center mt-4">
<Card>
<CardBody className="flex flex-row justify-center items-center gap-2">
Nenhum resultado encontrado, tente pesquisar novamente{" "}
<FaSearch />
</CardBody>
</Card>
</div>
)}
</div>
</div>
);
}
46 changes: 31 additions & 15 deletions components/Filters/index.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,61 @@
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<FilterProps> = ({ className }) => {
const [selectedType, setSelectedType] = useState<string>(""); // Estado para o tipo selecionado
const { places, setFilteredPlaces } = usePlacesContext(); // Acessa o context
const [selectedType, setSelectedType] = useState<string>("");
const [selectedVisibility, setSelectedVisibility] = useState<string>("");
const { places, setFilteredPlaces } = usePlacesContext();

// Função que será chamada quando o filtro de tipo mudar
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const handleTypeFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
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<HTMLSelectElement>
) => {
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 (
<div className={`${className}`}>
<h2 className="flex text-xl font-bold mb-4 gap-2 justify-start items-center">
<FaFilter /> Filtros
</h2>

{/* Filtro de tipo */}
<div>
<label className="block mb-2">Tipo:</label>
<select
className="block w-full p-2 border rounded"
value={selectedType}
onChange={handleFilterChange}
onChange={handleTypeFilterChange}
>
<option value="">Todos</option>
<option value="restaurant">Restaurantes</option>
Expand All @@ -52,7 +65,10 @@ const Filter: React.FC<FilterProps> = ({ className }) => {

<div className="mt-4">
<label className="block mb-2">Visibilidade:</label>
<select className="block w-full p-2 border rounded">
<select
className="block w-full p-2 border rounded"
onChange={handleVisibilityFilterChange}
>
<option value="">Todas</option>
<option value="alta">Muito conhecido</option>
<option value="media">Conhecido na região</option>
Expand Down
11 changes: 10 additions & 1 deletion components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,16 @@ export default function Header() {
>
Preferências
</DropdownItem>
<DropdownItem startContent={<FaQuestion />} key="help">
<DropdownItem
startContent={<FaQuestion />}
key="help"
onClick={() =>
window.open(
"https://github.com/fabriciopgl/turismo-serra-gaucha/blob/main/README.md",
"_blank"
)
}
>
Ajuda
</DropdownItem>
<DropdownItem
Expand Down
75 changes: 71 additions & 4 deletions components/PreferencesModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"use client";
import {
Button,
ButtonGroup,
Checkbox,
Divider,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
useDisclosure,
Select,
SelectItem,
Slider,
} from "@nextui-org/react";
import React from "react";
import React, { useState } from "react";
import { FaSave, FaTimes } from "react-icons/fa";

interface PreferencesModalProps {
Expand All @@ -19,6 +25,28 @@ const PreferencesModal: React.FC<PreferencesModalProps> = ({
onClose,
isOpen,
}) => {
const getSelectValueFromStorage = () => {
const storedValue = localStorage.getItem("selectValue");
return storedValue ? JSON.parse(storedValue) : ["all"];
};

const getSliderValueFromStorage = () => {
const storedValue = localStorage.getItem("sliderValue");
return storedValue ? JSON.parse(storedValue) : 100;
};

const [selectValue, setSelectValue] = useState(getSelectValueFromStorage());
const [sliderValue, setSliderValue] = useState(getSliderValueFromStorage());

const saveToLocalStorage = () => {
localStorage.setItem("selectValue", JSON.stringify(selectValue));
localStorage.setItem("sliderValue", JSON.stringify(sliderValue));
};

const onSavePreferences = () => {
saveToLocalStorage();
onClose();
};
return (
<>
<Modal
Expand All @@ -27,14 +55,53 @@ const PreferencesModal: React.FC<PreferencesModalProps> = ({
onClose();
}}
placement="center"
size="lg"
>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col gap-1">
Preferências
</ModalHeader>
<ModalBody></ModalBody>
<ModalBody className="gap-5">
<Select
disallowEmptySelection
labelPlacement="outside"
defaultSelectedKeys={selectValue}
label="Priorizar empreendimentos"
>
<SelectItem
key={"small"}
title="Pequenos"
onClick={() => setSelectValue(["small"])}
/>
<SelectItem
key={"medium"}
title="Médios"
onClick={() => setSelectValue(["medium"])}
/>
<SelectItem
key={"large"}
title="Grandes"
onClick={() => setSelectValue(["large"])}
/>
<SelectItem
key={"all"}
title="Todos"
onClick={() => setSelectValue(["all"])}
/>
</Select>

<Slider
step={10}
minValue={10}
maxValue={1000}
hideValue={true}
defaultValue={sliderValue}
onChange={(value) => setSliderValue(Number(value))}
label={`Mostrar locais em um raio de ${sliderValue} km`}
/>
</ModalBody>
<ModalFooter>
<Button
color="danger"
Expand All @@ -47,7 +114,7 @@ const PreferencesModal: React.FC<PreferencesModalProps> = ({
<Button
startContent={<FaSave />}
color="primary"
onPress={onClose}
onPress={onSavePreferences}
>
Salvar
</Button>
Expand Down
1 change: 1 addition & 0 deletions domains/Places/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type Place = {
description: string;
image: string;
type: PlaceType[];
popularity: string;
};

interface CarouselImage {
Expand Down

0 comments on commit cffe2d2

Please sign in to comment.