From 11991b5966aba4d4256ed3524c579764776df952 Mon Sep 17 00:00:00 2001 From: howu <67588757+choihooo@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:39:59 +0900 Subject: [PATCH] =?UTF-8?q?=ED=95=91=20=EC=83=88=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feature] 업데이트 기능 구현 * [feature] 빌드 오류 수정 --- fe/public/pin/recommendPing.svg | 26 +++-- fe/public/profile/Card/Map/pin.png | Bin 0 -> 2251 bytes fe/public/profile/Card/Map/pin.svg | 11 ++ fe/public/profile/level4.svg | 30 ++---- .../[id]/components/BottomDrawer.tsx | 101 +++++++++++++++--- .../[id]/components/MapComponent.tsx | 1 + .../[id]/components/RecommendInActive.tsx | 9 +- .../event-maps/[id]/components/StoreItem.tsx | 69 ++++++------ .../[id]/hooks/useDidmountEffect.ts | 26 +++++ fe/src/app/event-maps/[id]/page.tsx | 38 ++++++- .../event-maps/[id]/stores/useUpdateTime.ts | 19 ++++ fe/tailwind.config.ts | 12 ++- 12 files changed, 267 insertions(+), 75 deletions(-) create mode 100644 fe/public/profile/Card/Map/pin.png create mode 100644 fe/public/profile/Card/Map/pin.svg create mode 100644 fe/src/app/event-maps/[id]/hooks/useDidmountEffect.ts create mode 100644 fe/src/app/event-maps/[id]/stores/useUpdateTime.ts diff --git a/fe/public/pin/recommendPing.svg b/fe/public/pin/recommendPing.svg index 4243960..bdb26bd 100644 --- a/fe/public/pin/recommendPing.svg +++ b/fe/public/pin/recommendPing.svg @@ -1,7 +1,21 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/fe/public/profile/Card/Map/pin.png b/fe/public/profile/Card/Map/pin.png new file mode 100644 index 0000000000000000000000000000000000000000..c16697caaa159dcda075cee8db5944c9dedf4dfe GIT binary patch literal 2251 zcmV;+2sHPJP)K~#7F)mlw# z8`lw@*?qgqUyHWoxOC}=7U&ud3mc6NZcrd~PXSuAMFYsa21rmKKrcP_Ao^CImlg=n zLyMw73-mxBm!5L6;UYi}5mFnKVAqU7JG7NVwk(RI$o+kpdAm!Iw3GxUMMod8-1oaX z@4b05^JW%de<1%!DdFnXt5~U2C|3b>BP=Z~$*>$$Sy(%ap|YqQRF?RWfEe~2YF`*)AWGPe$Qblwm1n|MR#tFrZB1M5^{`~Wh&8W^#i(7KesNj|A!}Yi_3PK?Z7i-^ zGHh4uKfgZEW;j3GlFLiylhV3>UnUX>xx73dR4Qf31BwR1?~|hgK%SZ_SFUKQtDrZV z4ZYXf6mH5Ca?8i9n9g`baB$|C_zyp9i!FWIeAIT$B-#n-N>c`&4yK_|Lo`96 ztvb?pDQ!`j^H%h7dw>wfainRQ$ZgeoqV`UdkDoONi|QOE&T#|Od>;+zh$h%A>x5m( z9dQbUu7B;?H9sDJVB!gsT+~XXwy42fEA`k&8(#BUcI)w*LE!v?2tNgT5U7Y8xx+s7 zd*$t~YL9paL0{F=xjzCLcWRp+*5H^;EFM6_LJG7&wS|QR17^=fMMM zJ^u^w0nzmH)Z9WIW`B@{a@k~>)foA2adA*YGY#q!D;JWB5C>V+HVGle|07e zzdii|ym}-J)5NWv9{lQV18#PDVF9tR+j{V3GBfkLUOM9}EUbASd>{igrPnt$v|Ps4 zrQ4$o8=Q|Hav`0B_sVCcPP}?|DgpnwSW*+%N8r6KJ=<`*+k(YA7x5;BvuDp@%}wZ5 zfC6N2E^hzlv)}Ib-J)f^wbO-ZV9qk>GE|yZ|3>rUu!2@Wii9Bp2TLmsvc3{5fr`Z< zX8f$?O25zKGsN>e3ygcV3*Wz4dzvo~2>kBbFMmhvJ6?Uh_uoxYa~lFYR`hUbsUkHJ zL0Bhk+8%jGPS^YCd$uz*;u%eP7w>@>gTg~H+iwS02eue0sIiSx^gINuBjFIhGz`q7 z11xxXwFk5c{}+pp3DRhq65$&T$8~9*uAmc*_9~cAQRr$>{bI{7J~mj1hTxw zW}$z4z6Jl@XO>x-O$rS-P5*C2dw^Jo6xc-g|L>na*;8V18P`n)Stvv)QBnUwZ447U zm;`=*CckI-JL`{OTv%*`L!dS>AG*R7ATg@g%7(FVk<5?%Fqbf3T!bXHh=)FbYPBqt zhA7h2W<4;z5c*P!^+GBM<0CvV6bh%+CT2~n&Y-LZU;>EEkG&AvH{-w=;gV1jvlIdr zqd2x0olBU{RQGjZTFfn)YzRxy+LuC#uO@&C>4RMX26Icnv&nPv5{f0VkNyViY#=FQ zdSkQEFK^$U!pV&%J3*UxRSKdQi@?gQMf~;bo9w;se*J?FYvkAd_pBP3*-${tLad2H zz9*j#Zuy76B6XnMv1LId@U2QXfRJtIKp@AkM)#W0Snl8JP0oa1usJQnx>RDc5{Xeh z&`d%K4dV98=hQV(L}JvV^FXKvAXH@8Lvo-QRkpkWq1%r{lRN|mno;lysPpBd^uR%5 z)Y|E@&_RGrXm-a;60s%GKT;C^$TJWsGRv8)7=3MpCDVktl1E^Wn?II9w3VYbv|!SE z;1;{{m0-e-5- zk3&lA+Gh9q^Tef_AMFYP)b1oslz*H^wKlhe=t8aLXvFmL@`)3{6Or^aPdcE9cjM0N zMY}{^=|M;9w4h9G62rq%3|Gu280SaU%ea@YJc&Y&5UhiPyN+x-hVw_ z1r~*u{#*M-gqTG7*dKIHa~^d%-j7~+g(H<9I6%p}`ClhbdW~M&Nx|%=f!Tc9cbb11 zj82AHb$TxLQQ{$a{B1Kad$;ZF^cFsCc;#|gQp`9MIvi@HK~R{rWF4Jfm`Tl^&qHSR zhp3Bp5w+X!oT8I7!w)pwddJAkeUBrQOj`*`76&Ec*!EDQo+?zJyS91P%9vTp*L_QR zKp`DXfD!ch#{>f%aEbd26}WCWuFRSS1+ASZ0B&IDzZv#I>=g{(Mc7yMq36-8%LEGF z@m;%X2^-vAuQcg+ck$w-Vu}2CMo?v#CQbEpnNp?z=s`CDYjdWI%;;*m})| zBPT7+zExH*>@i@sM`py=+NSoIsJ+UMv_a}CRo{-xay!(erkjDWv*9HY9q%Nu`10~H ze`XNG#E-5(|5?$u2-;;zUoVyPqF0Da?$C1Obiu1(=pm*uGOSoLs)IJUJ2=lR>y&0E zEL)E1PW9{#fhY|l^$?|tR43{U2_-7875wXPorxWe&d0>UKIrophBr1j)F(HKiwNYC zj6c6&2W#A?T;@&1?N*S#F4_IyCcYr5tICy?MXXjiUzYp~-jrMii_|FGoHCkBIXL5^ Z_yV?5K7|*Kg|`3z002ovPDHLkV1f_lHBSHl literal 0 HcmV?d00001 diff --git a/fe/public/profile/Card/Map/pin.svg b/fe/public/profile/Card/Map/pin.svg new file mode 100644 index 0000000..c99f27d --- /dev/null +++ b/fe/public/profile/Card/Map/pin.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/fe/public/profile/level4.svg b/fe/public/profile/level4.svg index 9fb321f..db4e807 100644 --- a/fe/public/profile/level4.svg +++ b/fe/public/profile/level4.svg @@ -1,23 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx index 1ac7657..b9eaf12 100644 --- a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx +++ b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx @@ -8,6 +8,7 @@ import { RecommendButton } from "./RecommendButton"; import { RecommendInActive } from "./RecommendInActive"; import LocationButton from "./LocationButton"; import { RecommendActive } from "./RecommendActive"; +import useUpdateTimeStore from "../stores/useUpdateTime"; interface NonMember { nonMemberId: number; @@ -32,6 +33,15 @@ interface BottomDrawerProps { } interface RecommendPing { + placeName: string; + sid: string; + px: number; + py: number; + url: string; + type: string; +} + +interface CustomRecommendPing { iconLevel: number; placeName: string; sid: string; @@ -50,10 +60,13 @@ export function BottomDrawer({ const [selectedButton, setSelectedButton] = useState(null); const [nonMembers, setNonMembers] = useState(initialNonMembers); const [allPings, setAllPings] = useState([]); + const [recommendPings, setrecommendPings] = useState([]); const [isRecommend, setIsRecommend] = useState(false); const [isRecommended, setIsRecommended] = useState(false); const [neighborhood, setNeighborhood] = useState(""); const [nonRecommend, setNonRecommend] = useState(false); + const [trigger, setTrigger] = useState(false); + const { setUpdateTime } = useUpdateTimeStore(); const { setCustomMarkers } = useMarkerStore(); const moveToLocation = useLocationStore((state) => state.moveToLocation); @@ -73,7 +86,7 @@ export function BottomDrawer({ if (data.recommendPings && data.recommendPings.length > 0) { setIsRecommended(true); recommendProfile = data.recommendPings.map( - (ping: RecommendPing) => ({ + (ping: CustomRecommendPing) => ({ iconLevel: 10, // Fixed icon level nonMembers: [ { @@ -104,13 +117,14 @@ export function BottomDrawer({ ...(recommendProfile || []), // recommendProfile 추가 ]); setNeighborhood(data.neighborhood); + console.log(data); } } catch (error) { console.error("Error:", error); } }; fetchAllPings(); - }, [apiUrl, id, setCustomMarkers]); + }, [apiUrl, id, setCustomMarkers, trigger]); const handleLocationClick = () => { if (navigator.geolocation) { @@ -124,11 +138,38 @@ export function BottomDrawer({ } }; - const handleRecommendAllowClick = () => { - setIsRecommend(true); + const handleAddToMorphing = async () => { + const uuid = id; // id를 uuid로 설정 + const sids = recommendPings.map((item) => item.sid); // sid 값만 추출 + + const requestBody = { + uuid, // 문자열로 uuid 설정 + sids, // 추출한 sid 배열 + }; + + try { + const response = await fetch(`${apiUrl}/nonmembers/pings/recommend`, { + method: "POST", + headers: { + "Content-Type": "application/json;charset=UTF-8", + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const result = await response.json(); // 응답 데이터 처리 + console.log("Recommended Data Response:", result); + setTrigger((prev) => !prev); + setIsRecommend(false); // 추천 모드 비활성화 + } catch (error) { + console.error("Error fetching recommended data:", error); + } }; - const handleAddToMorphing = async () => { + const handleRecommendShowClick = async () => { const Km = 1.0; let found = false; @@ -139,11 +180,13 @@ export function BottomDrawer({ ); if (response.ok) { const data = await response.json(); + console.log(data); if (data.recommendPings.length === 0) { setNonRecommend(true); } else if (data.recommendPings.length >= 5) { setCustomMarkers(data.recommendPings); found = true; + setrecommendPings(data.recommendPings); setIsRecommend(found); } } else { @@ -181,14 +224,48 @@ export function BottomDrawer({ const handleRefresh = async () => { try { const response = await fetch( - `${apiUrl}/nonmembers/pings/refresh?uuid=${id}` + `${apiUrl}/nonmembers/pings/refresh-all?uuid=${id}`, + { method: "GET" } ); if (response.ok) { const data = await response.json(); - setEventName(data.eventName); - setNonMembers(data.nonMembers); - setAllPings(data.pings || []); - setCustomMarkers(data.pings || []); + let recommendProfile = []; + if (data.recommendPings && data.recommendPings.length > 0) { + setIsRecommended(true); + + recommendProfile = data.recommendPings.map( + (ping: CustomRecommendPing) => ({ + iconLevel: 10, // Fixed icon level + nonMembers: [ + { + nonMemberId: -1, + name: "추천 모핑", // Fixed name + profileSvg: "/profile/recommendProfile.svg", // Fixed profileSvg + }, + ], + url: ping.url, + placeName: ping.placeName, + px: ping.px, + py: ping.py, + type: ping.type, + }) + ); + } + setEventName(data.eventName || ""); + setNonMembers([ + ...(recommendProfile[0]?.nonMembers || []), + ...(data.nonMembers || []), + ]); + setAllPings([ + ...(data.pings || []), // 기존 pings + ...(recommendProfile || []), // recommendProfile 추가 + ]); + setCustomMarkers([ + ...(data.pings || []), // 기존 pings + ...(recommendProfile || []), // recommendProfile 추가 + ]); + setNeighborhood(data.neighborhood); + setUpdateTime(data.pingLastUpdateTime); } else { console.error("Failed to fetch refreshed data:", response.status); } @@ -201,11 +278,11 @@ export function BottomDrawer({
{!isRecommend && !isRecommended && (
- +
)}
diff --git a/fe/src/app/event-maps/[id]/components/MapComponent.tsx b/fe/src/app/event-maps/[id]/components/MapComponent.tsx index 9dde6de..51ee072 100644 --- a/fe/src/app/event-maps/[id]/components/MapComponent.tsx +++ b/fe/src/app/event-maps/[id]/components/MapComponent.tsx @@ -319,6 +319,7 @@ export default function MapComponent({ false ) ); + previousMarkerIndexRef.current = null; } diff --git a/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx b/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx index efa69ed..19b38f1 100644 --- a/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx +++ b/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx @@ -49,6 +49,9 @@ export function RecommendInActive({ }: RecommendInActiveProps): JSX.Element { const router = useRouter(); + // iconLevel에 따라 내림차순으로 정렬 + const sortedPings = [...allPings].sort((a, b) => b.iconLevel - a.iconLevel); + return ( <>
@@ -91,14 +94,16 @@ export function RecommendInActive({ ))}
- {allPings.map((ping, index) => ( + {sortedPings.map((ping, index) => ( ))}
diff --git a/fe/src/app/event-maps/[id]/components/StoreItem.tsx b/fe/src/app/event-maps/[id]/components/StoreItem.tsx index 742c296..a5bc2b0 100644 --- a/fe/src/app/event-maps/[id]/components/StoreItem.tsx +++ b/fe/src/app/event-maps/[id]/components/StoreItem.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from "react"; +import React, { forwardRef, useState } from "react"; import Image from "next/image"; interface StoreItemProps { @@ -9,38 +9,45 @@ interface StoreItemProps { } const StoreItem = forwardRef( - ({ name, type, url, iconLevel }, ref) => ( -
-
- { - e.currentTarget.src = "/profile/default.svg"; // Fallback to a default image - }} - alt="edit" - width={40} - height={40} - /> -
-
{name}
-
{type}
+ ({ name, type, url, iconLevel }, ref) => { + const [imageSrc, setImageSrc] = useState(`/profile/level${iconLevel}.svg`); + + return ( +
+
+ setImageSrc("/profile/recommendProfile.svg")} + alt="pin" + width={40} + height={40} + /> +
+
{name}
+
{type}
+
+
- -
- ) + ); + } ); export default StoreItem; diff --git a/fe/src/app/event-maps/[id]/hooks/useDidmountEffect.ts b/fe/src/app/event-maps/[id]/hooks/useDidmountEffect.ts new file mode 100644 index 0000000..57ca749 --- /dev/null +++ b/fe/src/app/event-maps/[id]/hooks/useDidmountEffect.ts @@ -0,0 +1,26 @@ +import { useEffect, useRef } from "react"; + +/** + * Custom hook to execute a function only when the dependencies change after the first render. + * + * @param func - Function to execute on dependency change + * @param deps - Dependency array + */ +const useDidMountEffect = ( + func: () => void, + deps: React.DependencyList +): void => { + const didMount = useRef(false); + + useEffect(() => { + if (didMount.current) { + func(); + } else { + didMount.current = true; + } + // Ensure 'func' is included in the dependencies + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); +}; + +export default useDidMountEffect; diff --git a/fe/src/app/event-maps/[id]/page.tsx b/fe/src/app/event-maps/[id]/page.tsx index 6e64051..61c0459 100644 --- a/fe/src/app/event-maps/[id]/page.tsx +++ b/fe/src/app/event-maps/[id]/page.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; +import Image from "next/image"; import { a } from "@react-spring/web"; import { useDrag } from "@use-gesture/react"; import MapComponent from "./components/MapComponent"; @@ -10,6 +11,8 @@ import useDrawer from "./hooks/useDrawer"; import { useLocationStore } from "./stores/useLocationStore"; import { useMarkerStore } from "./load-mappin/stores/useMarkerStore"; import ExitModal from "./components/EventMapExitModal"; +import useUpdateTimeStore from "./stores/useUpdateTime"; +import useDidMountEffect from "./hooks/useDidmountEffect"; interface NonMember { nonMemberId: number; @@ -39,10 +42,13 @@ export default function Page() { const { id } = useParams(); const parsedId = Array.isArray(id) ? id[0] : id; const [data, setData] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isVisible, setIsVisible] = useState(false); + const [isFadingOut, setIsFadingOut] = useState(false); const moveToLocation = useLocationStore((state) => state.moveToLocation); const setCustomMarkers = useMarkerStore((state) => state.setCustomMarkers); const router = useRouter(); + const { updateTime, trigger } = useUpdateTimeStore(); useEffect(() => { const fetchData = async () => { @@ -96,6 +102,25 @@ export default function Page() { setIsModalOpen(false); }; + useDidMountEffect(() => { + // 메시지를 표시하고, 일정 시간 후에 숨깁니다. + setIsVisible(true); + + const fadeOutTimer = setTimeout(() => { + setIsFadingOut(true); + }, 4000); + + const hideTimer = setTimeout(() => { + setIsVisible(false); + setIsFadingOut(false); // 상태 초기화 + }, 4500); + + return () => { + clearTimeout(fadeOutTimer); + clearTimeout(hideTimer); + }; // 타이머 정리 + }, [updateTime, trigger]); // updateTime이 변경될 때만 실행 + const bind = useDrag( ({ last, movement: [, my], memo = y.get() }) => { if (last) { @@ -123,6 +148,17 @@ export default function Page() { return (
+ {isVisible && ( +
+ 핑 +
{updateTime} 전 맵핀이 업데이트 됐어요!
+
+ )}