import cloneDeep from 'lodash/cloneDeep' import groupBy from 'lodash/groupBy' import forIn from 'lodash/forIn' import { LABEL_ALL_GAME, SLUG_SPORTS, SLUG_ALL_SPORTS, SPORTS_LIMIT } from '#lib/constants' import { useGames, useCheckGame, useNavigate } from '#lib/composables' import { AppDataEnum, IFrameGameURLEnum, ResponseStatus, SportScheduleFilterDay, } from '#lib/enums' import { checkIsInCurrentWeek, checkIsToday, checkIsTomorrow, checkTimeUpcoming, } from '#lib/utils' import type { CategorySchedule, FootballSchedule, MatchData, PlaySportItem, } from '#lib/types' import { useSportsService } from '#lib/composables/service/use-sports-service' import { ref } from 'vue' import { useApiFetch } from '#lib/composables/service/use-api-fetch' import { APP_API } from '#lib/configs' import { useAsyncData, useNuxtData } from 'nuxt/app' import cacheManager from '#lib/utils/cache-manager' /** * Composable for managing and displaying sports schedules. * @param sportsLimit - The number of sports schedules to display per page. * @namespace */ export function useSports(sportsLimit: number = SPORTS_LIMIT) { const { playGame, openModalMaintainAthenaGames } = useGames() const { isMaintainAthenaGames } = useCheckGame() const { getFootballSchedulesService } = useSportsService() const { request } = useApiFetch() const isLoadingHotmatch = ref(false) const categorySchedules = ref([]) const scheduleSports = ref([]) const originScheduleSports = ref([]) const totalSchedule = ref(0) const leagueSlug = ref(SportScheduleFilterDay.ALL) const isLoading = ref(false) const offSetSchedule = 0 const numberPagedSports = 1 let numberPages = numberPagedSports const currentFilterDay = ref(SportScheduleFilterDay.ALL) const { data: hotmatches } = useNuxtData(AppDataEnum.HotMatches) /** * Plays a sport game based on the provided item. * @param item - The play sport item. */ const playSport = (item: PlaySportItem): void => { const { navigate } = useNavigate() if (!item || item.isMaintain) { return } if (item.link) { void navigate(item.link, item.loginRequired, item.newTab) } else { void playGame(item) } } /** * Plays a K-Sports game based on the hot match item. * @param hotmatchItem - The hot match item. */ const playKSportGame = (hotmatchItem: MatchData | undefined): void => { if (!hotmatchItem || hotmatchItem.isDisable) { return } const { match_id = '', league_id = '' } = hotmatchItem const playItem = { link: `${IFrameGameURLEnum.K_SPORTS}?eventId=${match_id}&leagueId=${league_id}`, } playSport(playItem) } /** * Gets the API URL for playing an A-Sports game. * @param hotmatch - The hot match data. * @param isMobile - Whether the request is for mobile. * @returns The API URL for the game. */ const getASportApiURL = (hotmatch: MatchData, isMobile: boolean): string => { const matchId = hotmatch?.athena_match_id ?? '' if (isMobile) { const leagueId = hotmatch?.athena_league_id ?? '43' const params = new URLSearchParams({ gameName: 'the-thao', matchId: matchId.toString(), leagueId: leagueId.toString(), isMobile: 'true', }) return `${IFrameGameURLEnum.C_SPORTS}?${params.toString()}` } return `${IFrameGameURLEnum.C_SPORTS}?mid=${matchId}` } /** * Plays an A-Sports game based on the hot match and device type. * @param hotmatch - The hot match data. * @param isMobile - Whether the request is for mobile. */ const playASportGame = (hotmatch: MatchData, isMobile?: boolean): void => { const { navigate } = useNavigate() if (isMaintainAthenaGames(SLUG_SPORTS.C_SPORTS)) { openModalMaintainAthenaGames() return } const apiURL = getASportApiURL(hotmatch, isMobile!) void navigate(apiURL, false, isMobile) } /** * Fetches and sets football schedules. */ const setScheduleMatches = async () => { // check cache const cachedData = cacheManager.get(AppDataEnum.ScheduleMatches) if (cachedData) { return cachedData } try { isLoading.value = true const { data } = await getFootballSchedulesService() if (data.value && data.value.status === ResponseStatus.OK) { const schedules = data.value.data const dataConvert = [] for (const key in schedules) { if (Object.prototype.hasOwnProperty.call(schedules, key)) { dataConvert.push(...schedules[key]) } } const listHotmach: FootballSchedule[] = [] const dataGroupHotmach = groupBy(dataConvert, 'league_name') forIn(dataGroupHotmach, (value, key) => { if (value[0]) { const item = { leagueName: key, logo: value[0]?.league_image, matches: value, } listHotmach.push(item) } }) setCategorySchedules(listHotmach) setSchedulesLeague(listHotmach) totalSchedule.value = listHotmach.length cacheManager.set(AppDataEnum.ScheduleMatches, { scheduleSports: scheduleSports.value, categorySchedules: categorySchedules.value }) return { scheduleSports: scheduleSports.value, categorySchedules: categorySchedules.value } } } catch { cacheManager.delete(AppDataEnum.ScheduleMatches) } finally { isLoading.value = false } } /** * Sets the category schedules. * @param schedules - The list of football schedules. */ const setCategorySchedules = (schedules: FootballSchedule[]): void => { const leagues = schedules.map((it) => { return { slug: it.leagueName.replaceAll(' ', '-').toLowerCase(), thumb: it.logo, name: it.leagueName.toLowerCase(), } }) categorySchedules.value = [ { thumb: '', name: LABEL_ALL_GAME, slug: SLUG_ALL_SPORTS, }, ...leagues, ] } /** * Filters and processes schedule data. * @param matchesData - The football schedule data. * @returns The filtered and processed schedule data. */ const filterSchedulesData = ( matchesData: FootballSchedule[], ): FootballSchedule[] => { matchesData.forEach((match) => { const matchingItem = findMatchingItem(match) if (matchingItem) { match.thumb = matchingItem.thumb match.slug = matchingItem.slug } }) return matchesData.filter((match) => doesMatchBelongToLeague(match)) } /** * Finds a matching category schedule for a given match. * @param match - The football schedule match. * @returns The matching category schedule. */ const findMatchingItem = ( match: FootballSchedule, ): CategorySchedule | undefined => { return categorySchedules.value.find( (item) => match?.leagueName?.toLowerCase() === item.name.toLowerCase(), ) } /** * Checks if a match belongs to any league in the category schedules. * @param match - The football schedule match. * @returns Whether the match belongs to a league. */ const doesMatchBelongToLeague = (match: FootballSchedule): boolean => { return categorySchedules.value.some( (item) => match?.leagueName?.toLowerCase() === item.name.toLowerCase(), ) } /** * Sets and filters the schedules based on league data. * @param matchesData - The football schedule data. */ const setSchedulesLeague = (matchesData: FootballSchedule[]): void => { originScheduleSports.value = filterSchedulesData(matchesData) filterSchedules() } /** * Changes the league filter and refreshes the schedule view. * @param slug - The league slug. */ const changeLeagueName = (slug: string): void => { numberPages = numberPagedSports leagueSlug.value = slug filterSchedules() } /** * Loads more schedules based on pagination. */ const onLoadMoreSchedules = (): void => { numberPages += numberPagedSports filterSchedules() } /** * Filters and slices the schedules based on league slug and pagination. */ const filterSchedules = (): void => { const itemPerPage = numberPages * sportsLimit if (leagueSlug.value === SLUG_ALL_SPORTS || !leagueSlug.value) { scheduleSports.value = cloneDeep(originScheduleSports.value).slice( offSetSchedule, itemPerPage, ) if (currentFilterDay.value !== SportScheduleFilterDay.ALL) { scheduleSports.value.forEach((schedule: FootballSchedule) => { schedule.matches = filterMatchesByCategory( schedule.matches, currentFilterDay.value, ) }) } } else { const newScheduleSports = filterSchedulesByLeagueSlug(leagueSlug.value) newScheduleSports.forEach((schedule: FootballSchedule) => { schedule.matches = filterMatchesByCategory( schedule.matches, currentFilterDay.value, ) }) scheduleSports.value = newScheduleSports.slice( offSetSchedule, itemPerPage, ) } removeEmptyMatches() } /** * Filters schedules by the selected league slug. * @param leagueName - The league slug. * @returns The filtered list of football schedules. */ const filterSchedulesByLeagueSlug = ( leagueName: string, ): FootballSchedule[] => { return cloneDeep(originScheduleSports.value).filter( (match: FootballSchedule) => match?.slug?.toLowerCase() === leagueName.toLowerCase(), ) } /** * Removes schedules with no matches from the display. */ const removeEmptyMatches = (): void => { for (let i = scheduleSports.value.length - 1; i >= 0; i--) { if ( !scheduleSports.value[i].matches || scheduleSports.value[i].matches.length === 0 ) { scheduleSports.value.splice(i, 1) } } } /** * Filters matches based on the selected filter day. * @param matches - The list of matches. * @param slug - The filter day. * @returns The filtered list of matches. */ const filterMatchesByCategory = ( matches: MatchData[], slug: string, ): MatchData[] => { const matchesTemp: MatchData[] = [] const categoryConditions: Record boolean> = { [SportScheduleFilterDay.TODAY]: (match) => !!match?.live || checkIsToday(match?.text_time), [SportScheduleFilterDay.TOMORROW]: (match) => checkIsTomorrow(match?.text_time), [SportScheduleFilterDay.IN_WEEK]: (match) => checkIsInCurrentWeek(match?.text_time), } const condition = categoryConditions[slug] || (() => true) matchesTemp.push(...matches.filter(condition)) return matchesTemp } /** * Changes the filter day and refreshes the schedule view. * @param slug - The filter day slug. */ const changeFilterDay = (slug: string): void => { currentFilterDay.value = slug filterSchedules() } /** * Fetches hot matches data. */ const fetchHotmatches = async (): Promise => { isLoadingHotmatch.value = true await useAsyncData(AppDataEnum.HotMatches, async () => { // check cache const cachedData = cacheManager.get(AppDataEnum.HotMatches) if (cachedData) { return cachedData } try { const { data } = await request(APP_API.HOT_MATCHES) cacheManager.set(AppDataEnum.HotMatches, data) return data } catch { cacheManager.delete(AppDataEnum.HotMatches) return [] } }) isLoadingHotmatch.value = false } return { playSport, playASportGame, playKSportGame, setScheduleMatches, changeLeagueName, onLoadMoreSchedules, changeFilterDay, checkTimeUpcoming, filterSchedules, fetchHotmatches, categorySchedules, currentFilterDay, scheduleSports, leagueSlug, totalSchedule, isLoading, isLoadingHotmatch, hotmatches, } }