Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ export default function Page() {
const category = purposes[purposes.length - 1];
localStorage.setItem(`meeting_${meetingId}_category`, category);
}
if (meetingType) {
localStorage.setItem(`meeting_${meetingId}_meetingType`, meetingType);
}

// 링크 공유 페이지 이동
router.push(`/share/${meetingId}`);
Expand Down
84 changes: 69 additions & 15 deletions app/recommend/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
import Image from 'next/image';
import KakaoMapRecommend from '@/components/map/kakaoMapRecommend';
import { useRecommend } from '@/hooks/api/query/useRecommend';
import { useCheckMeeting } from '@/hooks/api/query/useCheckMeeting';

function RecommendContent() {
const router = useRouter();
Expand All @@ -21,25 +22,72 @@ function RecommendContent() {
// 현재 선택된 장소 ID (기본값: 첫 번째)
const [selectedPlaceId, setSelectedPlaceId] = useState<number>(1);

// 모임 카테고리 가져오기 (localStorage에서 캐싱된 값 사용)
const meetingCategory = useMemo(() => {
const cachedCategory = localStorage.getItem(`meeting_${meetingId}_category`);
if (cachedCategory) {
return cachedCategory;
// 모임 정보 조회 (purposes 정보를 가져오기 위해)
const { data: meetingData } = useCheckMeeting(meetingId);

// 상위 카테고리 추출 (API에서 가져오거나 localStorage에서)
const meetingType = useMemo(() => {
if (typeof window === 'undefined') return null;

// 1. API에서 purposes 가져오기 (참여자도 접근 가능)
if (meetingData?.data?.purposes && meetingData.data.purposes.length > 0) {
const firstPurpose = meetingData.data.purposes[0];
if (firstPurpose === '회의' || firstPurpose === '친목') {
// localStorage에도 저장 (다음 접근 시 빠르게 사용) localStorage.setItem(`meeting_${meetingId}_meetingType`, firstPurpose);
return firstPurpose as '회의' | '친목';
}
}


const cachedType = localStorage.getItem(`meeting_${meetingId}_meetingType`);
if (cachedType === '회의' || cachedType === '친목') {
return cachedType as '회의' | '친목';
}

return null;
}, [meetingId, meetingData]);
Comment on lines +29 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

localStorage.setItem 호출이 주석 안에 포함되어 실행되지 않습니다.

Line 36에서 localStorage.setItem(...) 호출이 주석(//)과 같은 줄에 있어 실행되지 않습니다. API에서 가져온 meetingType이 localStorage에 캐싱되지 않으므로, API 데이터가 없는 후속 접근 시 fallback이 작동하지 않습니다.

🐛 주석과 코드를 분리
     if (meetingData?.data?.purposes && meetingData.data.purposes.length > 0) {
       const firstPurpose = meetingData.data.purposes[0];
       if (firstPurpose === '회의' || firstPurpose === '친목') {
-        // localStorage에도 저장 (다음 접근 시 빠르게 사용)    localStorage.setItem(`meeting_${meetingId}_meetingType`, firstPurpose);
+        // localStorage에도 저장 (다음 접근 시 빠르게 사용)
+        localStorage.setItem(`meeting_${meetingId}_meetingType`, firstPurpose);
         return firstPurpose as '회의' | '친목';
       }
     }
🤖 Prompt for AI Agents
In `@app/recommend/page.tsx` around lines 29 - 48, The meetingType useMemo
currently has the localStorage.setItem call commented out on the same line so
the API-derived firstPurpose never gets cached; inside the block that checks
meetingData.data.purposes and determines firstPurpose (the code dealing with
meetingData, firstPurpose, and meetingId), move the
localStorage.setItem(`meeting_${meetingId}_meetingType`, firstPurpose) out of
the comment and execute it before returning firstPurpose (ensuring typeof window
!== 'undefined' remains satisfied) so the value is persisted for future lookups
used by the cachedType logic.


return '';
}, [meetingId]);
// 하위 카테고리 추출 (API에서 가져오거나 localStorage에서)
const defaultCategory = useMemo(() => {
if (typeof window === 'undefined') return '';

// 1. API에서 가져온 purposes의 마지막 값 사용
if (meetingData?.data?.purposes && meetingData.data.purposes.length > 1) {
const subCategory = meetingData.data.purposes[meetingData.data.purposes.length - 1];
if (subCategory) {
// localStorage에도 저장 (다음 접근 시 빠르게 사용)
localStorage.setItem(`meeting_${meetingId}_category`, subCategory);
return subCategory;
}
}

const cachedCategory = localStorage.getItem(`meeting_${meetingId}_category`);
return cachedCategory || '';
}, [meetingId, meetingData]);

const [selectedCategory, setSelectedCategory] = useState<string>(() => {
if (typeof window === 'undefined') return '';
return localStorage.getItem(`meeting_${meetingId}_category`) || '';
});

const currentCategory = selectedCategory || defaultCategory;

const effectiveCategory = currentCategory;

// 카테고리 변경 핸들러
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
setSelectedPlaceId(1); // 카테고리 변경 시 첫 번째 장소 선택
if (typeof window !== 'undefined') {
localStorage.setItem(`meeting_${meetingId}_category`, category);
}
};

// 장소 추천 API 호출
const {
data: recommendData,
isLoading,
isError,
} = useRecommend({
// 장소 추천 API 호출 (effectiveCategory 사용 - selectedCategory가 우선)
const { data: recommendData, isLoading, isError } = useRecommend({
meetingId,
midPlace,
category: meetingCategory,
category: effectiveCategory,
page: 1,
size: 15,
});
Expand All @@ -52,7 +100,7 @@ function RecommendContent() {
return recommendData.data.placeInfos.map((place, index) => ({
id: index + 1,
name: place.placeName,
category: place.categoryGroupName || place.categoryName,
category: place.categoryGroupName,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

categoryGroupName이 빈 값일 때의 fallback 제거.

이전에 categoryName으로의 fallback이 있었으나 제거되었습니다. categoryGroupName이 빈 문자열이나 undefined인 경우 카테고리 배지가 비어 보일 수 있습니다.

🛡️ fallback 추가 제안
-      category: place.categoryGroupName,
+      category: place.categoryGroupName || place.categoryName || '',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
category: place.categoryGroupName,
category: place.categoryGroupName || place.categoryName || '',
🤖 Prompt for AI Agents
In `@app/recommend/page.tsx` at line 103, 현재 category 필드에 직접 할당된 category:
place.categoryGroupName이 빈 문자열이나 undefined일 때 배지가 비어 보이는 문제가 있습니다;
categoryGroupName이 falsy(빈 문자열 포함)인 경우 place.categoryName으로 대체되도록 수정하세요 — 예:
category에 place.categoryGroupName이 유효한 값인지 확인하고(빈 문자열 체크 포함) 유효하지 않으면
place.categoryName을 할당하도록 변경하여 category: place.categoryGroupName 대신 안전한
fallback을 사용하도록 고치세요.

description: place.categoryName,
phone: place.phone || '전화번호 없음',
address: place.addressName,
Expand Down Expand Up @@ -99,6 +147,9 @@ function RecommendContent() {
places={places}
selectedPlaceId={selectedPlaceId}
onSelectPlace={setSelectedPlaceId}
selectedCategory={effectiveCategory}
onCategoryChange={handleCategoryChange}
meetingType={meetingType}
/>
</div>

Expand Down Expand Up @@ -195,6 +246,9 @@ function RecommendContent() {
places={places}
selectedPlaceId={selectedPlaceId}
onSelectPlace={setSelectedPlaceId}
selectedCategory={effectiveCategory}
onCategoryChange={handleCategoryChange}
meetingType={meetingType}
/>
</section>
</div>
Expand Down
26 changes: 23 additions & 3 deletions components/map/kakaoMapRecommend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ interface KakaoMapRecommendProps {
onSelectPlace?: (placeId: number) => void;
selectedCategory?: string;
onCategoryChange?: (category: string) => void;
meetingType?: '회의' | '친목' | null;
}

const CATEGORIES = [
// 전체 카테고리 목록
const ALL_CATEGORIES = [
{ id: '식당', label: '식당', icon: '/icon/place/restaurant' },
{ id: '놀거리', label: '놀거리', icon: '/icon/place/play' },
{ id: '술집', label: '술집', icon: '/icon/place/bar' },
{ id: '카페', label: '카페', icon: '/icon/place/cafe' },
{ id: '놀거리', label: '놀거리', icon: '/icon/place/play' },
{ id: '스터디카페', label: '스터디카페', icon: '/icon/place/studycafe' },
{ id: '장소 대여', label: '장소 대여', icon: '/icon/place/rent' },
];
Comment on lines +29 to 37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for both spelling variants of study cafe category
echo "=== Searching for '스터디카페' (no space) ==="
rg '스터디카페' --type=ts --type=tsx -n

echo ""
echo "=== Searching for '스터디 카페' (with space) ==="
rg '스터디 카페' --type=ts --type=tsx -n

Repository: SWYP-mingling/Frontend

Length of output: 224


🏁 Script executed:

#!/bin/bash
# Search for both spelling variants without file type restriction
echo "=== Searching for '스터디카페' (no space) ==="
rg '스터디카페' -n --glob='*.tsx' --glob='*.ts'

echo ""
echo "=== Searching for '스터디 카페' (with space) ==="
rg '스터디 카페' -n --glob='*.tsx' --glob='*.ts'

Repository: SWYP-mingling/Frontend

Length of output: 533


🏁 Script executed:

#!/bin/bash
# Check the recommend page to see how selectedCategory is used and compared
echo "=== Content of recommend/page.tsx to understand data flow ==="
rg 'selectedCategory|defaultCategory|purposes' -n --glob='*.tsx' app/ 

echo ""
echo "=== Check kakaoMapRecommend.tsx line 141 (selectedCategory comparison) ==="
sed -n '135,150p' components/map/kakaoMapRecommend.tsx

Repository: SWYP-mingling/Frontend

Length of output: 2855


'스터디카페''스터디 카페'의 불일치로 카테고리 선택이 동작하지 않습니다.

app/create/page.tsx Line 227에서 목적으로 '스터디 카페'(띄어쓰기 있음)를 전송하고, API에서도 이 값을 그대로 반환합니다. 그러나 kakaoMapRecommend.tsx에서 ALL_CATEGORIES의 ID는 '스터디카페'(띄어쓰기 없음)로 정의되어 있습니다. 결과적으로 selectedCategory === cat.id 비교(Line 141)가 실패하여 카테고리 하이라이트가 표시되지 않으며, 필터링 로직(Line 57)도 정상 작동하지 않습니다.

둘 중 하나로 통일해야 합니다.

🐛 권장 사항: ALL_CATEGORIES의 ID를 create 페이지와 일치시키기
-  { id: '스터디카페', label: '스터디카페', icon: '/icon/place/studycafe' },
+  { id: '스터디 카페', label: '스터디 카페', icon: '/icon/place/studycafe' },

또는 app/create/page.tsx Line 227의 값을 '스터디카페'로 변경하세요.

🤖 Prompt for AI Agents
In `@components/map/kakaoMapRecommend.tsx` around lines 29 - 37, The category ID
for the study-cafe is inconsistent: ALL_CATEGORIES currently uses id '스터디카페'
while the rest of the app (e.g., the value sent from create/page and returned by
the API, and the comparisons using selectedCategory in kakaoMapRecommend.tsx)
uses '스터디 카페' with a space; update the ALL_CATEGORIES entry for the study cafe
so its id is '스터디 카페' to match selectedCategory checks and filtering logic, and
verify any other places that construct or compare this category string use the
same exact value.


export default function KakaoMapRecommend({
Expand All @@ -41,10 +45,26 @@ export default function KakaoMapRecommend({
selectedPlaceId,
selectedCategory = '',
onCategoryChange,
meetingType,
}: KakaoMapRecommendProps) {
// 1. 지도 객체를 state로 관리 (줌 컨트롤 제어용)
const [map, setMap] = useState<kakao.maps.Map | null>(null);

// 상위 카테고리에 따라 하위 카테고리 필터링
const categories = useMemo(() => {
if (meetingType === '회의') {
// 회의: 스터디카페, 장소 대여
return ALL_CATEGORIES.filter((cat) => cat.id === '스터디카페' || cat.id === '장소 대여');
} else if (meetingType === '친목') {
// 친목: 식당, 술집, 카페, 놀거리
return ALL_CATEGORIES.filter(
(cat) => cat.id === '식당' || cat.id === '술집' || cat.id === '카페' || cat.id === '놀거리'
);
}
// meetingType이 없으면 전체 카테고리 표시
return ALL_CATEGORIES;
}, [meetingType]);

// 선택된 장소 찾기
const selectedPlace = useMemo(() => {
if (!selectedPlaceId || places.length === 0) return null;
Expand Down Expand Up @@ -113,7 +133,7 @@ export default function KakaoMapRecommend({

{/* 상단 카테고리 필터 (Floating) */}
<div className="scrollbar-hide absolute top-4 right-0 left-0 z-20 flex justify-start gap-2 overflow-x-hidden px-4">
{CATEGORIES.map((cat) => (
{categories.map((cat) => (
<button
key={cat.id}
onClick={() => onCategoryChange?.(cat.id)}
Expand Down
2 changes: 1 addition & 1 deletion types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export interface MeetingStatusData {
currentParticipantCount: number;
pendingParticipantCount: number;
deadlineAt: string;
// 출발지 등록한 참여자 리스트
participants: SetDepartureData[];
purposes?: string[];
}

// 모임 참여 현황 조회 API 조립
Expand Down