Showing
2 changed files
with
43 additions
and
11 deletions
| @@ -6,7 +6,7 @@ import { useTMDB } from "~/composables/tMDB"; | @@ -6,7 +6,7 @@ import { useTMDB } from "~/composables/tMDB"; | ||
| 6 | import { Movie } from "~/models/movie"; | 6 | import { Movie } from "~/models/movie"; |
| 7 | import { FilmIcon } from "lucide-vue-next"; | 7 | import { FilmIcon } from "lucide-vue-next"; |
| 8 | import type { MovieInterface } from "~/interfaces/movie"; | 8 | import type { MovieInterface } from "~/interfaces/movie"; |
| 9 | -import { useDateFormat } from '@vueuse/core' | 9 | +import { useDateFormat } from "@vueuse/core"; |
| 10 | //#endregion | 10 | //#endregion |
| 11 | 11 | ||
| 12 | //#region --Declaration--. | 12 | //#region --Declaration--. |
| @@ -26,7 +26,7 @@ const observer = ref<IntersectionObserver | null>(null); | @@ -26,7 +26,7 @@ const observer = ref<IntersectionObserver | null>(null); | ||
| 26 | 26 | ||
| 27 | //#region --Computed--. | 27 | //#region --Computed--. |
| 28 | const movies = computed(() => { | 28 | const movies = computed(() => { |
| 29 | - return useRepo(Movie).query().orderBy("popularity", 'desc').get() as unknown as MovieInterface[]; | 29 | + return useRepo(Movie).query().orderBy("popularity", "desc").get() as unknown as MovieInterface[]; |
| 30 | }); | 30 | }); |
| 31 | //#endregion | 31 | //#endregion |
| 32 | 32 | ||
| @@ -40,7 +40,11 @@ const fetchMovies = async (page: number) => { | @@ -40,7 +40,11 @@ const fetchMovies = async (page: number) => { | ||
| 40 | isLoadingMore.value = true; | 40 | isLoadingMore.value = true; |
| 41 | const data = await fetchPopularMovies(page); | 41 | const data = await fetchPopularMovies(page); |
| 42 | // Save in Movie model. | 42 | // Save in Movie model. |
| 43 | - isInitialLoading.value ? useRepo(Movie).fresh(data.results) : useRepo(Movie).save(data.results); | 43 | + if (isInitialLoading.value) { |
| 44 | + useRepo(Movie).fresh(data.results); | ||
| 45 | + } else { | ||
| 46 | + useRepo(Movie).save(data.results); | ||
| 47 | + } | ||
| 44 | 48 | ||
| 45 | totalPages.value = data.total_pages; | 49 | totalPages.value = data.total_pages; |
| 46 | currentPage.value = page; | 50 | currentPage.value = page; |
| @@ -63,6 +67,15 @@ function createIntersectionObserver() { | @@ -63,6 +67,15 @@ function createIntersectionObserver() { | ||
| 63 | { threshold: 1.0 }, | 67 | { threshold: 1.0 }, |
| 64 | ); | 68 | ); |
| 65 | } | 69 | } |
| 70 | + | ||
| 71 | +function handleSearchEvent(event: string) { | ||
| 72 | + console.log(event); | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +function handleClearSearchEvent() { | ||
| 76 | + console.log("clear event"); | ||
| 77 | +} | ||
| 78 | + | ||
| 66 | //#endregion | 79 | //#endregion |
| 67 | 80 | ||
| 68 | //#region --Global event--. | 81 | //#region --Global event--. |
| @@ -94,7 +107,11 @@ onBeforeUnmount(() => { | @@ -94,7 +107,11 @@ onBeforeUnmount(() => { | ||
| 94 | <section> | 107 | <section> |
| 95 | <h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1> | 108 | <h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1> |
| 96 | <!-- Barre de recherche --> | 109 | <!-- Barre de recherche --> |
| 97 | - <search-bar placeholder="Rechercher un film..." /> | 110 | + <search-bar |
| 111 | + placeholder="Rechercher un film..." | ||
| 112 | + @event:search="handleSearchEvent" | ||
| 113 | + @event:clear_search="handleClearSearchEvent" | ||
| 114 | + /> | ||
| 98 | <!-- Loading Skeleton --> | 115 | <!-- Loading Skeleton --> |
| 99 | <skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" /> | 116 | <skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" /> |
| 100 | <!-- Liste des films --> | 117 | <!-- Liste des films --> |
| @@ -108,10 +125,10 @@ onBeforeUnmount(() => { | @@ -108,10 +125,10 @@ onBeforeUnmount(() => { | ||
| 108 | <div class="relative pb-[150%]"> | 125 | <div class="relative pb-[150%]"> |
| 109 | <img | 126 | <img |
| 110 | v-if="movie.poster_path" | 127 | v-if="movie.poster_path" |
| 111 | - :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
| 112 | :alt="movie.title" | 128 | :alt="movie.title" |
| 129 | + :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
| 113 | class="absolute inset-0 w-full h-full object-cover" | 130 | class="absolute inset-0 w-full h-full object-cover" |
| 114 | - > | 131 | + /> |
| 115 | <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center"> | 132 | <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center"> |
| 116 | <FilmIcon :size="48" class="text-gray-500" /> | 133 | <FilmIcon :size="48" class="text-gray-500" /> |
| 117 | </div> | 134 | </div> |
| @@ -123,13 +140,13 @@ onBeforeUnmount(() => { | @@ -123,13 +140,13 @@ onBeforeUnmount(() => { | ||
| 123 | </div> | 140 | </div> |
| 124 | <div class="p-4"> | 141 | <div class="p-4"> |
| 125 | <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2> | 142 | <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2> |
| 126 | - <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, 'DD-MM-YYYY') }}</p> | 143 | + <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p> |
| 127 | </div> | 144 | </div> |
| 128 | </div> | 145 | </div> |
| 129 | </div> | 146 | </div> |
| 130 | 147 | ||
| 131 | <!-- Élément observé pour le défilement infini --> | 148 | <!-- Élément observé pour le défilement infini --> |
| 132 | - <div ref="loadMoreTrigger" class="h-10 mt-4"/> | 149 | + <div ref="loadMoreTrigger" class="h-10 mt-4" /> |
| 133 | </section> | 150 | </section> |
| 134 | </template> | 151 | </template> |
| 135 | 152 |
| @@ -2,10 +2,11 @@ | @@ -2,10 +2,11 @@ | ||
| 2 | //#region --import--. | 2 | //#region --import--. |
| 3 | import { SearchIcon, XIcon } from "lucide-vue-next"; | 3 | import { SearchIcon, XIcon } from "lucide-vue-next"; |
| 4 | import { ref } from "vue"; | 4 | import { ref } from "vue"; |
| 5 | +import { useDebounceFn } from "@vueuse/core"; | ||
| 5 | //#endregion | 6 | //#endregion |
| 6 | 7 | ||
| 7 | //#region --Emits--. | 8 | //#region --Emits--. |
| 8 | -// const emit = defineEmits([]); | 9 | +const emit = defineEmits(['event:search', 'event:clear_search']); |
| 9 | //#endregion | 10 | //#endregion |
| 10 | 11 | ||
| 11 | //#region --Props--. | 12 | //#region --Props--. |
| @@ -22,6 +23,20 @@ defineProps({ | @@ -22,6 +23,20 @@ defineProps({ | ||
| 22 | //#region --Data/refs--. | 23 | //#region --Data/refs--. |
| 23 | const searchQuery = ref(""); | 24 | const searchQuery = ref(""); |
| 24 | //#endregion | 25 | //#endregion |
| 26 | + | ||
| 27 | +//#region --Function--. | ||
| 28 | +/** | ||
| 29 | + * Debounced function | ||
| 30 | + */ | ||
| 31 | +const handleSearchEvent = useDebounceFn(() => { | ||
| 32 | + emit('event:search', searchQuery.value); | ||
| 33 | +}, 500); | ||
| 34 | + | ||
| 35 | +function handleClearSearchEvent() { | ||
| 36 | + searchQuery.value = ''; | ||
| 37 | + emit('event:clear_search') | ||
| 38 | +} | ||
| 39 | +//#endregion | ||
| 25 | </script> | 40 | </script> |
| 26 | 41 | ||
| 27 | <template> | 42 | <template> |
| @@ -33,12 +48,12 @@ const searchQuery = ref(""); | @@ -33,12 +48,12 @@ const searchQuery = ref(""); | ||
| 33 | :placeholder="placeholder" | 48 | :placeholder="placeholder" |
| 34 | class="w-full px-4 py-3 bg-gray-800 rounded-full text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary" | 49 | class="w-full px-4 py-3 bg-gray-800 rounded-full text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary" |
| 35 | type="text" | 50 | type="text" |
| 36 | - @input="console.log('debouncedSearch à prévoir')" | 51 | + @input="handleSearchEvent" |
| 37 | /> | 52 | /> |
| 38 | <button | 53 | <button |
| 39 | v-if="searchQuery" | 54 | v-if="searchQuery" |
| 40 | class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white" | 55 | class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white" |
| 41 | - @click="console.log('clearSearch à prévoir')" | 56 | + @click="handleClearSearchEvent" |
| 42 | > | 57 | > |
| 43 | <XIcon :size="20" /> | 58 | <XIcon :size="20" /> |
| 44 | </button> | 59 | </button> |
-
Please register or login to post a comment