Fix bug favorites detail

This commit is contained in:
David Ascanio 2026-05-23 17:31:15 -03:00
parent 3424b22fcf
commit b12c31099c
2 changed files with 133 additions and 9 deletions

View File

@ -5,18 +5,126 @@ import { breakpointsTailwind } from '@vueuse/core'
import type { DropdownMenuItem } from '@nuxt/ui' import type { DropdownMenuItem } from '@nuxt/ui'
import type { SearchHit } from '~/types' import type { SearchHit } from '~/types'
import { useFavoritesStore, type FavoriteItem } from '~/stores/favorites' 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' import EntrelineaDetail from '~/components/entrelineas/EntrelineaDetail.vue'
const favorites = useFavoritesStore() const favorites = useFavoritesStore()
// Refs reactivos para usar en watchers / template (Pinia setup store). // Refs reactivos para usar en watchers / template (Pinia setup store).
const { items: favItems, total: favTotal, collections: favCollections } = storeToRefs(favorites) const { items: favItems, total: favTotal, collections: favCollections } = storeToRefs(favorites)
const toast = useToast() 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. /** Identificador de la colección de entrelíneas en el store de favoritos.
* Debe coincidir con `FAVORITES_COLLECTION` en `pages/entrelineas.vue`. */ * Debe coincidir con `FAVORITES_COLLECTION` en `pages/entrelineas.vue`. */
const ENTRELINEAS_COLLECTION = 'entrelineas' 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<string, unknown>
}
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<DocumentDoc | null>(null)
const detailDocumentLoading = ref(false)
const detailParagraphs = ref<TypesenseParagraphHit[]>([])
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 // Nota: la URL de ImageKit y los presets de transformación viven en
// `~/utils/entrelineaImage.ts` (auto-importado). EntrelineaDetail los usa // `~/utils/entrelineaImage.ts` (auto-importado). EntrelineaDetail los usa
// internamente, así que aquí no hay que pasar nada relacionado a ImageKit. // internamente, así que aquí no hay que pasar nada relacionado a ImageKit.
@ -116,6 +224,16 @@ watch(favItems, (items) => {
const breakpoints = useBreakpoints(breakpointsTailwind) const breakpoints = useBreakpoints(breakpointsTailwind)
const isMobile = breakpoints.smaller('lg') 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 --------------------------------------------------- // ---- Helpers de fila ---------------------------------------------------
function safeDate(hit: SearchHit) { function safeDate(hit: SearchHit) {
@ -477,11 +595,14 @@ const mobileActions = computed<DropdownMenuItem[][]>(() => [[
:collection="selectedCollection" :collection="selectedCollection"
@close="selected = null" @close="selected = null"
/> />
<!-- Resto (actividades, conferencias) InboxActivity. --> <!-- Resto (actividades, conferencias) detalle con párrafos de Typesense. -->
<InboxActivity <EstudiosTypensenseDetail
v-else-if="selected && !isMobile" v-else-if="selected && !isMobile"
:activity="selectedHit!" :document="detailDocument"
:collection="selectedCollection" :document-loading="detailDocumentLoading"
:paragraphs="detailParagraphs"
:paragraphs-loading="detailParagraphsLoading"
:collection="selectedCollection!"
@close="selected = null" @close="selected = null"
/> />
<div v-else-if="!selected" class="hidden lg:flex flex-1 items-center justify-center"> <div v-else-if="!selected" class="hidden lg:flex flex-1 items-center justify-center">
@ -502,10 +623,13 @@ const mobileActions = computed<DropdownMenuItem[][]>(() => [[
:collection="selectedCollection" :collection="selectedCollection"
@close="selected = null" @close="selected = null"
/> />
<InboxActivity <EstudiosTypensenseDetail
v-else-if="selected" v-else-if="selected"
:activity="selectedHit!" :document="detailDocument"
:collection="selectedCollection" :document-loading="detailDocumentLoading"
:paragraphs="detailParagraphs"
:paragraphs-loading="detailParagraphsLoading"
:collection="selectedCollection!"
@close="selected = null" @close="selected = null"
/> />
</template> </template>

View File

@ -46,7 +46,7 @@ export function formatFiles( files:FilesObject ){
} }
if (files.audio) { if (files.audio) {
items.push({ items.push({
to: files.audio, to: `https://actividadeswp.carpa.com/wp-content/uploads/${files.audio}`,
target: '_blank', target: '_blank',
label: t('Activities.audio'), label: t('Activities.audio'),
icon: 'ph:file-audio-thin', icon: 'ph:file-audio-thin',