index.vue 6.12 KB
<script lang="ts" setup>
//#region --import--.
import { ArrowLeftIcon, FilmIcon } from "lucide-vue-next";
import { useTMDB } from "~/composables/tMDB";
import { computed, onMounted, ref } from "vue";
import { Movie } from "~/models/movie";
import type { MovieInterface } from "~/interfaces/movie";
import { Credit } from "~/models/credit";
import type { CreditsResponse } from "~/interfaces/credit";
//#endregion

//#region --Declaration--.
const { fetchMovieDetails, fetchMovieCredits } = useTMDB();
//#endregion

//#region --Declaration--.
const { currentRoute } = useRouter();
//#endregion

//#region --Data/ref--.
const isLoading = ref(true);
//#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;
  }
});

/**
 * Computed property for director
 */
const director = computed(() => {
  if (unref(movie)?.credit?.crew) {
    return movie.value?.credit.crew.find((person) => person.job === "Director");
  } else {
    return null;
  }
});
//#endregion

//#region --Function--.
/**
 * Fetch movie details
 */
const fetchDetails = async (id: number | string) => {
  try {
    isLoading.value = true;

    const data = await fetchMovieDetails(id);
    // Add to store collection.
    useRepo(Movie).save(data);
  } catch (error) {
    console.error("Error fetching movie details:", error);
  } 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`;
};

async function fetchCredits(id: number | string) {
  try {
    const data = (await fetchMovieCredits(id)) as CreditsResponse;
    data.movie_id = id;
    // Add to store collection.
    useRepo(Credit).save(data);
  } catch (error) {
    console.error("Error fetching movie credits:", error);
  }
}

//#endregion

//#region --Global event--.
onMounted(() => {
  // Fetch data on component mount.
  if (unref(movieId)) {
    const id = unref(movieId) as string | number;
    fetchDetails(id);
    fetchCredits(id);
  }
  // loadComments()
});
//#endregion
</script>

<template>
  <section>
    <!-- Skeleton loader pendant le chargement -->
    <ui-components-skeleton-movie-detail-loader v-if="isLoading" />

    <!-- Contenu du film -->
    <div v-else-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"
          :alt="movie.title"
          :src="`https://image.tmdb.org/t/p/original${movie.backdrop_path}`"
          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('/')"
        >
          <ArrowLeftIcon :size="20" class="mr-2" />
          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"
                :alt="movie.title"
                :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
                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 -->
            <details-score-and-vote :nb-vote="movie.vote_count" :score="movie.vote_average" />

            <!-- 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>

            <!-- Réalisateur et têtes d'affiche -->
            <div v-if="movie.credit" class="mb-6">
              <h2 class="text-xl font-bold mb-2">Équipe</h2>
              <div v-if="director" class="mb-2">
                <span class="font-semibold">Réalisateur:</span> {{ director.name }}
              </div>
              <div v-if="movie.credit.cast.length > 0">
                <span class="font-semibold">Têtes d'affiche:</span>
                {{
                  movie.credit.cast
                    .slice(0, 5)
                    .map((person) => person.name)
                    .join(", ")
                }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<style scoped></style>