diff --git a/app/pages/favoritos.vue b/app/pages/favoritos.vue index 2e2c53d..0d7bc97 100644 --- a/app/pages/favoritos.vue +++ b/app/pages/favoritos.vue @@ -5,18 +5,126 @@ import { breakpointsTailwind } from '@vueuse/core' import type { DropdownMenuItem } from '@nuxt/ui' import type { SearchHit } from '~/types' import { useFavoritesStore, type FavoriteItem } from '~/stores/favorites' -import InboxActivity from '~/components/inbox/InboxActivity.vue' +import EstudiosTypensenseDetail from '~/components/estudiosTypensense/EstudiosTypensenseDetail.vue' import EntrelineaDetail from '~/components/entrelineas/EntrelineaDetail.vue' const favorites = useFavoritesStore() // Refs reactivos para usar en watchers / template (Pinia setup store). const { items: favItems, total: favTotal, collections: favCollections } = storeToRefs(favorites) const toast = useToast() +const { locale } = useI18n() +const { documentsApi } = useTypesenseApi() + +const TYPESENSE_DOCUMENTS = 'documents' +const TYPESENSE_PARAGRAPHS = 'paragraphs' /** Identificador de la colección de entrelíneas en el store de favoritos. * Debe coincidir con `FAVORITES_COLLECTION` en `pages/entrelineas.vue`. */ const ENTRELINEAS_COLLECTION = 'entrelineas' +interface ParagraphDoc { + id?: string + document_id: string + text: string + number: number + locale: string + type: string +} + +interface TypesenseParagraphHit { + document: ParagraphDoc + highlights?: unknown[] + highlight?: Record +} + +interface DocumentDoc { + id?: string + code: string + locale: string + type: string + title: string + timestamp: number + date: string + place?: string + city?: string + state?: string + country?: string + thumbnail?: string + files?: { youtube?: string; video?: string; audio?: string; booklet?: string; simple?: string } + slug?: string + body?: string + draft?: boolean + [key: string]: unknown +} + +const detailDocument = ref(null) +const detailDocumentLoading = ref(false) +const detailParagraphs = ref([]) +const detailParagraphsLoading = ref(false) + +async function fetchDetailDocument(hit: SearchHit) { + const docId = String(hit._id || hit.id || '') + if (!docId) return + detailDocumentLoading.value = true + detailDocument.value = null + try { + const res = await documentsApi.multiSearch({ + multiSearchParameters: {}, + multiSearchSearchesParameter: { + searches: [{ collection: TYPESENSE_DOCUMENTS, q: '*', queryBy: 'title', filterBy: `id:=${docId}`, perPage: 1, page: 1 }] + } + }) + const hits = (res?.results?.[0] as { hits?: Array<{ document: DocumentDoc }> })?.hits ?? [] + detailDocument.value = hits[0]?.document ?? null + } catch (err) { + console.error('Error fetching document', err) + detailDocument.value = null + } finally { + detailDocumentLoading.value = false + } +} + +async function fetchDetailParagraphs(hit: SearchHit) { + const docId = String(hit._id || hit.id || '') + const type = hit.type as string + if (!docId || !type) return + detailParagraphsLoading.value = true + detailParagraphs.value = [] + const PER_PAGE = 250 + let page = 1 + let totalFound = 0 + const all: Array<{ document: ParagraphDoc }> = [] + try { + do { + const res = await documentsApi.multiSearch({ + multiSearchParameters: {}, + multiSearchSearchesParameter: { + searches: [{ + collection: TYPESENSE_PARAGRAPHS, + q: '*', + queryBy: '', + filterBy: `document_id:=${docId} && locale:=${locale.value} && type:=${type}`, + perPage: PER_PAGE, + page, + sortBy: 'number:asc' + }] + } + }) + const result = (res?.results?.[0] as { found?: number; hits?: Array<{ document: ParagraphDoc }> } | undefined) + if (!result) break + if (page === 1) totalFound = result.found ?? 0 + all.push(...(result.hits ?? [])) + page++ + } while (all.length < totalFound) + detailParagraphs.value = all.map(h => ({ document: h.document })) + } catch (err) { + console.error('Error fetching paragraphs', err) + detailParagraphs.value = [] + } finally { + detailParagraphsLoading.value = false + } +} + // Nota: la URL de ImageKit y los presets de transformación viven en // `~/utils/entrelineaImage.ts` (auto-importado). EntrelineaDetail los usa // internamente, así que aquí no hay que pasar nada relacionado a ImageKit. @@ -116,6 +224,16 @@ watch(favItems, (items) => { const breakpoints = useBreakpoints(breakpointsTailwind) const isMobile = breakpoints.smaller('lg') +watch(selected, (item) => { + if (!item || item.collection === ENTRELINEAS_COLLECTION) { + detailDocument.value = null + detailParagraphs.value = [] + return + } + fetchDetailDocument(item.hit) + fetchDetailParagraphs(item.hit) +}) + // ---- Helpers de fila --------------------------------------------------- function safeDate(hit: SearchHit) { @@ -477,11 +595,14 @@ const mobileActions = computed(() => [[ :collection="selectedCollection" @close="selected = null" /> - - +