From 71242433e66997c57861c3d2d6d1302f937c0eae Mon Sep 17 00:00:00 2001 From: "ext.jeremy.guillot@maxicoffee.domains" Date: Sun, 30 Mar 2025 16:58:05 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20int=C3=A9gration=20de=20@tanstack/vue-q?= =?UTF-8?q?uery=20pour=20la=20gestion=20des=20requ=C3=AAtes=20dans=20l'app?= =?UTF-8?q?lication=20Vue,=20ajout=20de=20nouveaux=20composables=20pour=20?= =?UTF-8?q?les=20chapitres=20et=20les=20d=C3=A9tails=20des=20mangas,=20et?= =?UTF-8?q?=20mise=20=C3=A0=20jour=20du=20store=20pour=20une=20meilleure?= =?UTF-8?q?=20gestion=20des=20=C3=A9tats=20de=20chargement=20et=20d'erreur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manga/application/store/mangaStore.js | 153 +++++++++--------- .../composables/useMangaChapters.js | 23 +++ .../composables/useMangaDetails.js | 31 ++++ .../composables/useMangaVolumes.js | 51 ++++++ .../manga/presentation/pages/HomePage.vue | 7 +- .../manga/presentation/pages/MangaDetails.vue | 94 +++++------ assets/vue/app/index.js | 24 +-- assets/vue/app/shared/plugin/vueQuery.js | 25 +++ package-lock.json | 115 ++++++++++--- package.json | 1 + 10 files changed, 355 insertions(+), 169 deletions(-) create mode 100644 assets/vue/app/domain/manga/presentation/composables/useMangaChapters.js create mode 100644 assets/vue/app/domain/manga/presentation/composables/useMangaDetails.js create mode 100644 assets/vue/app/domain/manga/presentation/composables/useMangaVolumes.js create mode 100644 assets/vue/app/shared/plugin/vueQuery.js diff --git a/assets/vue/app/domain/manga/application/store/mangaStore.js b/assets/vue/app/domain/manga/application/store/mangaStore.js index 7400485..458b4b5 100644 --- a/assets/vue/app/domain/manga/application/store/mangaStore.js +++ b/assets/vue/app/domain/manga/application/store/mangaStore.js @@ -1,81 +1,82 @@ -import {defineStore} from 'pinia'; -import {ApiMangaRepository} from '../../infrastructure/api/apiMangaRepository'; +import { defineStore } from 'pinia'; +import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository'; const mangaRepository = new ApiMangaRepository(); -export const useMangaStore = defineStore('manga', { - state: () => ({ - // État pour la collection - collection: null, - // État pour les détails - currentManga: null, - chapters: [], - loading: false, - error: null, - isBackgroundLoading: false - }), - - actions: { - // Actions pour la collection - async loadCollection() { - if (this.loading) return; - - this.loading = true; - this.error = null; - - try { - this.collection = await mangaRepository.getCollection(); - } catch (err) { - this.error = err.message; - } finally { - this.loading = false; - } - }, - - async refreshCollectionInBackground() { - if (this.isBackgroundLoading) return; - - this.isBackgroundLoading = true; - - try { - this.collection = await mangaRepository.getCollection(); - } catch (err) { - console.error('Failed to refresh collection:', err); - } finally { - this.isBackgroundLoading = false; - } - }, - - // Actions pour les détails du manga - async fetchMangaDetails(mangaId) { - this.loading = true; - this.error = null; - try { - this.currentManga = await mangaRepository.getMangaById(mangaId); - } catch (error) { - this.error = error.message; - } finally { - this.loading = false; - } - }, - - async fetchMangaChapters(mangaId) { - this.loading = true; - this.error = null; - try { - const response = await mangaRepository.getChapters(mangaId); - this.chapters = Array.isArray(response) ? response : - (response.items ? response.items : []); - } catch (error) { - this.error = error.message; - } finally { - this.loading = false; - } - }, - - clearCurrentManga() { - this.currentManga = null; - this.chapters = []; +// Helper pour comparer la collection (peut être supprimé si non utilisé ailleurs) +const deepCompare = (obj1, obj2) => { + try { + if (obj1 == null && obj2 == null) return true; + if (obj1 == null || obj2 == null) return false; + return JSON.stringify(obj1) === JSON.stringify(obj2); + } catch (e) { + console.error("Erreur lors de la comparaison d'objets:", e); + return false; + } +}; + +export const useMangaStore = defineStore('manga', { + state: () => ({ + // --- Collection State --- + collection: null, + loadingCollection: false, + errorCollection: null, + isBackgroundLoadingCollection: false, + + // --- 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 + }), + + getters: { + // Plus de getters spécifiques aux détails/chapitres ici + }, + + actions: { + // --- Collection Actions --- + async loadCollection() { + if (this.loadingCollection) return; + this.loadingCollection = true; + this.errorCollection = null; + try { + const newCollection = await mangaRepository.getCollection(); + // On garde la comparaison pour éviter màj inutile de la collection + if (!deepCompare(this.collection, newCollection)) { + this.collection = newCollection; + } + } catch (err) { + this.errorCollection = err.message; + } finally { + this.loadingCollection = false; + } + }, + + async refreshCollectionInBackground() { + if (this.isBackgroundLoadingCollection) return; + this.isBackgroundLoadingCollection = true; + try { + const newCollection = await mangaRepository.getCollection(); + if (!deepCompare(this.collection, newCollection)) { + this.collection = newCollection; + } + } catch (err) { + console.error('Failed to refresh collection:', err); + } finally { + this.isBackgroundLoadingCollection = false; + } + }, + + // --- Selected Manga Actions --- + setCurrentMangaId(mangaId) { + // Met simplement à jour l'ID sélectionné + this.currentMangaId = mangaId; + }, + + clearCurrentMangaFocus() { + this.currentMangaId = null; + } + + // Plus d'actions fetchMangaDetails / fetchMangaChapters ici } - } }); diff --git a/assets/vue/app/domain/manga/presentation/composables/useMangaChapters.js b/assets/vue/app/domain/manga/presentation/composables/useMangaChapters.js new file mode 100644 index 0000000..5492b3d --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/composables/useMangaChapters.js @@ -0,0 +1,23 @@ +import { computed } from 'vue'; +import { useQuery } from '@tanstack/vue-query'; +import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository'; + +export function useMangaChapters(mangaId) { + const mangaRepository = new ApiMangaRepository(); + + const query = useQuery({ + queryKey: ['manga', mangaId, 'chapters'], + queryFn: async () => { + if (!mangaId.value) { + return Promise.resolve([]); // Retourne un tableau vide si pas d'ID + } + const response = await mangaRepository.getChapters(mangaId.value); + // Assure de toujours retourner un tableau + return Array.isArray(response) ? response : response?.items ?? []; + }, + enabled: computed(() => !!mangaId.value) + }); + + // Retourne le résultat de useQuery (contenant data, isLoading, etc.) + return query; +} diff --git a/assets/vue/app/domain/manga/presentation/composables/useMangaDetails.js b/assets/vue/app/domain/manga/presentation/composables/useMangaDetails.js new file mode 100644 index 0000000..c0f88f7 --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/composables/useMangaDetails.js @@ -0,0 +1,31 @@ +import { computed } from 'vue'; +import { useQuery } from '@tanstack/vue-query'; +import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository'; + +// Accepte un ID de manga (peut être une ref, un computed ref, ou une valeur simple) +export function useMangaDetails(mangaId) { + const mangaRepository = new ApiMangaRepository(); + + // Assure que mangaId est une ref ou une computed pour la réactivité de la queryKey + // Si ce n'est pas déjà le cas, mais généralement on passera une computed( () => route.params.id ) + // const mangaIdRef = computed(() => unref(mangaId)); // unref est utile si on accepte des valeurs simples aussi + + const query = useQuery({ + // La queryKey doit être réactive à mangaId + queryKey: ['manga', mangaId], // mangaId est déjà une computed ref, donc c'est bon + queryFn: () => { + // Vérifier que l'ID a une valeur avant d'appeler l'API + if (!mangaId.value) { + // Retourner null ou undefined si pas d'ID, pour éviter un appel API invalide + // TanStack Query gère aussi l'option 'enabled' pour cela + return Promise.resolve(null); // ou throw new Error("ID manquant"); + } + return mangaRepository.getMangaById(mangaId.value); + }, + // Activer la requête seulement si mangaId a une valeur truthy + enabled: computed(() => !!mangaId.value) + }); + + // Retourne tous les états et données fournis par useQuery + return query; +} diff --git a/assets/vue/app/domain/manga/presentation/composables/useMangaVolumes.js b/assets/vue/app/domain/manga/presentation/composables/useMangaVolumes.js new file mode 100644 index 0000000..dc3fce2 --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/composables/useMangaVolumes.js @@ -0,0 +1,51 @@ +import { computed } from 'vue'; +import { useMangaChapters } from './useMangaChapters'; // Importe le composable des chapitres + +export function useMangaVolumes(mangaId) { + // Utilise le composable des chapitres pour récupérer les données brutes et les états + const { data: rawChaptersData, isLoading, isFetching, error, status } = useMangaChapters(mangaId); + + // Calcule les volumes à partir des données des chapitres + const volumes = computed(() => { + const chaptersArray = rawChaptersData.value || []; // Utilise la data retournée par useMangaChapters + if (chaptersArray.length === 0) return []; + + const volumeMap = new Map(); + chaptersArray.forEach(chapter => { + const volumeNumber = chapter.volume || 'Unknown'; + if (!volumeMap.has(volumeNumber)) { + volumeMap.set(volumeNumber, { + number: volumeNumber, + downloadedChapter: 0, + totalChapter: 0, + chapters: [] + }); + } + const volumeEntry = volumeMap.get(volumeNumber); + volumeEntry.chapters.push({ + ...chapter, + isAvailable: Boolean(chapter.isAvailable) + }); + volumeEntry.totalChapter++; + if (chapter.isAvailable) { + volumeEntry.downloadedChapter++; + } + }); + + return Array.from(volumeMap.values()).sort((a, b) => { + const numA = a.number === 'Unknown' ? -Infinity : Number(a.number); + const numB = b.number === 'Unknown' ? -Infinity : Number(b.number); + return numB - numA; + }); + }); + + // Retourne les volumes calculés et propage les états pertinents de useMangaChapters + return { + volumes, // Les données transformées + isLoading, // L'état de chargement initial des chapitres + isFetching, // L'état de rafraîchissement des chapitres + error, // L'erreur potentielle lors du fetch des chapitres + status // L'état global ('pending', 'error', 'success') + // On pourrait aussi retourner rawChaptersData si nécessaire ailleurs + }; +} diff --git a/assets/vue/app/domain/manga/presentation/pages/HomePage.vue b/assets/vue/app/domain/manga/presentation/pages/HomePage.vue index 7eae228..247fbb6 100644 --- a/assets/vue/app/domain/manga/presentation/pages/HomePage.vue +++ b/assets/vue/app/domain/manga/presentation/pages/HomePage.vue @@ -31,7 +31,12 @@ const router = useRouter(); const mangaStore = useMangaStore(); - const { collection, loading, error, isBackgroundLoading } = storeToRefs(mangaStore); + const { + collection, + loadingCollection: loading, + errorCollection: error, + isBackgroundLoadingCollection: isBackgroundLoading + } = storeToRefs(mangaStore); onMounted(() => { mangaStore.loadCollection(); diff --git a/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue b/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue index d8c2e7d..861b7d6 100644 --- a/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue +++ b/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue @@ -3,84 +3,68 @@
-
- {{ error }} +
+ {{ error.message || 'Une erreur est survenue.' }}
+
+ +
+ +
Aucun manga sélectionné ou trouvé.
diff --git a/assets/vue/app/index.js b/assets/vue/app/index.js index 66e898d..d98b823 100644 --- a/assets/vue/app/index.js +++ b/assets/vue/app/index.js @@ -1,18 +1,18 @@ -import { createApp } from 'vue' -import { createPinia } from 'pinia' -import App from './App.vue' -import { router } from './router' -import '../../styles/app.scss' - +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import App from './App.vue'; +import { router } from './router'; +import '../../styles/app.scss'; +import { installVueQuery } from './shared/plugin/vueQuery'; // Création du store -const pinia = createPinia() +const pinia = createPinia(); // Création de l'application -const app = createApp(App) +const app = createApp(App); // Installation des plugins -app.use(router) -app.use(pinia) - +app.use(router); +app.use(pinia); +app.use(installVueQuery); // Montage de l'application -app.mount('#vue-app') \ No newline at end of file +app.mount('#vue-app'); diff --git a/assets/vue/app/shared/plugin/vueQuery.js b/assets/vue/app/shared/plugin/vueQuery.js new file mode 100644 index 0000000..2317d3d --- /dev/null +++ b/assets/vue/app/shared/plugin/vueQuery.js @@ -0,0 +1,25 @@ +import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query'; + +// Créez une instance de QueryClient +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Options par défaut pour toutes les requêtes (queries) + staleTime: 5 * 60 * 1000, // 5 minutes: temps pendant lequel les données sont considérées "fraîches" (ne déclenche pas de re-fetch en arrière-plan juste en montant le composant) + gcTime: 10 * 60 * 1000, // 10 minutes: temps avant que les données inactives soient supprimées du cache (garbage collected) + retry: 1, // Nombre de tentatives en cas d'erreur + refetchOnWindowFocus: true // Re-fetcher quand la fenêtre reprend le focus (bon pour garder les données à jour) + } + } +}); + +export const vueQueryPluginOptions = { + queryClient // Fournir le client au plugin +}; + +export const installVueQuery = app => { + app.use(VueQueryPlugin, vueQueryPluginOptions); +}; + +// Exportez aussi le client si vous avez besoin d'y accéder directement ailleurs (rare) +export { queryClient }; diff --git a/package-lock.json b/package-lock.json index 98fe511..224383a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@tanstack/vue-query": "^5.71.0", "alpinejs": "^3.13.3", "autoprefixer": "^10.4.14", "axios": "^1.7.9", @@ -420,7 +421,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -477,7 +477,6 @@ "version": "7.26.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz", "integrity": "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.26.8" @@ -1899,7 +1898,6 @@ "version": "7.26.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz", "integrity": "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -3401,6 +3399,32 @@ "node": ">=8" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", + "license": "MIT", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.71.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.71.0.tgz", + "integrity": "sha512-p4+T7CIEe1kMhii4booWiw42nuaiYI9La/bRCNzBaj1P3PDb0dEZYDhc/7oBifKJfHYN+mtS1ynW1qsmzQW7Og==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.13.5", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.5.tgz", @@ -3412,6 +3436,63 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/vue-query": { + "version": "5.71.0", + "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.71.0.tgz", + "integrity": "sha512-/jjuDka26wbuhJi4X7DBbbgp3Fh+ZX/MG4/SKAEQOht3+MKucoEQm2uwUKv739m2td8kvFDNSFysWYiT6J+a5w==", + "license": "MIT", + "dependencies": { + "@tanstack/match-sorter-utils": "^8.19.4", + "@tanstack/query-core": "5.71.0", + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@vue/composition-api": "^1.1.2", + "vue": "^2.6.0 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@tanstack/vue-query/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@tanstack/vue-query/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@tanstack/vue-virtual": { "version": "3.13.5", "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.5.tgz", @@ -3941,7 +4022,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", @@ -3955,14 +4035,12 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-core/node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3975,7 +4053,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.13", @@ -3986,14 +4063,12 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-sfc": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", @@ -4011,14 +4086,12 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-ssr": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.13", @@ -4029,7 +4102,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/devtools-api": { @@ -4080,7 +4152,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.13", @@ -4091,7 +4162,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "dev": true, "license": "MIT", "dependencies": { "@vue/shared": "3.5.13" @@ -4101,14 +4171,12 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/runtime-dom": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.13", @@ -4121,7 +4189,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "dev": true, "license": "MIT", "dependencies": { "@vue/shared": "3.5.13" @@ -4131,14 +4198,12 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/server-renderer": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-ssr": "3.5.13", @@ -4152,7 +4217,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/@vue/shared": { @@ -5908,7 +5972,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/culori": { @@ -6454,7 +6517,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, "license": "MIT" }, "node_modules/esutils": { @@ -8091,7 +8153,6 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -10187,6 +10248,12 @@ "jsesc": "bin/jsesc" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -11762,7 +11829,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.13", @@ -11906,7 +11972,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, "license": "MIT" }, "node_modules/watchpack": { diff --git a/package.json b/package.json index 4b63d2c..05bbbb4 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@tanstack/vue-query": "^5.71.0", "alpinejs": "^3.13.3", "autoprefixer": "^10.4.14", "axios": "^1.7.9",