From 574d3f91793ea213bd00a12982c4cb5430b4dafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20H=C3=A9tru?= Date: Sat, 21 Feb 2026 13:50:39 +0100 Subject: [PATCH 1/4] feat: genealogie page + api for family --- app/components/SearchItem.vue | 2 +- app/pages/genealogie.vue | 142 ++++++++++++++++++++++++++++- app/stores/entites.store.ts | 20 +++- app/types/database.types.ts | 8 ++ server/api/genealogie/[famille].ts | 67 ++++++++++++++ 5 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 server/api/genealogie/[famille].ts diff --git a/app/components/SearchItem.vue b/app/components/SearchItem.vue index 5fb37fe..46ff0f6 100644 --- a/app/components/SearchItem.vue +++ b/app/components/SearchItem.vue @@ -1,6 +1,6 @@ - + + + diff --git a/app/stores/entites.store.ts b/app/stores/entites.store.ts index ae22380..be09c2d 100644 --- a/app/stores/entites.store.ts +++ b/app/stores/entites.store.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import type { Entite } from '~/types/database.types' +import type { Entite, EntiteTree } from '~/types/database.types' export const useEntitesStore = defineStore('entites', () => { const entites = ref([]) @@ -11,6 +11,7 @@ export const useEntitesStore = defineStore('entites', () => { const errorMessage = ref('') const currentPage = ref(1) const hasMore = ref(true) + const tree = ref([]) const loadEntites = async () => { if (!hasMore.value) return @@ -91,6 +92,22 @@ export const useEntitesStore = defineStore('entites', () => { ) }) + const loadTree = async (selectedFamilyRoot: string) => { + isLoading.value = true + isError.value = false + + try { + const data = await $fetch(`/api/genealogie/${selectedFamilyRoot}`) + tree.value = data + } catch (e) { + const err = e as Error + isError.value = true + errorMessage.value = err.message + } finally { + isLoading.value = false + } + } + return { entites, typeFilter, @@ -104,5 +121,6 @@ export const useEntitesStore = defineStore('entites', () => { resetAndLoad, getEntiteById, searchEntites, + loadTree, } }) diff --git a/app/types/database.types.ts b/app/types/database.types.ts index 079c61a..6860bb7 100644 --- a/app/types/database.types.ts +++ b/app/types/database.types.ts @@ -16,3 +16,11 @@ export type Entite = { relations?: string[] lieu_id?: string } + +export interface EntiteTree { + id: string + nom: string + espece?: string + type?: string + highlight?: boolean +} diff --git a/server/api/genealogie/[famille].ts b/server/api/genealogie/[famille].ts new file mode 100644 index 0000000..d8b549a --- /dev/null +++ b/server/api/genealogie/[famille].ts @@ -0,0 +1,67 @@ +import { serverSupabaseClient } from '#supabase/server' + +type characterInTree = { + id: string + nom: string + espece: string + type: string + highlight?: boolean +} + +export default defineEventHandler(async (event) => { + const famille = getRouterParam(event, 'famille') + const supabase = await serverSupabaseClient(event) + + const { data: personnages, error } = await supabase + .from('entites') + .select('id, nom, espece, type') + .eq('type', 'personnage') + + if (error) { + throw createError({ + statusCode: 500, + message: 'Erreur lors de la récupération des personnages', + }) + } + + const findByName = (nom: string) => { + return personnages?.find((p) => p.nom.toLowerCase() === nom.toLowerCase()) + } + + const arbres: Record = { + abraracourcix: [ + // Génération 1 : Grands-parents (couples séparés) + [ + [ + { ...findByName('Panoramix'), highlight: false }, + { ...findByName('Agecanonix'), highlight: false }, // Invente sa femme ou laisse seul + ].filter((p) => p?.id), + ], + // Génération 2 : Parents (couple) + [ + [ + { ...findByName('Abraracourcix'), highlight: true }, + // Tu peux ajouter Bonemine si tu l'as en BDD + ].filter((p) => p?.id), + ], + // Génération 3 : Enfants (frères/sœurs séparés) + [ + [{ ...findByName('Astérix'), highlight: false }].filter((p) => p?.id), + [{ ...findByName('Obélix'), highlight: false }].filter((p) => p?.id), + ], + ], + panoramix: [ + [[{ ...findByName('Panoramix'), highlight: true }].filter((p) => p?.id)], + [[{ ...findByName('Astérix'), highlight: false }].filter((p) => p?.id)], + ], + asterix: [ + [[{ ...findByName('Panoramix'), highlight: false }].filter((p) => p?.id)], + [ + [{ ...findByName('Astérix'), highlight: true }].filter((p) => p?.id), + [{ ...findByName('Obélix'), highlight: false }].filter((p) => p?.id), + ], + ], + } + + return arbres[famille as string] || arbres.abraracourcix +}) From 2b264c24492591c5b5d5a90ec5a174a54763d769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20H=C3=A9tru?= Date: Sat, 21 Feb 2026 20:04:00 +0100 Subject: [PATCH 2/4] fix: observer --- app/components/HeroItem.vue | 15 +++---- app/components/SearchItem.vue | 26 ++++++++---- app/pages/genealogie.vue | 10 +++-- app/stores/entites.store.ts | 17 ++++++-- app/types/database.types.ts | 2 + commitlint.config.js => commitlint.config.ts | 0 eslint.config.mjs => eslint.config.mts | 0 nuxt.config.ts | 14 +++++++ server/api/genealogie/[famille].ts | 44 ++++++++++---------- 9 files changed, 83 insertions(+), 45 deletions(-) rename commitlint.config.js => commitlint.config.ts (100%) rename eslint.config.mjs => eslint.config.mts (100%) diff --git a/app/components/HeroItem.vue b/app/components/HeroItem.vue index d31968a..339365f 100644 --- a/app/components/HeroItem.vue +++ b/app/components/HeroItem.vue @@ -31,14 +31,15 @@
No data available
- diff --git a/app/components/SearchItem.vue b/app/components/SearchItem.vue index 46ff0f6..c78542b 100644 --- a/app/components/SearchItem.vue +++ b/app/components/SearchItem.vue @@ -1,5 +1,5 @@ diff --git a/app/stores/entites.store.ts b/app/stores/entites.store.ts index ef4c18a..ffe240d 100644 --- a/app/stores/entites.store.ts +++ b/app/stores/entites.store.ts @@ -13,7 +13,7 @@ export const useEntitesStore = defineStore('entites', () => { const hasMore = ref(true) const tree = ref([]) - const loadEntites = async () => { + const getEntites = async () => { if (!hasMore.value) return isError.value = false errorMessage.value = '' @@ -40,7 +40,7 @@ export const useEntitesStore = defineStore('entites', () => { const loadMore = () => { if (hasMore.value && !isLoading.value) { currentPage.value++ - return loadEntites() + return getEntites() } } @@ -48,7 +48,7 @@ export const useEntitesStore = defineStore('entites', () => { entites.value = [] currentPage.value = 1 hasMore.value = true - return loadEntites() + return getEntites() } const getEntiteById = async (id: string) => { @@ -64,7 +64,7 @@ export const useEntitesStore = defineStore('entites', () => { const searchEntites = async (terme: string) => { if (!terme) { - await loadEntites() + await getEntites() return } @@ -92,7 +92,7 @@ export const useEntitesStore = defineStore('entites', () => { ) }) - const loadTree = async (selectedFamilyRoot: string) => { + const getEntitesOnTree = async (selectedFamilyRoot: string) => { isLoading.value = true isError.value = false @@ -125,11 +125,11 @@ export const useEntitesStore = defineStore('entites', () => { errorMessage, entitesFilter, loadMore, - loadEntites, + getEntites, resetAndLoad, getEntiteById, searchEntites, - loadTree, + getEntitesOnTree, resetFilters, } }) diff --git a/nuxt.config.ts b/nuxt.config.ts index d9fdc30..279160e 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -83,13 +83,19 @@ export default defineNuxtConfig({ 'https://cloud.umami.is', 'https://esm.sh', ], - 'img-src': ["'self'", 'data:', 'https://avatars.githubusercontent.com'], + 'img-src': [ + "'self'", + 'data:', + 'https://avatars.githubusercontent.com', + 'https://res.cloudinary.com', + 'https://cloud.umami.is', + ], }, }, }, image: { cloudinary: { - baseURL: 'https://res.cloudinary.com/de4yjg0o6/image/upload', + baseURL: 'https://res.cloudinary.com/dbihnwsar/image/upload', }, }, }) diff --git a/server/api/genealogie/[famille].ts b/server/api/genealogie/[famille].ts deleted file mode 100644 index 5032483..0000000 --- a/server/api/genealogie/[famille].ts +++ /dev/null @@ -1,67 +0,0 @@ -import { serverSupabaseClient } from '#supabase/server' - -type characterInTree = { - id: string - nom: string - espece: string - type: string - highlight?: boolean -} - -export default defineEventHandler(async (event) => { - const famille = decodeURIComponent(getRouterParam(event, 'famille') || '') - const supabase = await serverSupabaseClient(event) - - const { data: personnages, error } = await supabase - .from('entites') - .select('id, nom, espece, type') - .eq('type', 'personnage') - - if (error) { - throw createError({ - statusCode: 500, - message: 'Erreur lors de la récupération des personnages', - }) - } - - const findByName = (nom: string) => { - return personnages?.find((p) => p.nom.toLowerCase() === nom.toLowerCase()) - } - - const arbres: Record = { - noor: [ - // Génération 1 : Grands-parents (couples séparés) - [ - [ - { ...findByName('Zaboor'), highlight: false }, - { ...findByName('Toolja'), highlight: false }, // Invente sa femme ou laisse seul - ].filter((p) => p?.id), - ], - // Génération 2 : Parents (couple) - [ - [ - { ...findByName('Noor'), highlight: true }, - { ...findByName('Brunilla'), highlight: true }, - ].filter((p) => p?.id), - ], - // // Génération 3 : Enfants (frères/sœurs séparés) - // [ - // [{ ...findByName('Astérix'), highlight: false }].filter((p) => p?.id), - // [{ ...findByName('Obélix'), highlight: false }].filter((p) => p?.id), - // ], - ], - weëna: [ - [[{ ...findByName('Galdec'), highlight: true }].filter((p) => p?.id)], - [[{ ...findByName('Kheëna'), highlight: false }].filter((p) => p?.id)], - ], - // asterix: [ - // [[{ ...findByName('Panoramix'), highlight: false }].filter((p) => p?.id)], - // [ - // [{ ...findByName('Galdec'), highlight: true }].filter((p) => p?.id), - // [{ ...findByName('Obélix'), highlight: false }].filter((p) => p?.id), - // ], - // ], - } - - return arbres[famille as string] || arbres.noor -}) diff --git a/server/api/genealogie/families.ts b/server/api/genealogie/families.ts new file mode 100644 index 0000000..3624413 --- /dev/null +++ b/server/api/genealogie/families.ts @@ -0,0 +1,58 @@ +import { serverSupabaseClient } from '#supabase/server' + +// type characterInTree = { +// id: string +// nom: string +// espece: string +// type: string +// highlight?: boolean +// } + +export default defineEventHandler(async (event) => { + const supabase = await serverSupabaseClient(event) + + const { data: personnages, error } = await supabase + .from('entites') + .select('id, nom, espece, type') + .eq('type', 'personnage') + + if (error) { + throw createError({ + statusCode: 500, + message: 'Erreur lors de la récupération des personnages', + }) + } + + const findByName = (nom: string) => { + return personnages?.find((p) => p.nom.toLowerCase() === nom.toLowerCase()) + } + + return { + // Souche = génération 0 (les grands-parents) + souche: [[{ ...findByName('Zaboor') }, { ...findByName('Toolja') }].filter((p) => p?.id)], + + // Les 3 branches côte à côte + branches: { + invisible: [ + // Génération 1 : Parents + [[{ ...findByName('Noor') }, { ...findByName('Brunilla') }].filter((p) => p?.id)], + // Génération 2 : Enfants + [[{ ...findByName('Galdec') }, { ...findByName('Kheëna') }].filter((p) => p?.id)], + // Génération 3 : Petits-enfants + [[{ ...findByName('Weëna') }].filter((p) => p?.id)], + ], + + maitresse: [ + [[{ ...findByName('Skoor') }, { ...findByName('Naounë') }].filter((p) => p?.id)], + [[{ ...findByName("Ot'Skoor") }, { ...findByName('Eda') }].filter((p) => p?.id)], + [[{ ...findByName('Armskoor') }].filter((p) => p?.id)], + ], + + morte: [ + [[{ ...findByName('Tengoor') }, { ...findByName('Golja') }].filter((p) => p?.id)], + [[{ ...findByName("Ot'Tengoor") }, { ...findByName('Queënzy') }].filter((p) => p?.id)], + [[{ ...findByName('Morckoor') }, { ...findByName('Olja') }].filter((p) => p?.id)], + ], + }, + } +}) From b57c4c4a6073e688d5de17c611f4a20260cd266d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20H=C3=A9tru?= Date: Tue, 24 Feb 2026 18:27:23 +0100 Subject: [PATCH 4/4] feat: todo --- app/pages/entites/[id].vue | 1 + app/pages/index.vue | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/pages/entites/[id].vue b/app/pages/entites/[id].vue index 8d50c0a..b512048 100644 --- a/app/pages/entites/[id].vue +++ b/app/pages/entites/[id].vue @@ -1,4 +1,5 @@