Fix bug favorites detail
This commit is contained in:
parent
3424b22fcf
commit
b12c31099c
|
|
@ -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<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
|
||||
// `~/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<DropdownMenuItem[][]>(() => [[
|
|||
:collection="selectedCollection"
|
||||
@close="selected = null"
|
||||
/>
|
||||
<!-- Resto (actividades, conferencias) → InboxActivity. -->
|
||||
<InboxActivity
|
||||
<!-- Resto (actividades, conferencias) → detalle con párrafos de Typesense. -->
|
||||
<EstudiosTypensenseDetail
|
||||
v-else-if="selected && !isMobile"
|
||||
:activity="selectedHit!"
|
||||
:collection="selectedCollection"
|
||||
:document="detailDocument"
|
||||
:document-loading="detailDocumentLoading"
|
||||
:paragraphs="detailParagraphs"
|
||||
:paragraphs-loading="detailParagraphsLoading"
|
||||
:collection="selectedCollection!"
|
||||
@close="selected = null"
|
||||
/>
|
||||
<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"
|
||||
@close="selected = null"
|
||||
/>
|
||||
<InboxActivity
|
||||
<EstudiosTypensenseDetail
|
||||
v-else-if="selected"
|
||||
:activity="selectedHit!"
|
||||
:collection="selectedCollection"
|
||||
:document="detailDocument"
|
||||
:document-loading="detailDocumentLoading"
|
||||
:paragraphs="detailParagraphs"
|
||||
:paragraphs-loading="detailParagraphsLoading"
|
||||
:collection="selectedCollection!"
|
||||
@close="selected = null"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export function formatFiles( files:FilesObject ){
|
|||
}
|
||||
if (files.audio) {
|
||||
items.push({
|
||||
to: files.audio,
|
||||
to: `https://actividadeswp.carpa.com/wp-content/uploads/${files.audio}`,
|
||||
target: '_blank',
|
||||
label: t('Activities.audio'),
|
||||
icon: 'ph:file-audio-thin',
|
||||
|
|
|
|||
Loading…
Reference in New Issue