All checks were successful
Deploy / deploy (push) Successful in 2m46s
feat(conversion): simplifier ConversionPage et brancher les toasts style(manga): réécriture de la liste de résultats dans AddManga chore(task): ajouter tâche conversion CBR→CBZ dans TASK.md
173 lines
7.5 KiB
Vue
173 lines
7.5 KiB
Vue
<template>
|
|
<div class="overflow-y-auto h-full">
|
|
<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 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500" />
|
|
<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 dark:text-gray-400">Recherche en cours...</p>
|
|
</div>
|
|
|
|
<!-- Message d'erreur -->
|
|
<div v-if="error" class="bg-red-100 dark:bg-red-900/20 border border-red-400 dark:border-red-700 text-red-700 dark:text-red-400 px-4 py-3 rounded relative mb-6">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<!-- Résultats de recherche -->
|
|
<div v-if="searchResults.length > 0" class="border-t border-gray-200 dark:border-gray-700">
|
|
<div
|
|
v-for="manga in searchResults"
|
|
:key="manga.externalId"
|
|
class="flex items-center gap-4 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/40 transition-colors border-b border-gray-100 dark:border-gray-700 cursor-pointer"
|
|
@click="openMangaModal(manga)">
|
|
<img
|
|
:src="manga.thumbnailUrl || manga.imageUrl || '/placeholder-cover.png'"
|
|
alt=""
|
|
class="h-36 w-24 object-cover flex-shrink-0 self-start"
|
|
referrerpolicy="no-referrer" />
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">{{ manga.title }}</p>
|
|
<p v-if="manga.description" class="text-sm text-gray-600 dark:text-gray-300 mt-2 line-clamp-4">{{ manga.description }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p v-else-if="!loading && searchQuery" class="text-center text-gray-600 dark:text-gray-400">Aucun résultat trouvé</p>
|
|
|
|
<!-- Modal de confirmation -->
|
|
<Dialog :open="isModalOpen" @close="closeModal" class="relative z-50">
|
|
<div class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-80 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 dark:bg-gray-800 rounded-xl shadow-xl p-6">
|
|
<DialogTitle class="text-lg mb-4 text-gray-900 dark:text-gray-100"> 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 text-gray-900 dark:text-gray-100">{{ selectedManga.title }}</h4>
|
|
<p class="mt-2 text-gray-700 dark:text-gray-300">
|
|
{{ 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 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 dark:bg-gray-800">
|
|
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>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { Dialog, DialogPanel, DialogTitle } from '@headlessui/vue';
|
|
import { ArrowPathIcon } from '@heroicons/vue/24/solid';
|
|
import { storeToRefs } from 'pinia';
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useMangaStore } from '../../application/store/mangaStore';
|
|
|
|
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>
|