From f4e2e992c25a555836ac06c33da7c65c179884bc Mon Sep 17 00:00:00 2001 From: choihooo Date: Sun, 24 Nov 2024 01:56:37 +0900 Subject: [PATCH] =?UTF-8?q?[feature]=20=EB=B9=8C=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[id]/components/BottomDrawer.tsx | 121 +----- .../[id]/components/ButtonComponents.tsx | 78 ++-- .../[id]/components/LocationButton.tsx | 20 +- .../[id]/components/MapComponent.tsx | 381 +++++++++--------- .../[id]/components/RecommendActive.tsx | 25 +- .../[id]/components/RecommendButton.tsx | 26 +- .../[id]/components/RecommendInActive.tsx | 111 ++--- .../event-maps/[id]/components/StoreItem.tsx | 3 +- 8 files changed, 352 insertions(+), 413 deletions(-) diff --git a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx index d7fc0fe..d3e2b20 100644 --- a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx +++ b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx @@ -1,12 +1,13 @@ import React, { useState, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; + import Image from "next/image"; import { useLocationStore } from "../stores/useLocationStore"; import { useMarkerStore } from "../load-mappin/stores/useMarkerStore"; -import RecommendButton from "./RecommendButton"; -import RecommendInActive from "./RecommendInActive"; +import { RecommendButton } from "./RecommendButton"; +import { RecommendInActive } from "./RecommendInActive"; import LocationButton from "./LocationButton"; -import RecommendActive from "./RecommendActive"; +import { RecommendActive } from "./RecommendActive"; interface NonMember { nonMemberId: number; @@ -40,11 +41,11 @@ interface RecommendPing { type: string; } -const BottomDrawer: React.FC = ({ +export function BottomDrawer({ nonMembers: initialNonMembers, eventName: initialEventName, id, -}) => { +}: BottomDrawerProps): JSX.Element { const [eventName, setEventName] = useState(initialEventName); const [selectedButton, setSelectedButton] = useState(null); const [nonMembers, setNonMembers] = useState(initialNonMembers); @@ -58,7 +59,6 @@ const BottomDrawer: React.FC = ({ const moveToLocation = useLocationStore((state) => state.moveToLocation); const router = useRouter(); - const observer = useRef(); const lastPingElementRef = useRef(null); const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL; @@ -69,11 +69,10 @@ const BottomDrawer: React.FC = ({ const response = await fetch(`${apiUrl}/nonmembers/pings?uuid=${id}`); if (response.ok) { const data = await response.json(); - let recommendProfile; + let recommendProfile = []; if (data.recommendPings && data.recommendPings.length > 0) { setIsRecommended(true); - console.log(isRecommended); - recommendProfile = (data.recommendPings || []).map( + recommendProfile = data.recommendPings.map( (ping: RecommendPing) => ({ iconLevel: 10, // Fixed icon level nonMembers: [ @@ -90,30 +89,24 @@ const BottomDrawer: React.FC = ({ type: ping.type, }) ); - console.log("리코멘트 프로필" + JSON.stringify(recommendProfile)); - } else { - recommendProfile = []; // Default value when no recommendPings } setEventName(data.eventName || ""); setNonMembers([ - ...(recommendProfile[0].nonMembers || []), + ...(recommendProfile[0]?.nonMembers || []), ...(data.nonMembers || []), ]); - console.log("논멤버스" + JSON.stringify(nonMembers)); setAllPings([ ...(data.pings || []), // 기존 pings ...(recommendProfile || []), // recommendProfile 추가 ]); - setCustomMarkers([ ...(data.pings || []), // 기존 pings ...(recommendProfile || []), // recommendProfile 추가 ]); - setNeighborhood(data.neighborhood); } } catch (error) { - console.log("Error:", error); + console.error("Error:", error); } }; fetchAllPings(); @@ -136,7 +129,7 @@ const BottomDrawer: React.FC = ({ }; const handleAddToMorphing = async () => { - let Km = 1.0; + const Km = 1.0; let found = false; try { @@ -146,23 +139,11 @@ const BottomDrawer: React.FC = ({ ); if (response.ok) { const data = await response.json(); - console.log( - `Recommended data fetched successfully for ${Km} km:`, - data - ); - if (data.recommendPings.length == 0) { + if (data.recommendPings.length === 0) { setNonRecommend(true); - console.log("없음"); - } - if (data.recommendPings && data.recommendPings.length >= 5) { - console.log( - "Found more than or equal to 5 pings at radius:", - Km, - "km" - ); + } else if (data.recommendPings.length >= 5) { setCustomMarkers(data.recommendPings); found = true; - // break; setIsRecommend(found); } } else { @@ -174,79 +155,23 @@ const BottomDrawer: React.FC = ({ } catch (error) { console.error("Error fetching recommended data:", error); } - - // 상태 업데이트는 검색 결과에 따라 결정 }; - // const handleAddToMorphing = async () => { - // if (recommendPings.length > 0) { - // console.log("Adding selected pings to morphing...", recommendPings); - - // // Prepare payload - // const payload = { - // uuid: id, // UUID는 필요에 따라 동적으로 설정하세요 - // sids: recommendPings.map((ping) => ping.sid), // recommendPings에서 sid 추출 - // }; - - // console.log(payload); - - // try { - // // Send POST request - // const response = await fetch(`${apiUrl}/nonmembers/pings/recommend`, { - // method: "POST", - // headers: { - // "Content-Type": "application/json;charset=UTF-8", - // }, - // body: JSON.stringify(payload), // Convert payload to JSON - // }); - // // Handle response - // if (response.ok) { - // const result = await response.json(); - // console.log("추가:" + JSON.stringify(result)); - - // setIsRecommend(false); - // } else { - // const errorText = await response.text(); // 서버에서 제공하는 오류 메시지 확인 - // console.error( - // "Failed to add to morphing:", - // response.status, - // response.statusText, - // errorText - // ); - // console.error("Failed to add to morphing:", response.statusText); - // } - // } catch (error) { - // console.error("Error adding to morphing:", error); - // } - // } else { - // console.log("No pings available to add to morphing."); - // } - // }; - const handleRecommendCancle = () => { setIsRecommend(false); }; const handleButtonClick = (nonMemberId: number) => { - // 1. 현재 선택 상태를 확인합니다. const isSelected = selectedButton === nonMemberId; - - // 2. 선택 상태를 토글합니다. setSelectedButton(isSelected ? null : nonMemberId); - // 3. 선택 상태에 따라 pingsToShow를 설정합니다. const pingsToShow = isSelected - ? [...allPings] // 선택 해제 시 모든 핑을 표시 + ? [...allPings] : allPings.filter((ping) => ping.nonMembers.some((member) => member.nonMemberId === nonMemberId) - ); // nonMemberId와 일치하는 항목만 필터링 + ); - // 4. Zustand의 customMarkers를 업데이트합니다. setCustomMarkers(pingsToShow); - - // 5. 현재 상태를 로그로 확인합니다. - console.log("선택된 버튼:", isSelected ? null : nonMemberId); - console.log("업데이트된 마커:", pingsToShow); }; const handleAddButtonClick = () => { @@ -260,16 +185,15 @@ const BottomDrawer: React.FC = ({ ); if (response.ok) { const data = await response.json(); - console.log(data); setEventName(data.eventName); setNonMembers(data.nonMembers); setAllPings(data.pings || []); setCustomMarkers(data.pings || []); } else { - console.log("Failed to fetch refreshed data:", response.status); + console.error("Failed to fetch refreshed data:", response.status); } } catch (error) { - console.log("Error refreshing data:", error); + console.error("Error refreshing data:", error); } }; @@ -278,14 +202,12 @@ const BottomDrawer: React.FC = ({ role="button" tabIndex={0} className="bottom-drawer w-full h-[760px] bg-grayscale-90 z-10 rounded-t-xlarge" - onClick={(event: React.MouseEvent) => {}} - onKeyDown={(event: React.KeyboardEvent) => {}} > - {isRecommend === false && isRecommended === false ? ( + {!isRecommend && !isRecommended && (
- ) : null} + )}
@@ -305,8 +227,8 @@ const BottomDrawer: React.FC = ({ handleRecommendCancle={handleRecommendCancle} handleAddToMorphing={handleAddToMorphing} setIsRecommend={setIsRecommend} - setNonRecommend={setNonRecommend} nonRecommend={nonRecommend} + setNonRecommend={setNonRecommend} /> ) : ( = ({ handleRefresh={handleRefresh} eventName={eventName} id={id} - router={router} /> )} ); -}; +} export default BottomDrawer; diff --git a/fe/src/app/event-maps/[id]/components/ButtonComponents.tsx b/fe/src/app/event-maps/[id]/components/ButtonComponents.tsx index 0fa6002..aa18dc3 100644 --- a/fe/src/app/event-maps/[id]/components/ButtonComponents.tsx +++ b/fe/src/app/event-maps/[id]/components/ButtonComponents.tsx @@ -5,27 +5,33 @@ interface ButtonProps { onClick: () => void; } -export const ShareButton: React.FC = ({ onClick }) => ( - -); +export function ShareButton({ onClick }: ButtonProps) { + return ( + + ); +} -export const RefreshButton: React.FC = ({ onClick }) => ( - -); +export function RefreshButton({ onClick }: ButtonProps) { + return ( + + ); +} -export const EditButton: React.FC = ({ onClick }) => ( - -); +export function EditButton({ onClick }: ButtonProps) { + return ( + + ); +} interface MemberButtonProps { member: { @@ -37,21 +43,25 @@ interface MemberButtonProps { onClick: (id: number) => void; } -export const MemberButton: React.FC = ({ +export function MemberButton({ member, isSelected, onClick, -}) => ( - -); +}: MemberButtonProps) { + return ( + + ); +} diff --git a/fe/src/app/event-maps/[id]/components/LocationButton.tsx b/fe/src/app/event-maps/[id]/components/LocationButton.tsx index 5d49551..eb268ed 100644 --- a/fe/src/app/event-maps/[id]/components/LocationButton.tsx +++ b/fe/src/app/event-maps/[id]/components/LocationButton.tsx @@ -5,14 +5,16 @@ interface ButtonProps { onClick: () => void; } -const LocationButton: React.FC = ({ onClick }) => ( - -); +function LocationButton({ onClick }: ButtonProps) { + return ( + + ); +} export default LocationButton; diff --git a/fe/src/app/event-maps/[id]/components/MapComponent.tsx b/fe/src/app/event-maps/[id]/components/MapComponent.tsx index c093520..9dde6de 100644 --- a/fe/src/app/event-maps/[id]/components/MapComponent.tsx +++ b/fe/src/app/event-maps/[id]/components/MapComponent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef } from "react"; import { useLocationStore } from "../stores/useLocationStore"; import { useMarkerStore } from "../load-mappin/stores/useMarkerStore"; @@ -13,97 +13,81 @@ interface NonMember { profileSvg: string; } -interface PingData { +interface Ping { placeName: string; url: string; nonMembers: NonMember[]; type: string; + px: number; + py: number; + iconLevel: number; } -const transformPingData = (ping: any): PingData => ({ - placeName: ping.placeName, - url: ping.url, - nonMembers: (ping.nonMembers || []).map( - (member: { profileSvg?: string; nonMemberId: number; name: string }) => ({ - ...member, - profileSvg: member.profileSvg || "https://default-image.svg", // 기본 이미지 URL 추가 - }) - ), - type: ping.type, -}); - -const setupToggleDropdown = (): void => { - window.toggleDropdown = (): void => { - const dropdown: HTMLElement | null = - document.getElementById("dropdownExtended"); - const dropdownIcon: HTMLImageElement | null = document.querySelector( - 'img[src="/svg/dropdown.svg"]' - ); - const namesShort: HTMLElement | null = - document.querySelector(".names-short"); +const transformPingData = (ping: unknown): Ping => { + if (typeof ping !== "object" || ping === null) { + throw new Error("Invalid ping data"); + } - if (!dropdown || !dropdownIcon || !namesShort) return; // 요소가 없으면 함수를 실행하지 않습니다. + const { + placeName = "Unknown Place", + url = "#", + nonMembers = [], + type = "Unknown", + px = 0, + py = 0, + iconLevel = 1, + } = ping as Partial; - if (dropdown.style.display === "none") { - dropdown.style.display = "block"; - dropdownIcon.style.transform = "rotate(0deg)"; - namesShort.style.opacity = "0"; // CSS 속성은 문자열로 처리해야 합니다. - } else { - dropdown.style.display = "none"; - dropdownIcon.style.transform = "rotate(180deg)"; - namesShort.style.opacity = "1"; // CSS 속성은 문자열로 처리해야 합니다. - } + return { + placeName, + url, + nonMembers: (nonMembers as NonMember[]).map((member) => ({ + ...member, + profileSvg: member.profileSvg || "https://default-image.svg", + })), + type, + px, + py, + iconLevel, }; }; -export default function MapComponent({ px, py }: MapComponentProps) { +export default function MapComponent({ + px, + py, +}: MapComponentProps): JSX.Element { const mapRef = useRef(null); const mapInstanceRef = useRef(null); const { customMarkers } = useMarkerStore(); const { center } = useLocationStore(); - const [selectedMarker, setSelectedMarker] = useState(null); const markersRef = useRef([]); const infoWindowRef = useRef(null); const previousMarkerIndexRef = useRef(null); - // HTML 아이콘으로 커스텀 마커 설정 const getHtmlIconByLevel = ( level: number, placeName: string, - isSelected: boolean = false + isSelected = false ) => { - const iconSize_w = isSelected ? 35 : 28; - const iconSize_h = isSelected ? 40 : 32; - let textColor, textShadow; - - if (level === 1 || level === 10 || level === 10) { - textColor = "#000000"; // 검정색 글씨 - textShadow = - "-1px 0px #FFFFFF, 0px 1px #FFFFFF, 1px 0px #FFFFFF, 0px -1px #FFFFFF"; // 흰색 텍스트 쉐도우 테두리 - } else { - textColor = "#FA8980"; // 살구색 글씨 - textShadow = - "-1px 0px #FFFFFF, 0px 1px #FFFFFF, 1px 0px #FFFFFF, 0px -1px #FFFFFF"; // 흰색 텍스트 쉐도우 테두리 - } + const iconWidth = isSelected ? 35 : 28; + const iconHeight = isSelected ? 40 : 32; + const textColor = level === 1 || level === 10 ? "#000000" : "#FA8980"; + const textShadow = + "-1px 0px #FFFFFF, 0px 1px #FFFFFF, 1px 0px #FFFFFF, 0px -1px #FFFFFF"; return { - content: `
-
+
${placeName}
`, - size: new window.naver.maps.Size(iconSize_w, iconSize_h), - anchor: new window.naver.maps.Point(iconSize_w / 2, iconSize_h + 15), // 라벨이 포함되므로 앵커 포지션 조정 + size: new window.naver.maps.Size(iconWidth, iconHeight), + anchor: new window.naver.maps.Point(iconWidth / 2, iconHeight + 15), }; }; - useEffect(() => { - setupToggleDropdown(); - // 마커와 이벤트 리스너 생성 코드 이하 생략 - }, []); - useEffect(() => { const initializeMap = () => { if (!mapInstanceRef.current && window.naver && mapRef.current) { @@ -130,7 +114,7 @@ export default function MapComponent({ px, py }: MapComponentProps) { script.onload = () => initializeMap(); document.head.appendChild(script); } - }, []); + }, [px, py]); useEffect(() => { if (!mapInstanceRef.current) return; @@ -140,10 +124,18 @@ export default function MapComponent({ px, py }: MapComponentProps) { customMarkers.forEach((ping, index) => { const transformedPing = transformPingData(ping); - const markerOptions = { - position: new window.naver.maps.LatLng(ping.py, ping.px), - map: mapInstanceRef.current as naver.maps.Map, - icon: getHtmlIconByLevel(ping.iconLevel, ping.placeName, false), + + const markerOptions: naver.maps.MarkerOptions = { + position: new window.naver.maps.LatLng( + transformedPing.py, + transformedPing.px + ), + map: mapInstanceRef.current || undefined, + icon: getHtmlIconByLevel( + transformedPing.iconLevel, + transformedPing.placeName, + false + ), clickable: true, }; @@ -165,148 +157,149 @@ export default function MapComponent({ px, py }: MapComponentProps) { } marker.setIcon( - getHtmlIconByLevel(ping.iconLevel, ping.placeName, true) + getHtmlIconByLevel( + transformedPing.iconLevel, + transformedPing.placeName, + true + ) ); previousMarkerIndexRef.current = index; - setSelectedMarker(index); if (infoWindowRef.current) { infoWindowRef.current.close(); } - const infoWindowContent = (data: PingData) => ` -
-
-
-
${data.type}
- -
-
- ${data.placeName} -
-
- -
-
- - ${data.nonMembers.length} -
-
- ${data.nonMembers.map((member) => member.name).join(", ")} -
- -
- - -
-`; const infoWindow = new window.naver.maps.InfoWindow({ - content: infoWindowContent(transformedPing), // API에서 받은 데이터를 infoWindowContent 함수에 전달 - borderWidth: 0, - backgroundColor: "transparent", - disableAnchor: true, + content: ` +
+
+
+
${transformedPing.type}
+ +
+
+ ${transformedPing.placeName} +
+
+ +
+
+ + ${transformedPing.nonMembers.length} +
+
+ ${transformedPing.nonMembers.map((member) => member.name).join(", ")} +
+ +
+ + +
+ `, + borderWidth: 0, // 보더 제거 + backgroundColor: "transparent", // 백그라운드 설정 + disableAnchor: true, // 앵커 비활성화 }); if (mapInstanceRef.current) { infoWindow.open(mapInstanceRef.current, marker); } - console.log(customMarkers); infoWindowRef.current = infoWindow; }); }); @@ -328,7 +321,6 @@ export default function MapComponent({ px, py }: MapComponentProps) { ); previousMarkerIndexRef.current = null; } - setSelectedMarker(null); if (infoWindowRef.current) { infoWindowRef.current.close(); @@ -336,7 +328,6 @@ export default function MapComponent({ px, py }: MapComponentProps) { } ); } - console.log(customMarkers); }, [customMarkers]); useEffect(() => { diff --git a/fe/src/app/event-maps/[id]/components/RecommendActive.tsx b/fe/src/app/event-maps/[id]/components/RecommendActive.tsx index c6490e2..77d1648 100644 --- a/fe/src/app/event-maps/[id]/components/RecommendActive.tsx +++ b/fe/src/app/event-maps/[id]/components/RecommendActive.tsx @@ -1,6 +1,5 @@ -import React from "react"; +import React, { Dispatch, SetStateAction } from "react"; import Image from "next/image"; -import { Dispatch, SetStateAction } from "react"; interface RecommendActiveProps { neighborhood: string; @@ -11,25 +10,29 @@ interface RecommendActiveProps { nonRecommend: boolean; } -const RecommendActive: React.FC = ({ +export function RecommendActive({ neighborhood, handleRecommendCancle, handleAddToMorphing, setIsRecommend, setNonRecommend, nonRecommend, -}) => { +}: RecommendActiveProps): JSX.Element { return (
{/* nonRecommend이 true일 때 다른 UI를 렌더링 */} {nonRecommend ? ( <> - {/* 추천 데이터가 있을 때 기본 UI */} + {/* 추천 데이터가 없을 때 UI */}
근처에 추천할만한 공간이 없어요
-
-
); -}; +} export default RecommendActive; diff --git a/fe/src/app/event-maps/[id]/components/RecommendButton.tsx b/fe/src/app/event-maps/[id]/components/RecommendButton.tsx index f995c12..a82beb7 100644 --- a/fe/src/app/event-maps/[id]/components/RecommendButton.tsx +++ b/fe/src/app/event-maps/[id]/components/RecommendButton.tsx @@ -5,15 +5,17 @@ interface RecommendButtonProps { onClick: () => void; } -const RecommendButton: React.FC = ({ onClick }) => ( - -); - -export default RecommendButton; +export function RecommendButton({ + onClick, +}: RecommendButtonProps): JSX.Element { + return ( + + ); +} diff --git a/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx b/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx index 6eb2207..efa69ed 100644 --- a/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx +++ b/fe/src/app/event-maps/[id]/components/RecommendInActive.tsx @@ -1,5 +1,6 @@ import React from "react"; import Image from "next/image"; +import { useRouter } from "next/navigation"; import { ShareButton, RefreshButton, @@ -33,10 +34,9 @@ interface RecommendInActiveProps { handleRefresh: () => void; eventName: string; id: string; - router: any; // Type based on your routing library } -const RecommendInActive: React.FC = ({ +export function RecommendInActive({ nonMembers, handleButtonClick, handleAddButtonClick, @@ -46,61 +46,64 @@ const RecommendInActive: React.FC = ({ handleRefresh, eventName, id, - router, -}) => ( - <> -
-
{eventName}
-
- navigator.share({ url: window.location.href })} - /> - {selectedButton !== null ? ( - router.push(`/event-maps/${id}/${selectedButton}`)} +}: RecommendInActiveProps): JSX.Element { + const router = useRouter(); + + return ( + <> +
+
{eventName}
+
+ navigator.share({ url: window.location.href })} /> - ) : ( - - )} + {selectedButton !== null ? ( + router.push(`/event-maps/${id}/${selectedButton}`)} // router 사용 + /> + ) : ( + + )} +
-
-
-
- +
+
+ +
+ {nonMembers.map((member) => ( +
+ handleButtonClick(member.nonMemberId)} + /> +
{member.name}
+
+ ))}
- {nonMembers.map((member) => ( -
- handleButtonClick(member.nonMemberId)} +
+ {allPings.map((ping, index) => ( + -
{member.name}
-
- ))} -
-
- {allPings.map((ping, index) => ( - - ))} -
- -); + ))} +
+ + ); +} export default RecommendInActive; diff --git a/fe/src/app/event-maps/[id]/components/StoreItem.tsx b/fe/src/app/event-maps/[id]/components/StoreItem.tsx index e487e35..742c296 100644 --- a/fe/src/app/event-maps/[id]/components/StoreItem.tsx +++ b/fe/src/app/event-maps/[id]/components/StoreItem.tsx @@ -30,8 +30,9 @@ const StoreItem = forwardRef(