diff --git a/src/features/home/ui/homeUseHooks/homeUseHooks.ts b/src/features/home/ui/homeUseHooks/homeUseHooks.ts
new file mode 100644
index 0000000..592597e
--- /dev/null
+++ b/src/features/home/ui/homeUseHooks/homeUseHooks.ts
@@ -0,0 +1,25 @@
+import { useHomeSheetStore } from "@/src/features/home/model/homeStore";
+import { useOAuthStore } from "@/src/features/login/model";
+import { splitAddress } from "@/src/shared/lib/utils";
+import { useRouter, useSearchParams } from "next/navigation";
+
+export const useHomeUseHooks = () => {
+ const openSheet = useHomeSheetStore(s => s.openSheet);
+ const { pinPointId, pinPointName } = useOAuthStore();
+ const [line1, line2] = splitAddress(pinPointName ?? "핀포인트 이름 설정해주세요");
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const onSelectSection = (key: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set("mode", key);
+ params.set("id", pinPointId ?? "");
+ router.push(`?${params.toString()}`, { scroll: false });
+ openSheet();
+ };
+
+ return {
+ line1,
+ line2,
+ onSelectSection
+ }
+}
\ No newline at end of file
diff --git a/src/features/listings/ui/listingsCardDetail/infra/components/roomTypeDetail.tsx b/src/features/listings/ui/listingsCardDetail/infra/components/roomTypeDetail.tsx
index a2cdc3c..c83a5e6 100644
--- a/src/features/listings/ui/listingsCardDetail/infra/components/roomTypeDetail.tsx
+++ b/src/features/listings/ui/listingsCardDetail/infra/components/roomTypeDetail.tsx
@@ -78,8 +78,8 @@ export const RoomTypeDetail = ({ listingId }: { listingId: string }) => {
{current?.thumbnail ? (
-
+
)}
diff --git a/src/features/listings/ui/listingsFilter/hooks.ts b/src/features/listings/ui/listingsFilter/hooks.ts
new file mode 100644
index 0000000..e987c5b
--- /dev/null
+++ b/src/features/listings/ui/listingsFilter/hooks.ts
@@ -0,0 +1,25 @@
+import { useFilterSheetStore, useListingsFilterStore } from "@/src/features/listings/model";
+import { useRouter, useSearchParams } from "next/navigation";
+
+export const ListingHooks = () => {
+ const openSheet = useFilterSheetStore(state => state.openSheet);
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const params = new URLSearchParams(searchParams.toString());
+ const onOpenSheet = () => {
+ openSheet();
+ router.push(`listings?${params}`);
+ };
+
+ const hasSelectedFilters = useListingsFilterStore(state =>
+ [state.regionType, state.rentalTypes, state.supplyTypes, state.houseTypes].some(
+ list => list.length > 0
+ )
+ );
+
+ return {
+ openSheet,
+ hasSelectedFilters,
+ onOpenSheet,
+ };
+};
diff --git a/src/features/listings/ui/listingsFilter/listingsFilterPanel.tsx b/src/features/listings/ui/listingsFilter/listingsFilterPanel.tsx
index c45d807..d05970c 100644
--- a/src/features/listings/ui/listingsFilter/listingsFilterPanel.tsx
+++ b/src/features/listings/ui/listingsFilter/listingsFilterPanel.tsx
@@ -1,25 +1,12 @@
"use client";
-import { useRouter, useSearchParams } from "next/navigation";
+
import { FILTER_OPTIONS, filterMap, getAllFilterIcon } from "../../model";
-import { useFilterSheetStore, useListingsFilterStore } from "../../model/listingsStore";
+import { useListingsFilterStore } from "../../model/listingsStore";
import { ListingTagButton } from "../listingsButton/listingsTagButton";
+import { ListingHooks } from "@/src/features/listings/ui/listingsFilter/hooks";
export const ListingFilterPanel = () => {
- const openSheet = useFilterSheetStore(state => state.openSheet);
- const router = useRouter();
- const searchParams = useSearchParams();
- const params = new URLSearchParams(searchParams.toString());
- params.set("tab", params.get("tab") ?? "region");
- const onOpenSheet = () => {
- openSheet();
- router.push(`listings?${params}`);
- };
-
- const hasSelectedFilters = useListingsFilterStore(state =>
- [state.regionType, state.rentalTypes, state.supplyTypes, state.houseTypes].some(
- list => list.length > 0
- )
- );
+ const { onOpenSheet, hasSelectedFilters } = ListingHooks();
return (
diff --git a/src/features/listings/ui/listingsFullSheet/hooks.ts b/src/features/listings/ui/listingsFullSheet/hooks.ts
new file mode 100644
index 0000000..970c734
--- /dev/null
+++ b/src/features/listings/ui/listingsFullSheet/hooks.ts
@@ -0,0 +1,92 @@
+import { useFilterSheetStore, useHasRouter } from "@/src/features/listings/model";
+import { useRouter, useSearchParams } from "next/navigation";
+import { useCallback, useEffect, useRef, useState } from "react";
+import { useListingListInfiniteQuery } from "@/src/entities/listings/hooks/useListingHooks";
+
+const useListingTotal = () => {
+ const { data } = useListingListInfiniteQuery();
+ const prevTotalRef = useRef
(null);
+ const newTotal = data?.pages?.[0]?.totalCount;
+
+ if (newTotal !== undefined && newTotal !== null) {
+ prevTotalRef.current = newTotal;
+ }
+
+ return prevTotalRef.current;
+};
+
+const useScrollBottom = () => {
+ const scrollRef = useRef(null);
+ const [isAtBottom, setIsAtBottom] = useState(true);
+
+ // useEffect 의존성을 안정화해서 불필요한 cleanup 반복을 막는다.
+ const handleScroll = useCallback(() => {
+ const el = scrollRef.current;
+ if (!el) return;
+ // 스크롤 위치에 따라 하단 그라데이션 표시를 정확히 맞추기 위함.
+ const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 5;
+ setIsAtBottom(atBottom);
+ }, []);
+
+ return { scrollRef, isAtBottom, handleScroll };
+};
+
+const useListingsTabSync = (open: boolean, handleScroll: () => void) => {
+ const searchParams = useSearchParams();
+ const { setHasListingsTab, reset } = useHasRouter();
+
+ useEffect(() => {
+ if (!open) return;
+
+ // 시트가 열릴 때 URL 상태를 반영해 하단 네비 같은 UI 분기 기준을 맞춘다.
+ handleScroll();
+ const hasTab = searchParams.has("tab");
+ setHasListingsTab(hasTab);
+
+ // 시트가 닫힐 때 router 관련 상태를 원복한다.
+ return () => {
+ reset();
+ };
+ }, [open, handleScroll, searchParams, setHasListingsTab, reset]);
+};
+
+export const ListingFilterPartialSheetHooks = () => {
+ const open = useFilterSheetStore(s => s.open);
+ const closeSheet = useFilterSheetStore(s => s.closeSheet);
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const displayTotal = useListingTotal();
+ const { scrollRef, isAtBottom, handleScroll } = useScrollBottom();
+ useListingsTabSync(open, handleScroll);
+
+ const resetListingsQuery = () => {
+ try {
+ // tab만 제거하고 나머지 쿼리는 그대로 유지한다.
+ const params = new URLSearchParams(searchParams.toString());
+ params.delete("tab");
+ const query = params.toString();
+ router.replace(query ? `/listings?${query}` : "/listings", { scroll: false });
+ } catch (error) {
+ console.error("[ListingFilterPartialSheet] Failed to reset query", error);
+ }
+ };
+
+ const handleCloseSheet = () => {
+ try {
+ // UI를 먼저 닫고, 이어서 URL을 정리하는 흐름으로 고정한다.
+ closeSheet();
+ resetListingsQuery();
+ } catch (error) {
+ console.error("[ListingFilterPartialSheet] Failed to close sheet", error);
+ }
+ };
+
+ return {
+ open,
+ scrollRef,
+ isAtBottom,
+ displayTotal,
+ handleScroll,
+ handleCloseSheet,
+ };
+};
diff --git a/src/features/listings/ui/listingsFullSheet/listingsFullSheet.tsx b/src/features/listings/ui/listingsFullSheet/listingsFullSheet.tsx
index 5f16da2..e23796e 100644
--- a/src/features/listings/ui/listingsFullSheet/listingsFullSheet.tsx
+++ b/src/features/listings/ui/listingsFullSheet/listingsFullSheet.tsx
@@ -1,62 +1,16 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
-import {
- useFilterSheetStore,
- useHasRouter,
- useListingsFilterStore,
-} from "../../model/listingsStore";
+import { useListingsFilterStore } from "../../model/listingsStore";
import { FILTER_TABS, FilterTabKey, TAB_CONFIG } from "../../model";
-import { useEffect, useRef, useState } from "react";
import { CloseButton } from "@/src/assets/icons/button";
import { useRouter, useSearchParams } from "next/navigation";
import { getIndicatorLeft, getIndicatorWidth } from "../../hooks/listingsHooks";
-import { useListingListInfiniteQuery } from "@/src/entities/listings/hooks/useListingHooks";
import { Checkbox } from "@/src/shared/lib/headlessUi/checkBox/checkbox";
+import { ListingFilterPartialSheetHooks } from "./hooks";
export const ListingFilterPartialSheet = () => {
- const open = useFilterSheetStore(s => s.open);
- const closeSheet = useFilterSheetStore(s => s.closeSheet);
- const router = useRouter();
- const searchParams = useSearchParams();
- const scrollRef = useRef(null);
- const [isAtBottom, setIsAtBottom] = useState(true);
- const { data } = useListingListInfiniteQuery();
- const prevTotalRef = useRef(null);
- const newTotal = data?.pages?.[0]?.totalCount;
- const { setHasListingsTab, reset } = useHasRouter();
-
- if (newTotal !== undefined && newTotal !== null) {
- prevTotalRef.current = newTotal;
- }
- const displayTotal = prevTotalRef.current;
- const handleScroll = () => {
- const el = scrollRef.current;
- if (!el) return;
- const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 5;
- setIsAtBottom(atBottom);
- };
- useEffect(() => {
- if (open) {
- handleScroll();
- const hasTab = window.location.search.includes("tab=");
- setHasListingsTab(hasTab);
-
- return () => {
- reset();
- };
- }
- }, [open]);
- const resetListingsQuery = () => {
- const params = new URLSearchParams(searchParams.toString());
- params.delete("tab");
- const query = params.toString();
- router.replace(query ? `/listings?${query}` : "/listings", { scroll: false });
- };
-
- const handleCloseSheet = () => {
- closeSheet();
- resetListingsQuery();
- };
+ const { open, scrollRef, isAtBottom, displayTotal, handleScroll, handleCloseSheet } =
+ ListingFilterPartialSheetHooks();
return (
diff --git a/src/features/mypage/hooks/useAddPinpoint.ts b/src/features/mypage/hooks/useAddPinpoint.ts
index 6ddd54a..feac8ec 100644
--- a/src/features/mypage/hooks/useAddPinpoint.ts
+++ b/src/features/mypage/hooks/useAddPinpoint.ts
@@ -1,6 +1,6 @@
"use client";
-import { useMutation } from "@tanstack/react-query";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import { requestSetPinpoint } from "@/src/entities/address/api";
export interface IPinpointData {
@@ -18,10 +18,14 @@ interface IUseAddPinpointParams {
* 핀포인트만 추가하는 커스텀 훅 (마이페이지 등에서 사용)
*/
export const useAddPinpoint = ({ onSuccess, onError }: IUseAddPinpointParams = {}) => {
+ const queryClient = useQueryClient();
+
const mutation = useMutation({
mutationFn: (data: IPinpointData) => requestSetPinpoint(data),
onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["pinpointSettings"] });
onSuccess?.();
+
},
onError: error => {
console.error("핀포인트 추가 실패:", error);
diff --git a/src/widgets/homeSection/hooks/homeHeaderHooks.ts b/src/widgets/homeSection/hooks/homeHeaderHooks.ts
new file mode 100644
index 0000000..3a57532
--- /dev/null
+++ b/src/widgets/homeSection/hooks/homeHeaderHooks.ts
@@ -0,0 +1,13 @@
+import { useRouter } from "next/navigation";
+import { useCallback } from "react";
+
+export const useHomeHeaderHooks = () => {
+ const router = useRouter();
+
+ const onRouteChange = useCallback(() => {
+ router.push("/home/search");
+ },[])
+ return {
+ onRouteChange,
+ }
+}
\ No newline at end of file