import type { ItemCategory, NewsItem, NewsData, NewsCategory, NewCategoryPayload, } from '#lib/types' import { CATEGORY_LIST, DATE_TIME_FORMAT } from '#lib/constants' import { formatUnitTime } from '#lib/utils' import { useAsyncData, useRoute } from 'nuxt/app' import { useDevice } from '#imports' import { computed, ref } from 'vue' import { useApiFetch } from '#lib/composables/service/use-api-fetch' import type { NewsPostsRequestPayload } from '@kira-dancer/nadal' 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 useNews(initCategories?: ItemCategory[]) { const { isMobile } = useDevice() const route = useRoute() const isLoading = ref(false) const totalPost = ref(0) const currentPage = ref(1) const offset = ref(0) const currentCategoryIndex = ref(0) const categorySlugCurrent = computed(() => { return route.query?.cat?.toString() }) const { request } = useApiFetch() const categories = initCategories || CATEGORY_LIST /** * Handles page changes and fetches news data based on the new page. * * @param {number} page The page number to fetch. * @param {NewsData[]} dataNews The array to store fetched news data. * @param {number} limit The number of items per page. * @param {string} [categorySlug] Optional category slug to filter news. * @returns {Promise} A promise that resolves when the data is fetched. */ const handlePage = async ( page: number, dataNews: NewsData[], limit: number, categorySlug?: string, ) => { dataNews.splice(0) currentPage.value = page isLoading.value = true offset.value = limit * (currentPage.value - 1) return await fetchAllCategoryData( dataNews, offset.value, limit, categorySlug, false, ).finally(() => { isLoading.value = false }) } /** * Fetches all news data for a specific category and updates the data array. * * @param {NewsData[]} dataNews The array to store fetched news data. * @param {number} [offset=0] The number of items to skip. * @param {number} [limit=4] The number of items to fetch. * @param {string} [categorySlugParam=''] Optional category slug to filter news. * @param {boolean} [useSSR=true] Flag indicating whether to use server-side rendering. * @returns {Promise} A promise that resolves when the data is fetched and updated. */ const fetchAllCategoryData = async ( dataNews: NewsData[], offset = 0, limit = 4, categorySlugParam: string = '', useSSR = true, ) => { const categorySlug = categorySlugParam || categorySlugCurrent.value const currentCategory = categories.find( (item) => item.slug === categorySlug, ) if (currentCategory) { if (useSSR) { return await useAsyncData(currentCategory.slug, async () => { await fetchPostsByCategory(currentCategory, dataNews, offset, limit) }) } else { return await fetchPostsByCategory( currentCategory, dataNews, offset, limit, ) } } } /** * Fetches news posts for a specific category and updates the data array. * * @param {ItemCategory} category The category to fetch news posts from. * @param {NewsData[]} dataNews The array to store fetched news data. * @param {number} [offset=0] The number of items to skip. * @param {number} [limit=4] The number of items to fetch. * @param {boolean} [isLoadMore=false] Flag indicating whether to load more items. * @returns {Promise} A promise that resolves with the fetched news items. */ const fetchPostsByCategory = async ( category: ItemCategory, dataNews: NewsData[], offset = 0, limit = 4, isLoadMore = false, ): Promise => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const response = await request( NEWS_API.POSTS, { catIds: category.subCategory?.join(','), offset, limit, }, ) // eslint-disable-next-line @typescript-eslint/no-explicit-any totalPost.value = (response as any).total const dataCategory = mapPostData([...response.data] as NewsItem[]) if (isLoadMore) { dataNews[currentCategoryIndex.value].data.push(...dataCategory) return } if (isMobile) { dataNews[currentCategoryIndex.value] = { category, data: dataCategory, } return } dataNews.push({ category, data: dataCategory, }) return dataCategory } /** * Fetches news posts for a specific categor * * @param {string} slug The slug. * @returns {NewsCategory} The news category. */ const fetchPostsCategoriesBySlug = async (slug: string) => { const { data } = await request( NEWS_API.CATEGORY, { slug, }, ) return data[0] } const getNameCategoryNewItem = (item: NewsItem): string => { return ( item.categories?.find( (cat: { slug: string }) => cat.slug === item.category_slug?.replaceAll('/', ''), )?.name ?? '' ) } /** * Maps raw post data to a more structured format suitable for the UI. * * @param {NewsItem[]} data The raw news item data. * @returns {NewsItem[]} The mapped news item data. */ const mapPostData = (data: NewsItem[]): NewsItem[] => { return data.map((element: NewsItem) => ({ id: element.id, slug: `${element.slug}.html`, title: element.title, date: formatUnitTime( element.updated_date, DATE_TIME_FORMAT.SHORT_DATE_FORMAT, ), time: formatUnitTime(element.updated_date, DATE_TIME_FORMAT.HOUR_MINUTE), image: element.media?.source_url ?? '', category: element.categories?.find( (cat: { slug: string }) => !element.category_slug?.split('/')?.[1]?.includes(cat.slug), )?.name ?? getNameCategoryNewItem(element), desc: element.meta?.description ?? '', updated_date: element.updated_date, created_date: element.created_date, })) } return { isLoading, totalPost, currentPage, categorySlugCurrent, categories, currentCategoryIndex, handlePage, fetchAllCategoryData, fetchPostsCategoriesBySlug, fetchPostsByCategory, } }