diff --git a/src/components/CharacterCardSmall.css b/src/components/CharacterCardSmall.css index b5c2fbf..c91cd7a 100644 --- a/src/components/CharacterCardSmall.css +++ b/src/components/CharacterCardSmall.css @@ -87,6 +87,10 @@ cursor: pointer; } +.star-favorite { + filter: brightness(0) saturate(100%) invert(84%) sepia(85%) saturate(829%) hue-rotate(359deg) brightness(105%) contrast(104%); +} + .details-button{ background-color: white; color: black; diff --git a/src/components/CharacterCardSmall.tsx b/src/components/CharacterCardSmall.tsx index 39c7a36..4f4c0df 100644 --- a/src/components/CharacterCardSmall.tsx +++ b/src/components/CharacterCardSmall.tsx @@ -5,11 +5,13 @@ import StarShape from '../assets/star.svg' type CharacterCardSmallProps = { name: string; image: string; + isFavorite: boolean; + onToggleFavorite: () => void; onDetails: () => void; }; -export default function CharacterCardSmall({name, image, onDetails}: CharacterCardSmallProps){ - return( +export default function CharacterCardSmall({ name, image, isFavorite, onToggleFavorite, onDetails }: CharacterCardSmallProps) { + return (
@@ -17,11 +19,11 @@ export default function CharacterCardSmall({name, image, onDetails}: CharacterCa

{name}

-
) -} \ No newline at end of file +} diff --git a/src/pages/CharactersPage.tsx b/src/pages/CharactersPage.tsx index 1204ce6..e4df42c 100644 --- a/src/pages/CharactersPage.tsx +++ b/src/pages/CharactersPage.tsx @@ -7,104 +7,151 @@ import SearchIcon from "../assets/search.svg" import type { Character } from "../types/Character"; -const filters = [ - {title: 'Gender', options: ['Female', 'Male', 'Unknown'] }, - {title: 'Species', options: ['Human', 'Aileen', 'Unknown']}, - {title: 'Status', options: ['Alive', 'Dead', 'Unknown']} +type FilterCategory = "gender" | "species" | "status"; + +const filters: { title: string, key: FilterCategory, options: string[] }[] = [ + { title: 'Gender', key: 'gender', options: ['Female', 'Male', 'Unknown'] }, + { title: 'Species', key: 'species', options: ['Human', 'Alien', 'Unknown'] }, + { title: 'Status', key: 'status', options: ['Alive', 'Dead', 'Unknown'] } ]; -export default function CharactersPage(){ +export default function CharactersPage() { const [characters, setCharacters] = useState([]); const [selectedCharacter, setSelectedCharacter] = useState(null); - + const [searchQuery, setsearchQuery] = useState(""); + const [selectedFilters, setSelectedFilters] = useState>({ + gender: [], + species: [], + status: [] + }); + const [favoriteCharacterIds, setFavoriteCharacterIds] = useState([]); + useEffect(() => { - async function fetchCharacters(){ - try{ + async function fetchCharacters() { + try { const response = await fetch("https://rickandmortyapi.com/api/character"); - + if (!response.ok) { throw new Error(`Could not fetch characters: ${response.status}`) } - + const data = await response.json(); setCharacters(data.results); - } - catch(error) { + } + catch (error) { console.error(error); } } fetchCharacters() }, []) - function showPreviousCharacter(){ + const filteredCharacters = characters.filter((character) => { + const matchesSearch = character.name.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesFilters = filters.every((filter) => { + const selectedOptions = selectedFilters[filter.key]; + + if (selectedOptions.length === 0) { + return true; + } + + return selectedOptions.includes(character[filter.key].toLowerCase()); + }); + + return matchesSearch && matchesFilters; + }); + + function handleFilterChange(category: FilterCategory, value: string, checked: boolean) { + setSelectedFilters((previousFilters) => ({ + ...previousFilters, + [category]: checked + ? [...previousFilters[category], value] + : previousFilters[category].filter((selectedValue) => selectedValue !== value) + })); + } + + function handleToggleFavorite(characterId: number) { + setFavoriteCharacterIds((previousFavorites) => + previousFavorites.includes(characterId) + ? previousFavorites.filter((id) => id !== characterId) + : [...previousFavorites, characterId] + ); + } + + function showPreviousCharacter() { if (!selectedCharacter) return; - const currentIndex = characters.findIndex( + const currentIndex = filteredCharacters.findIndex( (character) => character.id === selectedCharacter.id ); + if (currentIndex === -1) return; + const previousIndex = - currentIndex === 0 ? characters.length - 1 : currentIndex - 1; - - setSelectedCharacter(characters[previousIndex]); + currentIndex === 0 ? filteredCharacters.length - 1 : currentIndex - 1; + + setSelectedCharacter(filteredCharacters[previousIndex]); } - function showNextCharacter(){ + function showNextCharacter() { if (!selectedCharacter) return; - const currentIndex = characters.findIndex( + const currentIndex = filteredCharacters.findIndex( (character) => character.id === selectedCharacter.id ); + if (currentIndex === -1) return; + const nextIndex = - currentIndex === characters.length - 1 ? 0 : currentIndex + 1; + currentIndex === filteredCharacters.length - 1 ? 0 : currentIndex + 1; - setSelectedCharacter(characters[nextIndex]); + setSelectedCharacter(filteredCharacters[nextIndex]); } - return( -
-
-
- - -
-
- {characters.map((character) => ( - setSelectedCharacter(character)} - /> - ))} -
+ return ( +
+
+
event.preventDefault()}> + setsearchQuery(event.target.value)} /> + +
+
+ {filteredCharacters.map((character) => ( + handleToggleFavorite(character.id)} + onDetails={() => setSelectedCharacter(character)} + /> + ))}
-
- -
- {selectedCharacter && ( - setSelectedCharacter(null)} - onPrevious={showPreviousCharacter} - onNext={showNextCharacter} - /> - )} -
+
+
+ +
+ {selectedCharacter && ( + setSelectedCharacter(null)} + onPrevious={showPreviousCharacter} + onNext={showNextCharacter} + /> + )} +
) -} \ No newline at end of file +}