From 00944cfd7cc0b6a9410bf1f2b0417e98a1c39a72 Mon Sep 17 00:00:00 2001 From: goodsmell <87801306+goodsmell@users.noreply.github.com> Date: Sun, 8 Feb 2026 19:52:13 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EA=B3=B5=EC=97=B0=EC=86=8C=EC=8B=9D?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=ED=83=AD=20-=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99/=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=95=88=EB=B3=B4=EC=9D=B4=EB=8A=94=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/performanceNews/performanceNews.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/performanceNews/performanceNews.tsx b/src/pages/performanceNews/performanceNews.tsx index a6214c5..c1e3cd2 100644 --- a/src/pages/performanceNews/performanceNews.tsx +++ b/src/pages/performanceNews/performanceNews.tsx @@ -27,6 +27,8 @@ type PerformanceCardItem = { startDate: string; endDate: string; isUsed: boolean; + isOwner: boolean; + link: string; }; const PerformanceNews = () => { @@ -214,6 +216,7 @@ const PerformanceNews = () => { onClose: () => void; }) => { const { data: detailData } = usePerformanceDetail(item.id, isOpen); + console.log(item); function normalizationDate() { const splitEndDate = item.endDate.split("-"); @@ -226,7 +229,7 @@ const PerformanceNews = () => { const handleGoLink = (e: React.MouseEvent) => { e.stopPropagation(); - const url = detailData?.link; + const url = item?.link; if (!url) return; // 링크 없으면 아무것도 안 함 (또는 토스트) setPendingLink(url); @@ -263,7 +266,7 @@ const PerformanceNews = () => { onClose(); // ✅ 닫기 }} > - {detailData?.isOwner && ( + {item?.isOwner && ( + )} + + + + + + {/* 텍스트 영역 */} +
+

+ {item.title} +

+

+ {item.place} +

+

+ {normalizationDate()} +

+ + {item.isUsed && ( +
+ 포도상점 +
+ )} +
+ + ); +}; + +/** ========================= + * ✅ ViewAllSections (TOP LEVEL) + * ========================= */ +const ViewAllSections = ({ + title, + onClickSeeAll, + items, + filter, + onChangeFilter, + isLoading, + isError, + + isOver480, + openCardId, + setOpenCardId, + setPendingLink, + setIsLinkModalOpen, + navigate, +}: { + title: string; + onClickSeeAll: () => void; + items: PerformanceMainItem[]; + + filter: UsedFilter; + onChangeFilter: (next: UsedFilter) => void; + + isLoading: boolean; + isError: boolean; + + isOver480: boolean; + openCardId: string | null; + setOpenCardId: (id: string | null) => void; + setPendingLink: (v: string | null) => void; + setIsLinkModalOpen: (v: boolean) => void; + navigate: NavigateFunction; +}) => { + const list = items.slice(0, 4); + + return ( +
+
+

{title}

+ +
+
+ + + + + +
+ + +
+
+ + {isLoading &&
불러오는 중...
} + + {!isLoading && isError && ( +
불러오기에 실패했어요.
+ )} + + {!isLoading && !isError && items.length === 0 && ( +
존재하는 공연이 없습니다.
+ )} + + {!isLoading && !isError && ( +
+ {list.map((item) => ( + setOpenCardId(item.id)} + onClose={() => setOpenCardId(null)} + isOver480={isOver480} + setPendingLink={setPendingLink} + setIsLinkModalOpen={setIsLinkModalOpen} + navigate={navigate} + /> + ))} +
+ )} + + +
+ ); +}; + +/** ========================= + * ✅ ViewSingleSections (TOP LEVEL) + * ========================= */ +const ViewSingleSections = ({ + title, + status, + isOver480, + openCardId, + setOpenCardId, + setPendingLink, + setIsLinkModalOpen, + navigate, +}: { + title: string; + status: "ONGOING" | "UPCOMING" | "PAST"; + isOver480: boolean; + openCardId: string | null; + setOpenCardId: (id: string | null) => void; + setPendingLink: (v: string | null) => void; + setIsLinkModalOpen: (v: boolean) => void; + navigate: NavigateFunction; +}) => { + const [isUsed, setIsUsed] = useState(undefined); + + const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } = + usePerformanceListInfinite({ status, isUsed }); + + const items = useMemo(() => data?.pages.flat() ?? [], [data]); + + const bottomRef = useRef(null); + + useEffect(() => { + const el = bottomRef.current; + if (!el) return; + + const io = new IntersectionObserver( + (entries) => { + const first = entries[0]; + if (first.isIntersecting && hasNextPage && !isFetchingNextPage) fetchNextPage(); + }, + { threshold: 0.2 } + ); + + io.observe(el); + return () => io.disconnect(); + }, [fetchNextPage, hasNextPage, isFetchingNextPage]); + + if (isLoading) return <>로딩; + if (isError) return <>에러; + + return ( +
+
+

{title}

+
+
+

setIsUsed(undefined)} + > + 전체 +

+ + + +

setIsUsed(true)} + > + 포도상점 +

