diff --git a/src/components/Alert/Alert.tsx b/src/components/Alert/Alert.tsx index 4e42e5e..7d8f921 100644 --- a/src/components/Alert/Alert.tsx +++ b/src/components/Alert/Alert.tsx @@ -1,7 +1,7 @@ -import { useMemo, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import AlertCard from "./AlertCard"; -import useAlarm from "./hooks/useAlarm"; +import useAlerts from "./hooks/useAlerts"; import { Active as ActiveAlarmIcon, @@ -10,25 +10,29 @@ import { } from "@/assets/icon"; import useIntersection from "@/hooks/useIntersection"; import useOutsideClick from "@/hooks/useOutsideClick"; +import useRemoveTopPageScroll from "@/hooks/useRemoveTopPageScroll"; interface AlertProps { userId: string; } function Alert({ userId }: AlertProps) { - const [showDropdown, setShowDropdown] = useState(false); - const { isLoading, hasNext, refetch, alerts, totalCount } = useAlarm({ - userId, - }); - - const hasAlarm = useMemo( - () => alerts.filter(({ read }) => !read).length > 0, - [alerts], - ); - const AlarmIcon = hasAlarm ? ActiveAlarmIcon : InactiveAlarmIcon; + const { isLoading, hasNext, hasAlarm, refetch, alerts, totalCount } = + useAlerts({ + userId, + }); + const [showDropdown, setShowDropdown] = useState(false); + const [hasUnreadAlarm, setHasUnreadAlarm] = useState(false); const buttonRef = useRef(null); const wrapperRef = useRef(null); + const targetRef = useIntersection({ + callback: ([entry]) => { + if (entry.isIntersecting && !isLoading && hasNext) { + refetch(); + } + }, + }); const onToggleAlarm = () => { setShowDropdown((prev) => !prev); @@ -39,14 +43,19 @@ function Alert({ userId }: AlertProps) { callback: () => setShowDropdown(false), }); - const targetRef = useIntersection({ - callback: ([entry]) => { - if (entry.isIntersecting && !isLoading && hasNext) { - refetch(); - } - }, + useRemoveTopPageScroll({ + observeDevices: ["mobile"], + condition: showDropdown, }); + useEffect(() => { + setHasUnreadAlarm(hasAlarm); + }, [hasAlarm]); + + useEffect(() => { + setHasUnreadAlarm(false); + }, [showDropdown]); + return (
{showDropdown && (

- 알림 {totalCount}개{" "} + 알림 {totalCount}개

    - {alerts.map((alert, index) => ( - + {alerts.map((alert) => ( + ))} +
    {isLoading && "로딩 중 ..."}
diff --git a/src/components/Alert/AlertCard.tsx b/src/components/Alert/AlertCard.tsx index 1e8e60d..0c9d8a2 100644 --- a/src/components/Alert/AlertCard.tsx +++ b/src/components/Alert/AlertCard.tsx @@ -1,4 +1,4 @@ -import { forwardRef, Ref, useState } from "react"; +import { forwardRef, Ref } from "react"; import { putAlert } from "@/apis/services/alertService"; import useIntersection from "@/hooks/useIntersection"; @@ -14,16 +14,13 @@ interface AlertCardProps { const AlertCard = forwardRef( ({ alert, userId }: AlertCardProps, ref: Ref) => { const { id, shop, notice, read, result, createdAt } = alert; - const [readStatus, setReadStatus] = useState(read); + + const isAccepted = result === "accepted"; const targetRef = useIntersection({ callback: async ([entry]) => { if (entry.isIntersecting && userId && !read) { - const result = await putAlert(userId, id); - - if (result.status === 200) { - setReadStatus(result.data.items[0].item.read); - } + await putAlert(userId, id); } }, }); @@ -36,17 +33,15 @@ const AlertCard = forwardRef(

{shop.item.name}( {formatTimeRange(notice.item.startsAt, notice.item.workhour)}) 공고 지원이{" "} - - {result === "accepted" ? "승인" : "거절"} + + {isAccepted ? "승인" : "거절"} 되었어요.

diff --git a/src/components/Alert/hooks/useAlarm.ts b/src/components/Alert/hooks/useAlerts.ts similarity index 72% rename from src/components/Alert/hooks/useAlarm.ts rename to src/components/Alert/hooks/useAlerts.ts index f093ff5..6a35767 100644 --- a/src/components/Alert/hooks/useAlarm.ts +++ b/src/components/Alert/hooks/useAlerts.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { getAlerts } from "@/apis/services/alertService"; import { AlertItem } from "@/types/alert"; @@ -9,18 +9,17 @@ interface UseAlarmParams { limit?: number; } -const useAlarm = ({ userId, offset = 5, limit = 5 }: UseAlarmParams) => { +const useAlerts = ({ userId, offset = 10, limit = 10 }: UseAlarmParams) => { const [isLoading, setIsLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); const [alerts, setAlerts] = useState([]); const [hasNext, setHasNext] = useState(false); + const [hasAlarm, setHasAlarm] = useState(false); - const hasFetched = useRef(false); const pageRef = useRef(1); - const fetchAlerts = async () => { + const fetchAlerts = useCallback(async () => { setIsLoading(true); - try { const fetchedAlerts = await getAlerts( userId, @@ -29,8 +28,11 @@ const useAlarm = ({ userId, offset = 5, limit = 5 }: UseAlarmParams) => { ); const nextAlerts = fetchedAlerts.data.items.map(({ item }) => item); + const hasUnreadAlert = nextAlerts.filter(({ read }) => !read).length > 0; + setAlerts((prev) => [...prev, ...nextAlerts]); setHasNext(fetchedAlerts.data.hasNext); + setHasAlarm(hasUnreadAlert); setTotalCount(fetchedAlerts.data.count); if (fetchedAlerts.data.hasNext) { @@ -39,22 +41,20 @@ const useAlarm = ({ userId, offset = 5, limit = 5 }: UseAlarmParams) => { } finally { setIsLoading(false); } - }; + }, [userId, offset, limit]); useEffect(() => { - if (hasFetched.current) return; - hasFetched.current = true; - fetchAlerts(); - }, [userId, offset, limit]); + }, [userId, fetchAlerts]); return { refetch: fetchAlerts, - hasNext, alerts, - isLoading, totalCount, + hasNext, + hasAlarm, + isLoading, }; }; -export default useAlarm; +export default useAlerts; diff --git a/src/components/Dropdown/FilterDropdownContent.tsx b/src/components/Dropdown/FilterDropdownContent.tsx index 58f2647..ad0a1b4 100644 --- a/src/components/Dropdown/FilterDropdownContent.tsx +++ b/src/components/Dropdown/FilterDropdownContent.tsx @@ -13,11 +13,13 @@ import { extractDigits, numberCommaFormatter } from "@/utils/number"; import "react-datepicker/dist/react-datepicker.css"; interface FilterDropdownContentProps { - onClickApplyButton: () => void; + refetch: () => void; onClose: () => void; + onClickApplyButton: () => void; } function FilterDropdownContent({ + refetch, onClose, onClickApplyButton, }: FilterDropdownContentProps) { @@ -75,11 +77,12 @@ function FilterDropdownContent({ setPayFilter(null); setStartDateFilter(null); setAreasFilter([]); + refetch(); }; return (
-
+
상세 필터