Bruno Predot

Dans la pade détails, ajout d'un computed pour récupération dyynanique de l'id c…

…ontenu dans la route + d'un computed pour récupérer le film concerné dans le store.
Mise à jour du Model Movie + de linterface.
Ajout du fetch des détails.
Ajout du contenu principal du film dans le template.
export interface MovieInterface {
id: number;
adult: boolean;
backdrop_pat: string;
backdrop_path: string;
genre_ids: number[];
genres: Genre[];
original_language: string;
original_title: string;
overview: string;
popularity: number;
poster_path: string | null;
release_date: string;
runtime: number
title: string;
video: boolean;
vote_average: number;
vote_count: number;
}
type Genre = {
id: number,
name: string,
}
\ No newline at end of file
... ...
... ... @@ -22,7 +22,7 @@ export class Movie extends Model {
// Attributs.
id: this.number(null),
adult: this.boolean(false),
backdrop_pat: this.string(null),
backdrop_path: this.string(null),
belongs_to_collection: this.attr(null),
budget: this.number(null),
genre_ids: this.attr([]),
... ...
<script setup lang="ts">
import { ArrowLeftIcon } from "lucide-vue-next";
//#region --import--.
import { ArrowLeftIcon, FilmIcon } from "lucide-vue-next";
import { useTMDB } from "~/composables/tMDB";
import { onMounted } from "vue";
import { Movie } from "~/models/movie";
import type { MovieInterface } from "~/interfaces/movie";
//#endregion
//#region --Declaration--.
const { fetchPopularMovies, searchMovies, fetchMovieDetails } = useTMDB();
//#endregion
//#region --Declaration--.
const { currentRoute } = useRouter();
//#endregion
//#region --Computed--.
const movieId = computed(() => {
if (currentRoute.value.params.id) {
if (typeof currentRoute.value.params.id === 'string') {
if (typeof Number(+currentRoute.value.params.id) === "number") {
return +currentRoute.value.params.id as number;
} else {
return currentRoute.value.params.id as string;
}
} else {
return null;
}
} else {
return null;
}
});
const movie = computed(() => {
if (unref(movieId)) {
// Todo : revoir ici.
return useRepo(Movie).query().where('id', movieId.value).withAll().first() as unknown as MovieInterface;
} else {
return null;
}
});
//#endregion
//#region --Function--.
/**
* Fetch movie details
*/
const fetchDetails = async (id: number|string) => {
try {
// isLoading.value = true
const data = await fetchMovieDetails(id);
console.log('data', data)
// Add to store collection.
useRepo(Movie).save(data);
} catch (error) {
console.error('Error fetching movie details:', error)
// movie.value = null
} finally {
// isLoading.value = false
}
}
/**
* Format runtime
* @param minutes
*/
const formatRuntime = (minutes: number) => {
if (!minutes) return 'Durée inconnue';
// Find nb hours.
const hours = Math.floor(minutes / 60);
// Find last minutes.
const mins = minutes % 60;
return `${hours}h ${mins}min`;
}
/**
* Format vote count if > 1000.
* @param count
*/
const formatVoteCount = (count: number) => {
if (count >= 1000) {
return `${(count / 1000).toFixed(1)}k votes`
}
return `${count} votes`
}
//#endregion
//#region --Global event--.
onMounted(() => {
// Fetch data on component mount.
if (unref(movieId)) {
const id = unref(movieId) as string|number;
fetchDetails(id)
}
// fetchMovieCredits()
// loadComments()
});
//#endregion
</script>
<template>
<section>
<!-- Skeleton loader pendant le chargement -->
<ui-components-skeleton-movie-detail-loader />
<!-- Contenu du film -->
<div v-if="movie" class="relative">
<!-- Backdrop image -->
<div class="absolute inset-0 h-[500px] overflow-hidden z-0">
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-gray-900"/>
<img
v-if="movie.backdrop_path"
:src="`https://image.tmdb.org/t/p/original${movie.backdrop_path}`"
:alt="movie.title"
class="w-full h-full object-cover opacity-30"
>
</div>
<!-- Contenu principal -->
<div class="container mx-auto px-4 py-8 relative z-10 pt-20">
<button
class="flex items-center text-gray-400 hover:text-white mb-8 transition-colors"
@click="navigateTo('/')"
... ... @@ -14,6 +132,63 @@ import { ArrowLeftIcon } from "lucide-vue-next";
Retour
</button>
<div class="flex flex-col md:flex-row gap-8">
<!-- Poster -->
<div class="w-full md:w-1/3 lg:w-1/4">
<div class="rounded-lg overflow-hidden shadow-lg bg-gray-800">
<img
v-if="movie.poster_path"
:src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
:alt="movie.title"
class="w-full h-auto"
>
<div v-else class="aspect-[2/3] bg-gray-700 flex items-center justify-center">
<FilmIcon :size="64" class="text-gray-500" />
</div>
</div>
</div>
<!-- Informations du film -->
<div class="w-full md:w-2/3 lg:w-3/4">
<h1 class="text-3xl md:text-4xl font-bold mb-2">{{ movie.title }}</h1>
<p v-if="movie.release_date" class="text-gray-400 mb-4">
{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }} • {{ formatRuntime(movie.runtime) }}
</p>
<!-- Note et votes -->
<div class="flex items-center mb-6">
<div class="bg-primary text-white rounded-full w-12 h-12 flex items-center justify-center font-bold mr-3">
{{ movie.vote_average.toFixed(1) }}
</div>
<div>
<div class="font-semibold">Note TMDB</div>
<div class="text-sm text-gray-400">{{ formatVoteCount(movie.vote_count) }}</div>
</div>
</div>
<!-- Genres -->
<div class="mb-6">
<div class="flex flex-wrap gap-2">
<span
v-for="genre in movie.genres"
:key="genre.id"
class="px-3 py-1 bg-gray-800 rounded-full text-sm"
>
{{ genre.name }}
</span>
</div>
</div>
<!-- Synopsis -->
<div class="mb-6">
<h2 class="text-xl font-bold mb-2">Synopsis</h2>
<p class="text-gray-300">{{ movie.overview || 'Aucun synopsis disponible.' }}</p>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
... ...