158 lines
6.2 KiB
Vue
158 lines
6.2 KiB
Vue
<template>
|
|
<div class="container mx-auto px-4 py-8">
|
|
<!-- Barre de recherche -->
|
|
<div class="mb-8">
|
|
<div class="flex gap-4">
|
|
<input
|
|
type="text"
|
|
v-model="searchQuery"
|
|
@keyup.enter="performSearch"
|
|
placeholder="Rechercher un manga..."
|
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
|
|
<button
|
|
@click="performSearch"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
|
Rechercher
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- État de chargement -->
|
|
<div v-if="loading" class="text-center py-8">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
|
<p class="mt-4 text-gray-600">Recherche en cours...</p>
|
|
</div>
|
|
|
|
<!-- Message d'erreur -->
|
|
<div v-if="error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-6">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<!-- Résultats de recherche -->
|
|
<div class="max-w-full overflow-hidden">
|
|
<MangaList v-if="searchResults.length > 0" :mangas="searchResults" @manga-click="openMangaModal" />
|
|
<p v-else-if="!loading && searchQuery" class="text-center text-gray-600">Aucun résultat trouvé</p>
|
|
</div>
|
|
|
|
<!-- Modal de confirmation -->
|
|
<Dialog :open="isModalOpen" @close="closeModal" class="relative z-50">
|
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true" />
|
|
|
|
<div class="fixed inset-0 flex items-center justify-center p-4">
|
|
<DialogPanel class="w-full max-w-lg bg-white rounded-xl shadow-xl p-6">
|
|
<DialogTitle class="text-lg mb-4"> Ajouter à la bibliothèque </DialogTitle>
|
|
|
|
<div v-if="selectedManga">
|
|
<div class="flex gap-4">
|
|
<img
|
|
:src="selectedManga.imageUrl || '/placeholder-cover.png'"
|
|
:alt="selectedManga.title"
|
|
class="h-48 w-32 object-cover" />
|
|
<div class="flex-1 min-w-0">
|
|
<h4 class="text-lg">{{ selectedManga.title }}</h4>
|
|
<p class="mt-2">
|
|
{{ truncatedDescription }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 flex justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
@click="closeModal"
|
|
class="px-4 py-2 rounded-lg border border-gray-300 hover:bg-gray-50">
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="button"
|
|
@click="addManga"
|
|
:disabled="adding"
|
|
class="px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center">
|
|
<span v-if="adding" class="mr-2">
|
|
<ArrowPathIcon class="h-5 w-5 animate-spin" />
|
|
</span>
|
|
{{ adding ? 'Ajout en cours...' : 'Ajouter' }}
|
|
</button>
|
|
</div>
|
|
</DialogPanel>
|
|
</div>
|
|
</Dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed, onBeforeUnmount } from 'vue';
|
|
import { useRouter, useRoute } from 'vue-router';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useMangaStore } from '../../application/store/mangaStore';
|
|
import MangaList from '../components/MangaList.vue';
|
|
import { Dialog, DialogPanel, DialogTitle } from '@headlessui/vue';
|
|
import { ArrowPathIcon } from '@heroicons/vue/24/solid';
|
|
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
const mangaStore = useMangaStore();
|
|
|
|
const searchQuery = ref('');
|
|
const isModalOpen = ref(false);
|
|
const selectedManga = ref(null);
|
|
|
|
// Récupération des états du store
|
|
const { searchResults, loadingSearch: loading, searchError: error, addingManga: adding } = storeToRefs(mangaStore);
|
|
|
|
const truncatedDescription = computed(() => {
|
|
if (!selectedManga.value?.description) return '';
|
|
return selectedManga.value.description.length > 500
|
|
? selectedManga.value.description.slice(0, 500) + '...'
|
|
: selectedManga.value.description;
|
|
});
|
|
|
|
// Effectuer la recherche au chargement si un paramètre q est présent
|
|
onMounted(() => {
|
|
const queryParam = route.query.q;
|
|
if (queryParam) {
|
|
searchQuery.value = queryParam;
|
|
performSearch();
|
|
}
|
|
});
|
|
|
|
// Nettoyer la recherche et les résultats lors du démontage du composant
|
|
onBeforeUnmount(() => {
|
|
searchQuery.value = '';
|
|
mangaStore.clearSearchResults();
|
|
});
|
|
|
|
const performSearch = async () => {
|
|
if (!searchQuery.value.trim()) return;
|
|
try {
|
|
await mangaStore.searchMangaDex(searchQuery.value);
|
|
} catch (e) {
|
|
console.error('Erreur de recherche:', e);
|
|
}
|
|
};
|
|
|
|
const openMangaModal = manga => {
|
|
selectedManga.value = manga;
|
|
isModalOpen.value = true;
|
|
};
|
|
|
|
const closeModal = () => {
|
|
isModalOpen.value = false;
|
|
selectedManga.value = null;
|
|
};
|
|
|
|
const addManga = async () => {
|
|
if (!selectedManga.value) return;
|
|
|
|
try {
|
|
await mangaStore.createFromMangaDex(selectedManga.value.externalId);
|
|
router.push('/manga');
|
|
} catch (e) {
|
|
console.error("Erreur d'ajout:", e);
|
|
} finally {
|
|
closeModal();
|
|
}
|
|
};
|
|
</script>
|