From b1b5177d4ed19e697bf24f210402723e9f51b514 Mon Sep 17 00:00:00 2001 From: "ext.jeremy.guillot@maxicoffee.domains" Date: Sun, 30 Mar 2025 18:06:46 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ajout=20de=20la=20fonctionnalit=C3=A9?= =?UTF-8?q?=20de=20recherche=20et=20d'ajout=20de=20mangas,=20avec=20mise?= =?UTF-8?q?=20=C3=A0=20jour=20du=20store=20pour=20g=C3=A9rer=20les=20?= =?UTF-8?q?=C3=A9tats=20de=20recherche=20et=20d'ajout,=20ainsi=20que=20cr?= =?UTF-8?q?=C3=A9ation=20d'une=20nouvelle=20page=20AddManga=20pour=20l'int?= =?UTF-8?q?erface=20utilisateur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manga/application/store/mangaStore.js | 51 ++++- .../infrastructure/api/apiMangaRepository.js | 192 ++++++++++-------- .../presentation/components/MangaList.vue | 9 +- .../manga/presentation/pages/AddManga.vue | 149 ++++++++++++++ assets/vue/app/router/index.js | 4 +- 5 files changed, 316 insertions(+), 89 deletions(-) create mode 100644 assets/vue/app/domain/manga/presentation/pages/AddManga.vue diff --git a/assets/vue/app/domain/manga/application/store/mangaStore.js b/assets/vue/app/domain/manga/application/store/mangaStore.js index 458b4b5..5f2a782 100644 --- a/assets/vue/app/domain/manga/application/store/mangaStore.js +++ b/assets/vue/app/domain/manga/application/store/mangaStore.js @@ -26,7 +26,16 @@ export const useMangaStore = defineStore('manga', { // --- Selected Manga State --- // Gardé pour savoir quel manga est sélectionné dans l'UI, // mais les données détaillées ne sont plus stockées ici. - currentMangaId: null + currentMangaId: null, + + // --- Search State --- + searchResults: [], + loadingSearch: false, + searchError: null, + + // --- Add Manga State --- + addingManga: false, + addMangaError: null }), getters: { @@ -75,8 +84,44 @@ export const useMangaStore = defineStore('manga', { clearCurrentMangaFocus() { this.currentMangaId = null; - } + }, - // Plus d'actions fetchMangaDetails / fetchMangaChapters ici + // --- Search Actions --- + async searchMangaDex(query) { + if (this.loadingSearch) return; + + this.loadingSearch = true; + this.searchError = null; + this.searchResults = []; + + try { + const data = await mangaRepository.searchMangaDex(query); + this.searchResults = data.items || []; + } catch (error) { + this.searchError = error.message; + throw error; + } finally { + this.loadingSearch = false; + } + }, + + // --- Add Manga Actions --- + async createFromMangaDex(externalId) { + if (this.addingManga) return; + + this.addingManga = true; + this.addMangaError = null; + + try { + await mangaRepository.createFromMangaDex(externalId); + // Rafraîchir la collection après l'ajout + await this.loadCollection(); + } catch (error) { + this.addMangaError = error.message; + throw error; + } finally { + this.addingManga = false; + } + } } }); diff --git a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js index 35b75b2..6be0b50 100644 --- a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js +++ b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js @@ -1,90 +1,122 @@ import { MangaCollection } from '../../domain/entities/manga'; export class ApiMangaRepository { - async getCollection() { - try { - const response = await fetch('/api/mangas'); - if (!response.ok) { - throw new Error('Failed to fetch manga collection'); - } - const data = await response.json(); - return new MangaCollection( - data.items, - data.total, - data.page, - data.limit, - data.hasNextPage, - data.hasPreviousPage - ); - } catch (error) { - console.error('API Error:', error); - throw error; - } - } - - async getMangaById(id) { - try { - const response = await fetch(`/api/mangas/by-id/${id}`); - if (!response.ok) { - throw new Error('Failed to fetch manga details'); - } - return await response.json(); - } catch (error) { - console.error('API Error:', error); - throw error; - } - } - - async getChapters(mangaId) { - try { - let allChapters = []; - let page = 1; - let hasMore = true; - - while (hasMore) { - const response = await fetch(`/api/mangas/${mangaId}/chapters?limit=500&page=${page}`); - if (!response.ok) { - throw new Error('Failed to fetch manga chapters'); + async getCollection() { + try { + const response = await fetch('/api/mangas'); + if (!response.ok) { + throw new Error('Failed to fetch manga collection'); + } + const data = await response.json(); + return new MangaCollection( + data.items, + data.total, + data.page, + data.limit, + data.hasNextPage, + data.hasPreviousPage + ); + } catch (error) { + console.error('API Error:', error); + throw error; } - const data = await response.json(); - allChapters = allChapters.concat(data.items); - hasMore = data.hasNextPage; - page++; - } - - return { - items: allChapters, - total: allChapters.length - }; - } catch (error) { - console.error('API Error:', error); - throw error; } - } - async getMangaBySlug(slug) { - try { - const response = await fetch(`/api/mangas/by-slug/${slug}`); - if (!response.ok) { - throw new Error('Failed to fetch manga details'); - } - return await response.json(); - } catch (error) { - console.error('API Error:', error); - throw error; + async getMangaById(id) { + try { + const response = await fetch(`/api/mangas/by-id/${id}`); + if (!response.ok) { + throw new Error('Failed to fetch manga details'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } } - } - async searchMangas(query) { - try { - const response = await fetch(`/api/mangas/search?q=${encodeURIComponent(query)}`); - if (!response.ok) { - throw new Error('Failed to search mangas'); - } - return await response.json(); - } catch (error) { - console.error('API Error:', error); - throw error; + async getChapters(mangaId) { + try { + let allChapters = []; + let page = 1; + let hasMore = true; + + while (hasMore) { + const response = await fetch(`/api/mangas/${mangaId}/chapters?limit=500&page=${page}`); + if (!response.ok) { + throw new Error('Failed to fetch manga chapters'); + } + const data = await response.json(); + allChapters = allChapters.concat(data.items); + hasMore = data.hasNextPage; + page++; + } + + return { + items: allChapters, + total: allChapters.length + }; + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + + async getMangaBySlug(slug) { + try { + const response = await fetch(`/api/mangas/by-slug/${slug}`); + if (!response.ok) { + throw new Error('Failed to fetch manga details'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + + async searchMangas(query) { + try { + const response = await fetch(`/api/mangas/search?q=${encodeURIComponent(query)}`); + if (!response.ok) { + throw new Error('Failed to search mangas'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + + async searchMangaDex(query) { + try { + const response = await fetch(`https://localhost/api/mangadex-search?title=${encodeURIComponent(query)}`); + if (!response.ok) { + throw new Error('Failed to search MangaDex'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + + async createFromMangaDex(externalId) { + try { + const response = await fetch('https://localhost/api/mangas/create-from-mangadex', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ externalId }) + }); + if (!response.ok) { + throw new Error('Failed to create manga from MangaDex'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } } - } } diff --git a/assets/vue/app/domain/manga/presentation/components/MangaList.vue b/assets/vue/app/domain/manga/presentation/components/MangaList.vue index bbcb8c5..0c4b4b9 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaList.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaList.vue @@ -3,7 +3,8 @@
+ class="flex bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg p-4 space-x-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700" + @click="$emit('manga-click', manga)">
@@ -30,7 +31,9 @@ diff --git a/assets/vue/app/router/index.js b/assets/vue/app/router/index.js index 415a626..ba228f1 100644 --- a/assets/vue/app/router/index.js +++ b/assets/vue/app/router/index.js @@ -4,6 +4,7 @@ import HomePage from '../domain/manga/presentation/pages/HomePage.vue'; import MangaDetails from '../domain/manga/presentation/pages/MangaDetails.vue'; import ChapterPage from '../domain/reader/presentation/pages/ChapterPage.vue'; import ActivityPage from '../domain/activity/presentation/pages/ActivityPage.vue'; +import AddManga from '../domain/manga/presentation/pages/AddManga.vue'; // Placeholder component for new routes const PlaceholderComponent = { @@ -44,8 +45,7 @@ const routes = [ { path: '/add', name: 'add-manga', - component: PlaceholderComponent, - props: { title: 'Ajouter un manga' } + component: AddManga }, { path: '/reader/:chapterId',