feat: simplify MovieList component by consolidating filters and adding new Label component for enhanced filtering options
This commit is contained in:
		
							parent
							
								
									64bb4cd4e4
								
							
						
					
					
						commit
						6bd1b289d7
					
				|  | @ -5,19 +5,7 @@ export default async function Home() { | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <TrackedMovies /> |       <TrackedMovies /> | ||||||
|       <MovieList |       <MovieList heading="Moja lista" /> | ||||||
|         heading="Nadchodzące" |  | ||||||
|         filterUpcoming={1} |  | ||||||
|         sortDirection="desc" |  | ||||||
|       /> |  | ||||||
|       <MovieList |  | ||||||
|         heading="Do obejrzenia" |  | ||||||
|         filterReleased={1} |  | ||||||
|         filterSeen={0} |  | ||||||
|         filterFavorites={0} |  | ||||||
|       /> |  | ||||||
|       <MovieList heading="Obejrzane" filterSeen={1} filterFavorites={0} /> |  | ||||||
|       <MovieList heading="Ulubione" filterFavorites={1} filterSeen={1} /> |  | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| import { useOutsideClick } from "@/hooks/useOutsideClick"; | import { useOutsideClick } from "@/hooks/useOutsideClick"; | ||||||
| import { FC, useEffect, useRef, useState } from "react"; | import { FC, useEffect, useRef, useState } from "react"; | ||||||
| import { FaFilter } from "react-icons/fa"; | import { FaFilter } from "react-icons/fa"; | ||||||
|  | import { Button } from "../Button"; | ||||||
| 
 | 
 | ||||||
| type Props = { | type Props = { | ||||||
|   items: { |   items: { | ||||||
|  | @ -25,15 +26,12 @@ export const Dropdown: FC<Props> = ({ items, defaultValue, callback }) => { | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div ref={ref} className="relative inline-block"> |     <div ref={ref} className="relative inline-block"> | ||||||
|       <button |       <Button theme="glass" size="icon" onClick={() => setIsOpen(!isOpen)}> | ||||||
|         onClick={() => setIsOpen(!isOpen)} |  | ||||||
|         className="relative z-10 block p-2 shadow-sm cursor-pointer bg-white text-primary rounded-md" |  | ||||||
|       > |  | ||||||
|         <FaFilter /> |         <FaFilter /> | ||||||
|       </button> |       </Button> | ||||||
| 
 | 
 | ||||||
|       <div |       <div | ||||||
|         className="absolute left-0 z-20 w-48 py-2 mt-2 origin-top-right bg-white rounded-md shadow-xl dark:bg-gray-800" |         className="absolute right-0 z-20 w-48 py-2 mt-2 origin-top-right bg-gradient-to-r from-slate-800 via-slate-800/80 to-slate-900 border border-slate-700/30 shadow-lg rounded-md" | ||||||
|         style={{ |         style={{ | ||||||
|           opacity: isOpen ? 1 : 0, |           opacity: isOpen ? 1 : 0, | ||||||
|           pointerEvents: isOpen ? "auto" : "none", |           pointerEvents: isOpen ? "auto" : "none", | ||||||
|  | @ -45,8 +43,8 @@ export const Dropdown: FC<Props> = ({ items, defaultValue, callback }) => { | ||||||
|             onClick={() => { |             onClick={() => { | ||||||
|               setValue(item.value); |               setValue(item.value); | ||||||
|             }} |             }} | ||||||
|             className={`block px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-300 transform  hover:bg-primary-50 cursor-pointer ${ |             className={`block px-4 py-3 text-sm text-slate-400 capitalize transition-colors duration-300 transform  hover:bg-slate-700/80 cursor-pointer ${ | ||||||
|               value === item.value ? "bg-primary-50" : "" |               value === item.value ? "bg-slate-700/80" : "" | ||||||
|             }`}
 |             }`}
 | ||||||
|           > |           > | ||||||
|             {item.label} |             {item.label} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | import { FC, HTMLAttributes } from "react"; | ||||||
|  | 
 | ||||||
|  | type Props = HTMLAttributes<HTMLDivElement> & { | ||||||
|  |   active?: boolean; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const Label: FC<Props> = ({ children, className, active, ...props }) => { | ||||||
|  |   return ( | ||||||
|  |     <div | ||||||
|  |       className={`flex items-center gap-2 outline-slate-500 px-3 py-2 rounded-lg bg-slate-800/80 hover:bg-slate-700/80 transition-colors cursor-pointer group ${className} ${ | ||||||
|  |         active ? "outline-2" : "" | ||||||
|  |       }`}
 | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       <span className="text-sm text-slate-400 group-hover:text-slate-200 transition-colors"> | ||||||
|  |         {children} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | @ -6,6 +6,7 @@ import { useGlobalStore } from "@/app/store/globalStore"; | ||||||
| import { Dropdown } from "@/components/atoms/Dropdown"; | import { Dropdown } from "@/components/atoms/Dropdown"; | ||||||
| import { useAutoAnimate } from "@formkit/auto-animate/react"; | import { useAutoAnimate } from "@formkit/auto-animate/react"; | ||||||
| import { Button } from "@/components/atoms/Button"; | import { Button } from "@/components/atoms/Button"; | ||||||
|  | import { Label } from "@/components/atoms/Label"; | ||||||
| 
 | 
 | ||||||
| type Props = { | type Props = { | ||||||
|   heading?: string; |   heading?: string; | ||||||
|  | @ -14,13 +15,14 @@ type Props = { | ||||||
| 
 | 
 | ||||||
|   overrideMovies?: Movie[]; |   overrideMovies?: Movie[]; | ||||||
| 
 | 
 | ||||||
|  |   showFilters?: boolean; | ||||||
|   filterSeen?: 0 | 1; |   filterSeen?: 0 | 1; | ||||||
|   filterFavorites?: 0 | 1; |   filterFavorites?: 0 | 1; | ||||||
|   filterUpcoming?: 0 | 1; |   filterUpcoming?: 0 | 1; | ||||||
|   filterReleased?: 0 | 1; |   filterReleased?: 0 | 1; | ||||||
| 
 | 
 | ||||||
|   fluid?: boolean; |   fluid?: boolean; | ||||||
|   showFilters?: boolean; |   showSorting?: boolean; | ||||||
|   sort?: "title" | "releaseDate" | "popularity"; |   sort?: "title" | "releaseDate" | "popularity"; | ||||||
|   sortDirection?: "asc" | "desc"; |   sortDirection?: "asc" | "desc"; | ||||||
| 
 | 
 | ||||||
|  | @ -32,54 +34,62 @@ export const MovieList: FC<Props> = ({ | ||||||
|   icon, |   icon, | ||||||
|   colors = "white", |   colors = "white", | ||||||
|   overrideMovies, |   overrideMovies, | ||||||
|   filterSeen, |  | ||||||
|   filterFavorites, |  | ||||||
|   filterUpcoming, |  | ||||||
|   filterReleased, |  | ||||||
|   fluid = false, |  | ||||||
|   showFilters = true, |   showFilters = true, | ||||||
|   sort = "releaseDate", |   filterSeen: filterSeenInitial, | ||||||
|  |   filterFavorites: filterFavoritesInitial, | ||||||
|  |   filterUpcoming: filterUpcomingInitial, | ||||||
|  |   filterReleased: filterReleasedInitial, | ||||||
|  |   fluid = false, | ||||||
|  |   showSorting = true, | ||||||
|  |   sort: sortType = "releaseDate", | ||||||
|   sortDirection = "asc", |   sortDirection = "asc", | ||||||
|   loadMore = false, |   loadMore = false, | ||||||
| }) => { | }) => { | ||||||
|   const { movies: storeMovies } = useGlobalStore(); |   const { movies: storeMovies } = useGlobalStore(); | ||||||
|   const movies = overrideMovies || storeMovies; |   const movies = overrideMovies || storeMovies; | ||||||
| 
 | 
 | ||||||
|   const [filter, setFilter] = useState<"title" | "releaseDate" | "popularity">( |   const [filter, setFilter] = useState({ | ||||||
|     sort |     seen: filterSeenInitial, | ||||||
|  |     favorites: filterFavoritesInitial, | ||||||
|  |     upcoming: filterUpcomingInitial, | ||||||
|  |     released: filterReleasedInitial, | ||||||
|  |   }); | ||||||
|  |   const [sort, setSort] = useState<"title" | "releaseDate" | "popularity">( | ||||||
|  |     sortType | ||||||
|   ); |   ); | ||||||
|  | 
 | ||||||
|   const [loaded, setLoaded] = useState(loadMore ? 4 : movies.length); |   const [loaded, setLoaded] = useState(loadMore ? 4 : movies.length); | ||||||
|   const [parent] = useAutoAnimate(); |   const [parent] = useAutoAnimate(); | ||||||
| 
 | 
 | ||||||
|   const filteredMovies = movies.filter((movie) => { |   const filteredMovies = movies.filter((movie) => { | ||||||
|     let result = true; |     let result = true; | ||||||
|     const today = new Date(); |     const today = new Date(); | ||||||
|     if (filterSeen !== undefined) { |     if (filter.seen !== undefined) { | ||||||
|       result = movie.seen === !!filterSeen; |       result = movie.seen === !!filter.seen; | ||||||
|     } |     } | ||||||
|     if (filterFavorites !== undefined) { |     if (filter.favorites !== undefined) { | ||||||
|       result = result && movie.favorite === !!filterFavorites; |       result = result && movie.favorite === !!filter.favorites; | ||||||
|     } |     } | ||||||
|     if (filterUpcoming !== undefined) { |     if (filter.upcoming !== undefined) { | ||||||
|       const releaseDate = new Date(movie.release_date); |       const releaseDate = new Date(movie.release_date); | ||||||
|       result = |       result = | ||||||
|         result && filterUpcoming ? releaseDate > today : releaseDate < today; |         result && filter.upcoming ? releaseDate > today : releaseDate < today; | ||||||
|     } |     } | ||||||
|     if (filterReleased !== undefined) { |     if (filter.released !== undefined) { | ||||||
|       const releaseDate = new Date(movie.release_date); |       const releaseDate = new Date(movie.release_date); | ||||||
|       result = |       result = | ||||||
|         result && filterReleased ? releaseDate < today : releaseDate > today; |         result && filter.released ? releaseDate < today : releaseDate > today; | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   let sortedMovies = filteredMovies.sort((a, b) => { |   let sortedMovies = filteredMovies.sort((a, b) => { | ||||||
|     if (filter === "title") return a.title.localeCompare(b.title); |     if (sort === "title") return a.title.localeCompare(b.title); | ||||||
|     if (filter === "releaseDate") |     if (sort === "releaseDate") | ||||||
|       return ( |       return ( | ||||||
|         new Date(b.release_date).getTime() - new Date(a.release_date).getTime() |         new Date(b.release_date).getTime() - new Date(a.release_date).getTime() | ||||||
|       ); |       ); | ||||||
|     if (filter === "popularity") return b.popularity - a.popularity; |     if (sort === "popularity") return b.popularity - a.popularity; | ||||||
|     return 0; |     return 0; | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  | @ -89,6 +99,16 @@ export const MovieList: FC<Props> = ({ | ||||||
|     sortedMovies = sortedMovies.reverse(); |     sortedMovies = sortedMovies.reverse(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const handleFilter = (key?: keyof typeof filter) => { | ||||||
|  |     setFilter({ | ||||||
|  |       seen: filterSeenInitial, | ||||||
|  |       favorites: filterFavoritesInitial, | ||||||
|  |       upcoming: filterUpcomingInitial, | ||||||
|  |       released: filterReleasedInitial, | ||||||
|  |       ...(key && { [key]: 1 }), | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <section className="blocks"> |     <section className="blocks"> | ||||||
|       <div className={`${fluid ? "max-w-full px-4" : "container"}`}> |       <div className={`${fluid ? "max-w-full px-4" : "container"}`}> | ||||||
|  | @ -101,22 +121,76 @@ export const MovieList: FC<Props> = ({ | ||||||
|                 {icon} |                 {icon} | ||||||
|               </div> |               </div> | ||||||
|             )} |             )} | ||||||
|             {showFilters && !icon && ( |             <h2 | ||||||
|  |               className={`text-3xl font-bold ${colorsMap[colors]} bg-clip-text text-transparent text-center sm:text-left`} | ||||||
|  |             > | ||||||
|  |               {heading} | ||||||
|  |             </h2> | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |         {showFilters && ( | ||||||
|  |           <div className="flex flex-wrap gap-4 mt-6 p-4 rounded-xl bg-gradient-to-br from-slate-800/50 via-slate-800/30 to-slate-900/50 border border-slate-700/30 shadow-lg"> | ||||||
|  |             <Label | ||||||
|  |               onClick={() => handleFilter()} | ||||||
|  |               active={ | ||||||
|  |                 filter.seen === filterSeenInitial && | ||||||
|  |                 filter.favorites === filterFavoritesInitial && | ||||||
|  |                 filter.upcoming === filterUpcomingInitial && | ||||||
|  |                 filter.released === filterReleasedInitial | ||||||
|  |               } | ||||||
|  |             > | ||||||
|  |               Wszystkie ({movies.length}) | ||||||
|  |             </Label> | ||||||
|  |             <Label | ||||||
|  |               active={filter.favorites !== undefined} | ||||||
|  |               onClick={() => handleFilter("favorites")} | ||||||
|  |             > | ||||||
|  |               Ulubione ({movies.filter((movie) => movie.favorite).length}) | ||||||
|  |             </Label> | ||||||
|  |             <Label | ||||||
|  |               active={filter.seen !== undefined} | ||||||
|  |               onClick={() => handleFilter("seen")} | ||||||
|  |             > | ||||||
|  |               Obejrzane ({movies.filter((movie) => movie.seen).length}) | ||||||
|  |             </Label> | ||||||
|  |             <Label | ||||||
|  |               active={filter.released !== undefined} | ||||||
|  |               onClick={() => handleFilter("released")} | ||||||
|  |             > | ||||||
|  |               W kinach ( | ||||||
|  |               { | ||||||
|  |                 movies.filter( | ||||||
|  |                   (movie) => new Date(movie.release_date) < new Date() | ||||||
|  |                 ).length | ||||||
|  |               } | ||||||
|  |               ) | ||||||
|  |             </Label> | ||||||
|  |             <Label | ||||||
|  |               active={filter.upcoming !== undefined} | ||||||
|  |               onClick={() => handleFilter("upcoming")} | ||||||
|  |             > | ||||||
|  |               Nadchodzące ( | ||||||
|  |               { | ||||||
|  |                 movies.filter( | ||||||
|  |                   (movie) => new Date(movie.release_date) > new Date() | ||||||
|  |                 ).length | ||||||
|  |               } | ||||||
|  |               ) | ||||||
|  |             </Label> | ||||||
|  | 
 | ||||||
|  |             {showSorting && ( | ||||||
|  |               <div className="flex items-center gap-3 ml-auto"> | ||||||
|                 <Dropdown |                 <Dropdown | ||||||
|                   items={[ |                   items={[ | ||||||
|                     { label: "Tytuł", value: "title" }, |                     { label: "Tytuł", value: "title" }, | ||||||
|                     { label: "Data premiery", value: "releaseDate" }, |                     { label: "Data premiery", value: "releaseDate" }, | ||||||
|                     { label: "Popularność", value: "popularity" }, |                     { label: "Popularność", value: "popularity" }, | ||||||
|                   ]} |                   ]} | ||||||
|                 defaultValue={filter} |                   defaultValue={sort} | ||||||
|                 callback={(value) => setFilter(value as "title")} |                   callback={(value) => setSort(value as "title")} | ||||||
|                 /> |                 /> | ||||||
|  |               </div> | ||||||
|             )} |             )} | ||||||
|             <h2 |  | ||||||
|               className={`text-3xl font-bold ${colorsMap[colors]} bg-clip-text text-transparent text-center sm:text-left`} |  | ||||||
|             > |  | ||||||
|               {heading} |  | ||||||
|             </h2> |  | ||||||
|           </div> |           </div> | ||||||
|         )} |         )} | ||||||
|         {filteredMovies.length === 0 && ( |         {filteredMovies.length === 0 && ( | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue