|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import React from 'react'; |
4 | | -import { T_RawMediaContentFields } from 'types/media-content'; |
5 | | -import { useLibraryInfiniteScroll } from '@hooks/useLibrary/useLibraryInfiniteScroll'; |
6 | | -import { searchMedia } from 'fetchers/library/searchMedia'; |
7 | | -import { useSearchParams } from 'next/navigation'; |
8 | 3 | import { LibraryInfiniteScrollList } from '@components/library/LibraryInfiniteScrollList'; |
| 4 | +import { useFetch } from '@hooks/use-swr'; |
| 5 | +import { useSearchMediaSWR } from '@hooks/useLibrary/useSearchMediaSWR'; |
| 6 | +import { API_LINKS } from 'app/links'; |
9 | 7 | import { Dropdown } from 'flowbite-react'; |
10 | | -import { useSearchQuery } from '@hooks/useSearchQuery'; |
11 | 8 | import i18next from 'i18next'; |
| 9 | +import { useSearchParams } from 'next/navigation'; |
| 10 | +import { useState } from 'react'; |
| 11 | +import NETWORK_UTILS from 'utils/network'; |
| 12 | +import { SearchSkeleton } from './SearchSkeleton'; |
| 13 | + |
| 14 | +import Link from 'next/link'; |
| 15 | +import { HiOutlineArrowLeft, HiOutlineSearch } from 'react-icons/hi'; |
12 | 16 |
|
13 | | -export function SearchMediaContentList({ initialMediaContent }: { initialMediaContent: T_RawMediaContentFields[] }) { |
14 | | - let params = useSearchParams(); |
15 | | - const { createQueryString, pathname } = useSearchQuery(); |
16 | | - const baseSearchPath = `${pathname}?`; |
| 17 | +const GradePicker = ({ |
| 18 | + grades, |
| 19 | + gradeId, |
| 20 | + setGradeId, |
| 21 | +}: { |
| 22 | + grades: any[]; |
| 23 | + gradeId: string; |
| 24 | + setGradeId: (id: string) => void; |
| 25 | +}) => ( |
| 26 | + <Dropdown |
| 27 | + label={ |
| 28 | + gradeId |
| 29 | + ? (grades instanceof Array && grades.find((g) => g._id === gradeId)?.name) || i18next.t('Select Grade') |
| 30 | + : i18next.t('Select Grade') |
| 31 | + } |
| 32 | + dismissOnClick={true} |
| 33 | + > |
| 34 | + {gradeId && <Dropdown.Item onClick={() => setGradeId('')}>{i18next.t('All Grades')}</Dropdown.Item>} |
| 35 | + {grades instanceof Array && |
| 36 | + grades.map((grade) => ( |
| 37 | + <Dropdown.Item |
| 38 | + key={grade._id} |
| 39 | + onClick={() => { |
| 40 | + setGradeId(grade._id); |
| 41 | + }} |
| 42 | + > |
| 43 | + {grade.name} |
| 44 | + </Dropdown.Item> |
| 45 | + ))} |
| 46 | + </Dropdown> |
| 47 | +); |
| 48 | + |
| 49 | +export function SearchMediaContentList() { |
| 50 | + const params = useSearchParams(); |
| 51 | + const [gradeId, setGradeId] = useState(''); |
17 | 52 | const searchTerm = params.get('q') || ''; |
18 | 53 | const sortBy = params.get('sort_by') || ''; |
| 54 | + const query = { limit: '50', skip: '0', withMetaData: 'true' }; |
| 55 | + const { data } = useFetch(`${API_LINKS.FETCH_GRADES}${NETWORK_UTILS.formatGetParams(query)}`); |
| 56 | + |
| 57 | + const grades = data?.grades; |
19 | 58 |
|
20 | | - let { error, hasMore, loadMore, mediaContent } = useLibraryInfiniteScroll(initialMediaContent, async (offset) => |
21 | | - searchMedia(offset, searchTerm, sortBy), |
| 59 | + const { mediaContent, error, hasMore, loadMore, isLoadingInitialData, isValidating } = useSearchMediaSWR( |
| 60 | + searchTerm, |
| 61 | + sortBy, |
| 62 | + gradeId, |
22 | 63 | ); |
23 | 64 |
|
| 65 | + if (isLoadingInitialData || isValidating) { |
| 66 | + return <SearchSkeleton />; |
| 67 | + } |
| 68 | + |
| 69 | + if (error) { |
| 70 | + return ( |
| 71 | + <div className="mt-6 p-4 text-center"> |
| 72 | + <div className="flex justify-end mx-3 mb-4"> |
| 73 | + <GradePicker grades={grades || []} gradeId={gradeId} setGradeId={setGradeId} /> |
| 74 | + </div> |
| 75 | + <div className="bg-red-50 border border-red-200 rounded-lg p-6 inline-block mx-auto"> |
| 76 | + <h3 className="text-red-700 text-lg font-medium mb-2">Error loading results</h3> |
| 77 | + <p className="text-red-600">There was a problem loading search results. Please try again later.</p> |
| 78 | + </div> |
| 79 | + </div> |
| 80 | + ); |
| 81 | + } |
| 82 | + |
| 83 | + if (!isLoadingInitialData && (!mediaContent || mediaContent.length === 0)) { |
| 84 | + return ( |
| 85 | + <div> |
| 86 | + <div className="flex justify-end mx-3 mt-6 mb-4"> |
| 87 | + <GradePicker grades={grades || []} gradeId={gradeId} setGradeId={setGradeId} /> |
| 88 | + </div> |
| 89 | + <div className="flex flex-col items-center justify-center py-8 px-4"> |
| 90 | + <div className=" p-6 rounded-full mb-6"> |
| 91 | + <HiOutlineSearch className="w-16 h-16 text-gray-400" /> |
| 92 | + </div> |
| 93 | + |
| 94 | + <h2 className="text-2xl font-bold mb-2">No results found</h2> |
| 95 | + |
| 96 | + <p className="text-gray-400 mb-6 max-w-md text-center"> |
| 97 | + We couldn‘t find any content matching "<span className="font-semibold">{searchTerm}</span>" |
| 98 | + {gradeId && grades instanceof Array && ' for grade ' + grades.find((g) => g._id === gradeId)?.name}. Try |
| 99 | + selecting a different grade or modifying your search terms. |
| 100 | + </p> |
| 101 | + |
| 102 | + <div className="flex flex-col sm:flex-row gap-4 mt-2"> |
| 103 | + <Link |
| 104 | + href="/library" |
| 105 | + className="flex items-center justify-center gap-2 px-6 py-2 bg-teal-600 text-white rounded-md hover:bg-teal-700 transition-colors" |
| 106 | + > |
| 107 | + <HiOutlineArrowLeft className="w-5 h-5" /> |
| 108 | + Back to Library |
| 109 | + </Link> |
| 110 | + </div> |
| 111 | + </div> |
| 112 | + </div> |
| 113 | + ); |
| 114 | + } |
| 115 | + |
24 | 116 | return ( |
25 | | - <div className="mt-6 search-results" > |
26 | | - <div className="flex justify-between items-center mx-3 flex-wrap gap-3"> |
27 | | - <p> |
28 | | - Showing {Number(mediaContent?.length)} results for <b>{searchTerm}</b> |
29 | | - </p> |
30 | | - |
31 | | - <div className="mr-3"> |
32 | | - <Dropdown label={sortBy ? i18next.t(sortBy) : i18next.t('sort_by')} dismissOnClick={true}> |
33 | | - <Dropdown.Item href={`${baseSearchPath}${createQueryString('sort_by', i18next.t('most_viewed'))}`}> |
34 | | - {i18next.t('most_viewed')} |
35 | | - </Dropdown.Item> |
36 | | - <Dropdown.Item href={`${baseSearchPath}${createQueryString('sort_by', i18next.t('newest'))}`}> |
37 | | - {i18next.t('newest')} |
38 | | - </Dropdown.Item> |
39 | | - </Dropdown> |
| 117 | + <div className="mt-6 search-results"> |
| 118 | + <div className="flex justify-between items-center mx-3 mb-6"> |
| 119 | + <div className="flex-1 flex justify-center items-center gap-2"> |
| 120 | + <p className="text-center"> |
| 121 | + <span className="font-medium">{mediaContent?.length || 0}</span> results found for{' '} |
| 122 | + <span className="font-medium">"{searchTerm}"</span> |
| 123 | + </p> |
| 124 | + </div> |
| 125 | + |
| 126 | + <div className="flex gap-3"> |
| 127 | + <GradePicker grades={grades || []} gradeId={gradeId} setGradeId={setGradeId} /> |
40 | 128 | </div> |
41 | 129 | </div> |
42 | 130 |
|
43 | | - <LibraryInfiniteScrollList mediaContent={mediaContent} loadMore={loadMore} hasMore={hasMore} error={error} /> |
| 131 | + <div className="px-3"> |
| 132 | + <LibraryInfiniteScrollList |
| 133 | + mediaContent={mediaContent} |
| 134 | + loadMore={loadMore} |
| 135 | + hasMore={hasMore as boolean} |
| 136 | + error={error} |
| 137 | + /> |
| 138 | + </div> |
44 | 139 | </div> |
45 | 140 | ); |
46 | 141 | } |
0 commit comments