diff --git a/src/apis/contents.api.ts b/src/apis/contents.api.ts index 488fb758..e80fd421 100644 --- a/src/apis/contents.api.ts +++ b/src/apis/contents.api.ts @@ -10,8 +10,19 @@ import type { CultureContentDetail, GetCultureContentsParams, GetCultureContentsResponse, + OngoingContent, } from '../types/content.type'; +export async function getOngoingContents(): Promise { + try { + const { data } = await apiClient.get('/contents/ongoing'); + + return data; + } catch (error) { + throw normalizeApiError(error); + } +} + export async function getCultureContents( params: GetCultureContentsParams = {}, signal?: AbortSignal, diff --git a/src/hooks/useOngoingContents.ts b/src/hooks/useOngoingContents.ts new file mode 100644 index 00000000..5378798a --- /dev/null +++ b/src/hooks/useOngoingContents.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getOngoingContents } from '../apis/contents.api'; + +export function useOngoingContents() { + return useQuery({ + queryKey: ['contents', 'ongoing'], + queryFn: getOngoingContents, + }); +} diff --git a/src/pages/detail/festival/index.tsx b/src/pages/detail/festival/index.tsx index 3761f7e0..80655b52 100644 --- a/src/pages/detail/festival/index.tsx +++ b/src/pages/detail/festival/index.tsx @@ -195,7 +195,7 @@ function FestivalDetailContent({ contentId }: { contentId: number }) {
- {isCultureContentsPending ? ( + {isOngoingContentsPending ? ( <> diff --git a/src/pages/festival/ongoing/index.tsx b/src/pages/festival/ongoing/index.tsx index 404f77cc..7ca53449 100644 --- a/src/pages/festival/ongoing/index.tsx +++ b/src/pages/festival/ongoing/index.tsx @@ -1,14 +1,13 @@ -import { useCallback } from 'react'; +import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ContentCard, ContentCardSkeleton, } from '../../../components/common'; -import { useCultureContents } from '../../../hooks/useCultureContents'; +import { useOngoingContents } from '../../../hooks/useOngoingContents'; import { useContentLikeToggle } from '../../../hooks/useContentLikeToggle'; import { useGlobalScale } from '../../../hooks/useGlobalScale'; -import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import { toContentTagIds } from '../../../utils/contentTags'; import { buildFestivalDetailPath } from '../../../utils/routes'; @@ -29,14 +28,6 @@ const LIST_GAP = 16; const EMPTY_MARGIN_TOP = 40; const ERROR_MARGIN_TOP = 24; const MESSAGE_TEXT_SIZE = 13; -const LOAD_MORE_HEIGHT = 40; - -const contentSortByFestivalSort = { - RECOMMENDED: 'RECOMMEND', - SAVED: 'LIKE', - DISTANCE: 'DISTANCE', - ENDING_SOON: 'DEADLINE', -} as const; function FestivalOngoingPage() { const navigate = useNavigate(); @@ -47,35 +38,28 @@ function FestivalOngoingPage() { handleSortSelect, handleCategorySelect, } = useFestivalFilters(); - const { - data, - fetchNextPage, - hasNextPage, - isError, - isFetchingNextPage, - isPending, - } = useCultureContents({ - category: - selectedFilters.category === 'ALL' - ? undefined - : selectedFilters.category, - sort: contentSortByFestivalSort[selectedFilters.sort], - size: 20, - }); - - const festivals = data?.pages.flatMap((page) => page.items) ?? []; - const hasEmptyResult = !isPending && !isError && festivals.length === 0; - - const handleIntersect = useCallback(() => { - if (hasNextPage && !isFetchingNextPage) { - fetchNextPage(); + const { data: ongoingContents, isPending, isError } = useOngoingContents(); + + // /contents/ongoing은 쿼리 파라미터를 아예 받지 않는 엔드포인트로 확인됨 + // (category/sort/cursor 등 어떤 값을 보내도, 심지어 존재하지 않는 값을 + // 보내도 항상 동일한 결과가 옴 - curl로 재확인). 응답 필드에도 category가 + // 없어 카테고리 필터(체험/전시/공연/축제)를 적용할 데이터가 없다. 정렬도 + // 종료 임박순(endDate)만 응답 필드로 계산 가능하고, 추천순/저장순/거리순은 + // 추천 점수·좋아요 수·좌표가 응답에 없어 그대로 원본 순서를 유지한다. + // 필터 바 UI/상태는 그대로 두되, 실제 필터링·정렬은 API가 관련 필드/ + // 파라미터를 지원해야 완전히 동작한다 - 백엔드에 카테고리 필드 및 + // 정렬/페이지네이션 파라미터 추가를 요청해야 한다. + const festivals = useMemo(() => { + const items = ongoingContents ?? []; + + if (selectedFilters.sort !== 'ENDING_SOON') { + return items; } - }, [fetchNextPage, hasNextPage, isFetchingNextPage]); - const loadMoreRef = useInfiniteScroll({ - enabled: Boolean(hasNextPage) && !isPending, - onIntersect: handleIntersect, - }); + return [...items].sort((a, b) => a.endDate.localeCompare(b.endDate)); + }, [ongoingContents, selectedFilters.sort]); + + const hasEmptyResult = !isPending && !isError && festivals.length === 0; return (
))} - - {isFetchingNextPage - ? FESTIVAL_SKELETON_ITEMS.slice(0, 4).map((item) => ( - - )) - : null} {hasEmptyResult ? ( @@ -188,12 +162,6 @@ function FestivalOngoingPage() { 행사 목록을 불러오지 못했어요.

) : null} - -
); } diff --git a/src/pages/home/components/FestivalSection.tsx b/src/pages/home/components/FestivalSection.tsx index c73e218e..969a1683 100644 --- a/src/pages/home/components/FestivalSection.tsx +++ b/src/pages/home/components/FestivalSection.tsx @@ -5,12 +5,14 @@ import { ContentCardSkeleton, SectionHeader, } from '../../../components/common'; -import { useCultureContents } from '../../../hooks/useCultureContents'; +import { useOngoingContents } from '../../../hooks/useOngoingContents'; import { useContentLikeToggle } from '../../../hooks/useContentLikeToggle'; import { useGlobalScale } from '../../../hooks/useGlobalScale'; import { buildFestivalDetailPath } from '../../../utils/routes'; import { toContentTagIds } from '../../../utils/contentTags'; +const PREVIEW_ITEM_COUNT = 2; + const SECTION_MARGIN_TOP = 32; const SECTION_PADDING_X = 24; const LIST_MARGIN_TOP = 16; @@ -20,15 +22,8 @@ function FestivalSection() { const navigate = useNavigate(); const scale = useGlobalScale(); const { getLiked, toggleLike } = useContentLikeToggle(); - const { - data: cultureContents, - isPending: isLoading, - } = useCultureContents({ - category: 'FESTIVAL', - sort: 'RECOMMEND', - size: 2, - }); - const festivals = cultureContents?.pages[0]?.items ?? []; + const { data: ongoingContents, isPending: isLoading } = useOngoingContents(); + const festivals = (ongoingContents ?? []).slice(0, PREVIEW_ITEM_COUNT); return (
diff --git a/src/types/content.type.ts b/src/types/content.type.ts index 94745111..bf45c0a8 100644 --- a/src/types/content.type.ts +++ b/src/types/content.type.ts @@ -93,6 +93,16 @@ export interface CultureContentDetail { courses: CultureContentCourse[]; } +export interface OngoingContent { + contentId: number; + title: string; + thumbnailImageUrl: string | null; + startDate: string; + endDate: string; + regionName: string; + hashtags: string[]; +} + export interface RecentCultureContent { contentId: number; title: string;