import type { NewsItem, NewsDetail } from '#lib/types' import { DATE_TIME_FORMAT, PROMOTION_SLUG, PROMOTION_SLUG_BY_TYPE, } from '#lib/constants' import { formatUnitTime } from '#lib/utils' import { useAsyncData, useRoute } from 'nuxt/app' import { reactive, ref } from 'vue' import { useApiFetch } from '#lib/composables/service/use-api-fetch' import { NEWS_API } from '#lib/configs' /** * Composable for managing news-related data and operations, including fetching news articles, * handling pagination, and retrieving detailed news articles. * * @param {ItemCategory[]} [initCategories] Optional initial categories to use for fetching news. * @returns {Object} An object containing reactive variables and methods for managing news data. * @namespace */ export function useNewsDetail() { const { request } = useApiFetch() const route = useRoute() const isLoading = ref(false) const relatedNews = ref([]) const newsDetail = reactive({ slug: '', title: '', createdDate: 0, categorySlug: '', content: '', link: '', categories: [], relatedPost: [], }) /** * Fetches detailed information about a specific news item based on the route parameters. * * @returns {Promise} A promise that resolves when the news detail is fetched and updated. */ const fetchNewsDetail = async (relatedPostLimit = 3) => { try { isLoading.value = true const isPromotionPage = route.path.includes(PROMOTION_SLUG) const newSlug = isPromotionPage ? (route.fullPath.replace(PROMOTION_SLUG, '') ?? PROMOTION_SLUG_BY_TYPE.WELCOME) : route.params?.slug?.toString()?.replace('.html', '') // eslint-disable-next-line @typescript-eslint/no-explicit-any const { data } = await request(NEWS_API.POST_DETAIL, { slug: newSlug, }) const detailNews = data[0] detailNews.post.createdDate = detailNews.post.created_date detailNews.post.categorySlug = detailNews.post.category_slug Object.assign(newsDetail, detailNews.post) let relatedNews: NewsItem[] = [] if (detailNews.relatedPost) { relatedNews = mapRelatedPosts( detailNews.relatedPost, relatedPostLimit, ) } return { newsDetail, relatedNews } } catch (ex) { console.error(' ~ fetchNewsDetail ~ ex:', ex) } finally { isLoading.value = false } } /** * Fetches news detail SSR. * * @returns {Promise} A promise that resolves when the news detail SSR is fetched and updated. */ const fetchNewsDetailSSR = async () => { const isPromotionPage = PROMOTION_SLUG.includes(route.path.replace('/', '')) const newSlug = isPromotionPage ? (route.query.tab ?? PROMOTION_SLUG_BY_TYPE.WELCOME) : route.params?.slug?.toString()?.replace('.html', '') await useAsyncData(newSlug.toString() ?? '', async () => { await fetchNewsDetail() }) } /** * Maps related posts data to a more structured format suitable for the UI. * * @param {NewsItem[]} relatedPosts The raw related posts data. * @returns {NewsItem[]} The mapped related posts data. */ const mapRelatedPosts = ( relatedPosts: NewsItem[], relatedPostLimit: number, ): NewsItem[] => { return relatedPosts.slice(0, relatedPostLimit).map((element: NewsItem) => ({ id: element.id, title: element.title, slug: `${element.slug}.html`, image: element.thumbnail, category: element.categories?.[0]?.name ?? '', publishTime: formatUnitTime( new Date(element?.updated_date ?? 0).getTime() / 1_000, DATE_TIME_FORMAT.PUBLISH_TIME, ), updated_date: new Date(element?.updated_date ?? 0).getTime() / 1_000, desc: element.description, })) } return { isLoading, newsDetail, relatedNews, fetchNewsDetail, fetchNewsDetailSSR, } }