+
+
+
+ + {items.length === 0 &&
존재하는 공연이 없습니다.
} + +
+ {items.map((item) => ( + setOpenCardId(item.id)} + onClose={() => setOpenCardId(null)} + isOver480={isOver480} + setPendingLink={setPendingLink} + setIsLinkModalOpen={setIsLinkModalOpen} + navigate={navigate} + /> + ))} +
+ +
+ + {isFetchingNextPage &&
불러오는 중...
} +
+ ); +}; + +/** ========================= + * ✅ PerformanceNews (MAIN) + * ========================= */ const PerformanceNews = () => { const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); + const [isOver480, setIsOver480] = useState(() => window.innerWidth > 480); const [sectionUsedFilter, setSectionUsedFilter] = useState>({ ongoing: "all", upcoming: "all", past: "all", }); + const toIsUsedParam = (filter: UsedFilter) => (filter === "podo" ? true : false); const activeTab = searchParams.get("tab") || "전체"; const accessToken = Cookies.get("accessToken"); + const tabToStatus = (tab: string) => { if (tab === "지금 공연중") return "ONGOING"; if (tab === "공연 예정") return "UPCOMING"; @@ -51,6 +420,7 @@ const PerformanceNews = () => { }; const [openCardId, setOpenCardId] = useState(null); + const { ongoing, upcoming, past, section } = usePerformanceMainSections({ ongoing: { isUsed: toIsUsedParam(sectionUsedFilter.ongoing) }, upcoming: { isUsed: toIsUsedParam(sectionUsedFilter.upcoming) }, @@ -58,8 +428,10 @@ const PerformanceNews = () => { }); const data = useMemo(() => ({ ongoing, upcoming, past }), [ongoing, upcoming, past]); + const [isLinkModalOpen, setIsLinkModalOpen] = useState(false); const [pendingLink, setPendingLink] = useState(null); + const handleChangeCategory = useCallback( (value: string, type: "tab") => { const next = new URLSearchParams(searchParams.toString()); @@ -69,6 +441,16 @@ const PerformanceNews = () => { [searchParams, setSearchParams] ); + useEffect(() => { + const onResize = () => setIsOver480(window.innerWidth > 480); + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }, []); + + useEffect(() => { + setOpenCardId(null); + }, [activeTab, sectionUsedFilter]); + const handleSeeAll = useCallback( (tabName: string) => { handleChangeCategory(tabName, "tab"); @@ -78,333 +460,18 @@ const PerformanceNews = () => { const ALL_SECTIONS: SectionConfig[] = useMemo( () => [ - { - id: "ongoing", - title: "지금 공연중", - onClickSeeAll: () => handleSeeAll("지금 공연중"), - }, - { - id: "upcoming", - title: "공연 예정", - onClickSeeAll: () => handleSeeAll("공연 예정"), - }, - { - id: "past", - title: "지난 공연", - onClickSeeAll: () => handleSeeAll("지난 공연"), - }, + { id: "ongoing", title: "지금 공연중", onClickSeeAll: () => handleSeeAll("지금 공연중") }, + { id: "upcoming", title: "공연 예정", onClickSeeAll: () => handleSeeAll("공연 예정") }, + { id: "past", title: "지난 공연", onClickSeeAll: () => handleSeeAll("지난 공연") }, ], [handleSeeAll] ); - type ViewAllSectionsProps = { - sectionId: SectionId; - title: string; - onClickSeeAll: () => void; - items: PerformanceMainItem[]; - - filter: UsedFilter; - onChangeFilter: (next: UsedFilter) => void; - - isLoading: boolean; - isError: boolean; - }; - - const ViewAllSections = ({ - sectionId, - title, - onClickSeeAll, - items, - filter, - onChangeFilter, - isLoading, - isError, - }: ViewAllSectionsProps) => { - const list = items.slice(0, 4); - - return ( -
-
-

{title}

- -
-
- - - - - -
- - -
-
- {isLoading &&
불러오는 중...
} - - {!isLoading && isError && ( -
불러오기에 실패했어요.
- )} - - {!isLoading && !isError && items.length === 0 && ( -
존재하는 공연이 없습니다.
- )} - - {!isLoading && !isError && ( -
- {list.map((item) => ( - setOpenCardId((prev) => (prev === item.id ? null : item.id))} - onClose={() => setOpenCardId(null)} - /> - ))} -
- )} - - -
- ); - }; - - const PerformanceCard = ({ - item, - isOpen, - onToggle, - onClose, - }: { - item: PerformanceCardItem; - isOpen: boolean; - onToggle: () => void; - onClose: () => void; - }) => { - function normalizationDate() { - const splitEndDate = item.endDate.split("-"); - const finalEndDate = `${splitEndDate[1]}.${splitEndDate[2]}`; - const splitStartDate = item.startDate.split("-"); - const finalStartDate = `${splitStartDate[0]}.${splitStartDate[1]}.${splitStartDate[2]}`; - return `${finalStartDate}~${finalEndDate}`; - } - - const handleGoLink = (e: React.MouseEvent) => { - e.stopPropagation(); - - const url = item?.link; - if (!url) return; // 링크 없으면 아무것도 안 함 (또는 토스트) - - setPendingLink(url); - setIsLinkModalOpen(true); - }; - - const handleGoModify = (e: React.MouseEvent) => { - e.stopPropagation(); - onClose(); - navigate(`/performanceNews/edit/${item.id}`); - }; - - return ( -
{ - setOpenCardId(item.id); - }} // ✅ 무조건 열기 - > -
- {item.title} - - {/* ✅ 오버레이 버튼 */} - {isOpen && ( -
{ - e.stopPropagation(); // ✅ article onClick으로 안 올라가게 막기 - onClose(); // ✅ 닫기 - }} - > - {item?.isOwner && ( - - )} - - -
- )} -
-
-

- {item.title} -

-

- {item.place} -

-

- {normalizationDate()} -

- - {item.isUsed && ( -
- 포도상점 -
- )} -
-
- ); - }; - - const ViewSingleSections = ({ title }: { title: string }) => { - const status = useMemo(() => tabToStatus(title), [title]); - const [isUsed, setIsUsed] = useState(undefined); - const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } = - usePerformanceListInfinite({ status, isUsed }); - - const items = useMemo(() => data?.pages.flat() ?? [], [data]); - - const bottomRef = useRef(null); - - useEffect(() => { - const el = bottomRef.current; - if (!el) return; - - const io = new IntersectionObserver( - (entries) => { - const first = entries[0]; - if (first.isIntersecting && hasNextPage && !isFetchingNextPage) { - fetchNextPage(); - } - }, - { threshold: 0.2 } - ); - - io.observe(el); - return () => io.disconnect(); - }, [fetchNextPage, hasNextPage, isFetchingNextPage]); - - if (isLoading) return <>로딩; - if (isError) return <>에러; - - return ( -
-
-

{title}

-
-
-

setIsUsed(undefined)} - > - 전체 -

- - - -

setIsUsed(true)} - > - 포도상점 -

-
-
-
- - {items.length === 0 &&
존재하는 공연이 없습니다.
} -
- {items.map((item) => ( - setOpenCardId((prev) => (prev === item.id ? null : item.id))} - onClose={() => setOpenCardId(null)} - /> - ))} -
- - {/* ✅ sentinel */} -
- - {isFetchingNextPage &&
불러오는 중...
} -
- ); - }; + const singleStatus = useMemo(() => tabToStatus(activeTab), [activeTab]); return ( -
- {/* 상단 Info */} -
+
+

공연 소식

+ - {/* 메뉴 선택 */} + - {/* 전체 */} {activeTab === "전체" && (
{ALL_SECTIONS.map((sectionConfig) => ( { onChangeFilter={(next) => setSectionUsedFilter((prev) => ({ ...prev, [sectionConfig.id]: next })) } - isLoading={section[sectionConfig.id].isFetching} // ✅ 필터 전환 시에도 로딩 보이게: isFetching 추천 + isLoading={section[sectionConfig.id].isFetching} isError={section[sectionConfig.id].isError} + isOver480={isOver480} + openCardId={openCardId} + setOpenCardId={setOpenCardId} + setPendingLink={setPendingLink} + setIsLinkModalOpen={setIsLinkModalOpen} + navigate={navigate} /> ))}
)} - {/* 지금 공연중 */} - {activeTab === "지금 공연중" && } - {/* 공연 예정 */} - {activeTab === "공연 예정" && } - {/* 지난 공연 */} - {activeTab === "지난 공연" && } -
+ + {activeTab !== "전체" && ( + + )} + +
{ onClose={() => { setIsLinkModalOpen(false); setPendingLink(null); - setOpenCardId(null); // 원하면 카드 오버레이도 같이 닫기 + setOpenCardId(null); }} onConfirm={() => { if (pendingLink) window.open(pendingLink, "_blank", "noopener,noreferrer");