feat: 식당 110+ / 메뉴 1100+ 등록 + 지도·메뉴 UI 개편 + 관리자 강화#32
Merged
Conversation
제휴 데이터에 자유전공학부가 포함되면서 College enum 에 FREE_MAJOR 추가. 마이그레이션, 공통 타입, 관리자 폼 select 옵션, 한글 라벨을 함께 갱신.
PC localhost 와 같은 LAN 안 모바일 기기(예: http://192.168.x.x:3000) 양쪽에서 동시 접속 가능하도록 CORS_ORIGIN 을 콤마 split + trim 처리.
카카오 REST API 키워드 검색 + 주소 변환으로 광운대역/정문/후문 일대 108개 식당 좌표·전화·주소 자동 수집 후 SQL 일괄 임포트. 별칭 정규화(민들레뜨락→민들레뜨락2, 수해복마라탕↔마라탕&샹궈 등)와 카테고리 best-effort 추론 포함. 단과대학별 제휴 81건은 별도 SQL 로 ON CONFLICT 안전 임포트. 폐업 식당(FM.25.1st, 뉴욕쟁이디저트)과 카카오 미등록 식당(끝집, 스트릿츄러스 등)은 리포트로 분리.
- MARKER_CLUSTER_THRESHOLD 50 → 1 — 필터로 식당 수가 줄어도
CustomOverlay 경로로 안 빠지고 항상 클러스터링 동작
- 줌 레벨별 gridSize 동적 조정 (full:80 / small:130 / dot:180)
· 멀리 줌아웃 할수록 같은 픽셀 범위가 더 큰 실거리를 커버하므로
공격적으로 묶어야 자연스러움
· zoom_changed 핸들러에서 setGridSize + redraw 호출
- 타입 정의에 MarkerClusterer.setGridSize / redraw 추가
- dot tier 마커가 10~12px 라 손가락으로 누르기 힘들던 문제 해결 · 단일 일반 10x10 → 18x18 · 단일 제휴 12x12 → 20x20 · 그룹 dot 14x14 → 22x22 - 클러스터 gridSize 줌 레벨 5단계 그라데이션 · level 1: 80 (minLevel=2 로 사실상 비활성, 개별 마커 우선) · level 2: 100 (가까운 페어만 묶음) · level 3: 140 (3~5 클러스터/존) · level 4: 200 (광운대역/정문/후문 단위) · level 5+: 320 (광운대 일대 1~2 클러스터) - minLevel 3 → 2 로 낮춰 level 2 에서도 클러스터링 작동 - zoom_changed 핸들러를 tier 변화 / gridSize 변화 분리해서 redraw
검색에서 식당을 찾고도 바로 상세로 빠지면 지도 위치 감각을 잃어 동선이 끊겼다. 이제 검색 결과 탭 → 지도에서 그 마커로 이동 + 바텀시트 펼침. - SearchSheet 의 Link → button 변경, onSelect(restaurant) 콜백으로 위임 - page 의 handleSearchSelect: · panTo(lat, lng) 로 부드럽게 이동 · 줌 레벨이 멀면(>2) setLevel(2) 로 당겨오기 · selectedId 설정 + 바텀시트 half 스냅으로 펼침 → 결과적으로 마커 선택 + 리스트 강조까지 한 번에
새로고침 직후 시트 노출이 빈약하던 문제와, half 에서 리스트 마지막
아이템이 viewport 너머로 잘리던 문제 해결.
- 기본 snap 'peek' → 'half' — 첫 진입 시 환영/칩/리스트가 한 번에 보임
- useMotionValue 초기값을 window 비의존 상수(9999) 로 고정 → SSR/client
hydration mismatch 경고 해결, 아래에서 위로 spring 인트로 애니메이션
- 스크롤 영역 maxHeight 를 useTransform 으로 y 와 연동
· 시트 height 100dvh 의 절반이 viewport 밖에 있어 스크롤 max 위치에서도
마지막 아이템이 가장자리 너머에 가려지던 문제 해결
- touchAction 을 'peek' 외 상태에선 'pan-y' 로 — half 에서도 콘텐츠
터치 스크롤 가능 (스크롤 top 에서만 framer 드래그로 시트 확장)
- 중복된 초기 위치 useEffect 제거 (snap 변경 effect 하나로 통합)
지도 줌이나 이동에 따라 영업중 식당 카운트가 매번 바뀌어 의미가 약해지던 문제. WelcomeStats 가 부모에서 받은 visibleRestaurants 가 아닌 자체적으로 useRestaurants 호출해 전체 식당 기준으로 카운트. react-query 캐시 키가 페이지의 fresh entry 호출과 동일해 추가 네트워크 요청 없음. BottomSheetContent 의 restaurants prop 전달도 제거.
식당 100개 넘으면서 관리자가 특정 식당 찾기 힘들어진 문제 해결. - 검색 입력창 (Search 아이콘 + 식당명 placeholder) - 식당명 부분 일치 (대소문자 무관) 클라이언트 필터링 — 100여 개 규모 에서는 useMemo + filter 로 충분 - 검색어 입력 중 매치 개수 표시 (예: 5개) - X 버튼으로 검색어 즉시 초기화 - 결과 0건일 때 검색어와 안내 메시지 표시
실제 식당 정보 반영하다가 한계에 부딪힌 케이스들 해결. - 인생아구찜 (10:00-01:00) 과 썬더치킨 (14:30-02:30) — 자정 넘김 - 친친, 미미식당, 윤스쿡 등 — 브레이크타임 - 명동찌개마을 둘째/넷째 토 휴무 — 정형 표현 불가능한 비고 변경: - BusinessHours 에 breakStart, breakEnd, BusinessHoursMap 에 note 추가 - isRestaurantOpen 재작성. close 가 open 보다 작으면 자정 넘김으로 인식하고 어제의 영업이 오늘 새벽까지 이어진 경우도 영업중 판정. 브레이크 시간대는 영업중이어도 false. - getNextOpenAt 는 브레이크 종료 또는 다음 영업 시작 중 가장 가까운 시각 반환 - 관리자 폼: 각 요일에 브레이크 토글 (활성화 시 시작과 종료 시간 입력). 영업시간 섹션 하단에 비고 자유텍스트 입력 - buildBusinessHours new 와 동적 라우트 양쪽이 break 와 note 보존 - RestaurantInfo: note 가 있으면 info 박스로 노출
사용자 직접 조사한 영업시간 (학기중 기준) DB 반영. - business-hours-update.sql 1차 — 단순 시간 및 휴무일 70여 곳 - business-hours-update-v2.sql 2차 — 자정 넘김 (인생아구찜, 썬더치킨, 영축산정육식당 등) 및 브레이크타임 (친친, 미미식당, 윤스쿡, 병천청년순대, 스시덤, 이찌마김치, 서선생김치찜) 정확히 반영. 놀부부대찌개 금요일 휴무도 추가 신규 등록. - add-restaurants-v2.sql — 광운양꼬치 (정문, 카카오 검색) 와 5일장 햄버거 (김바삭군의 볼카츠마켙 광운대점 rename, 카테고리도 일식에서 양식으로) - add-restaurants-v3.sql — 샐러리아 광운대점 (주소 geocoding 과 POLICY_LAW 제휴), CAFE FIASCO (주소 geocoding), 스트릿츄러스 광운대점 (주소 geocoding 과 AI_CONVERGENCE 제휴) 모든 SQL 트랜잭션 + 검증 쿼리 포함.
- Menu.category + Menu.priceOptions 추가 (감자탕 소/중/대 식 옵션가격 지원) - Restaurant.coverImageUrl + Restaurant.externalMenuUrl (체인점 공식 메뉴 링크) - 공통 타입, DTO, service select 동기화
- 대표 사진 URL · 공식 메뉴 URL(체인점) 입력 섹션 신설 - 신규/수정 페이지에서 두 필드를 mutation 으로 전달
- MenuList 카테고리 2개 이상이면 가로 스크롤 탭 (전체 + 카테고리별 필터) - priceOptions 있으면 소 30,000원 / 중 35,000원 한 줄 표기 - 메뉴 항목·가격 변동 면책 문구 노출 - coverImageUrl 을 Hero 카루셀 첫 슬라이드 + 리스트 썸네일 우선순위
- zone 별 메뉴 데이터 (kwoondae/frontgate/backgate _menus.py) — 광운대역 23개, 정문 OCR 23개 + 굿킨 대표 5개, 후문 한라산감자탕·후문식당. category·priceOptions 포함 - xlsx 메뉴판 → menus-import.sql 변환기, 메뉴 vs 가격 검증 스크립트 - 식당 대표사진 일괄 적용 SQL + 한글 NFC 파일명 정규화 유틸 - 잘못 등록된 꽁꽁삼겹살차돌박이 석계2호점 seed 제거 (out.sql/seed.mjs) - 메뉴판 OCR 원본 자료(docs/메뉴판 사진*, xlsx) gitignore
- SelectedMarkerHighlight: clusterer/CustomOverlay 양쪽 경로 위에 떠 있는 펄스 후광 오버레이 (zIndex 200, clickable false) - RestaurantMarker: 선택 시 size 30→42 + 흰 테두리 2단 후광 + translateY 리프트 + 본인 z-index 상향 - 어느 줌 레벨/모드든 사용자가 누른 마커가 즉시 식별 가능
- 광운대역 큰맘할매순대국 광운대점 40개 (순대국·수육국밥·뼈해장국·할매토종순대·편육·전골·감자탕 사이즈별 priceOptions) - 정문 오쎄 55개 (COFFEE/TEA/HERBAL TEA/SOFT DRINK/JUICE/COCKTAIL/기타, HOT·ICE 가격 차이 있는 6개만 priceOptions) - 정문 큰집닭강정 광운대점 11개 (닭강정 사이즈 priceOptions + 맛 9종 안내 + 사이드) - 후문 포 레오 40개 (PHO/RICE/SIDE, 새우고로케 2pcs/4pcs priceOptions) - 카츠백 월계점 재OCR: 메인메뉴·면류·사이드 메뉴명/가격 전면 정정 (갈릭포가카레, 빽s 치킨너겟, 버팔로윙 등)
- 본도시락 광운대역점 / 프랭크버거 광운대점 / 썬더치킨 광운대점 - 맛닭꼬 광운대점 / 와플대학 광운대캠퍼스 / 샐러리아 광운대점 - DB 에는 이미 반영. seed 재실행·이관 시 복원용
- RESTAURANT_LIST_SELECT / RESTAURANT_DETAIL_SELECT 의 menus.select 에 priceOptions + category 추가 (사이즈별 가격 누락 버그 수정) - featuredMenu orderBy: isSignature+price → createdAt asc (입력 첫 메뉴 = 대표) - import-py-menus.py: kwoondae/frontgate/backgate _menus.py → SQL 변환. clock_timestamp() 로 row 별 createdAt 미세차 부여해 입력순 정렬 가능 - NAME_MAP 으로 민들레밥상 → 민들레뜨락2, 또와순대국 → 또와집순대국 보정 - 가마솥뼈다귀감자탕 메뉴 사용자 제공본으로 재정리 (해장국 + 추가 5종 + 주류 5종) - 광운양꼬치 훈둔 → 훈툰 오타 정정 (menus-import.sql) - .gitignore 에 __pycache__/, *.pyc 추가
- categoryPriority(): 키워드 매칭으로 5단계 점수 (메인 10 / 주력 20 / 사이드 30 / 기타 40 / 음료 50) - 동순위는 입력 순서 유지 (stable sort) - 카페·전문점 (COFFEE/PHO 등) 은 default 20 으로 메인 직후 배치, 음료 키워드(SOFT DRINK/COCKTAIL/JUICE) 만 마지막으로
- mapStore (Zustand 인메모리): 중심·줌 보관. 상세 페이지 갔다 뒤로 와도 직전 위치 복원 - KakaoMap: 초기 setTimeout 의 setCenter → relayout (복원 위치 덮어쓰기 버그 수정) - handleSearchSelect: panTo + setLevel 충돌 제거 → setCenter + setLevel(1, animate). idle 누락 대비 store 즉시 저장. level 1 으로 클러스터 해제해 개별 마커 노출 보장 - 마커 선택 시각화: 펄스 후광 (SelectedMarkerHighlight) 제거. 흰 stroke 굵기 2배 + SVG 사이즈 1.22x 만으로 강조 (어두운 색 안 씀, 브랜드 디자인 유지) - useMarkerClusterer: selectedId 옵션 추가. 이미지 캐시 12종(tier × partner × selected) lazy 생성. selectedId 변경 시 setImage 만 재호출 (클러스터러 재생성 없음) - ClusterPicker onSelect: 마커 선택 → router.push(/restaurants/:id) 로 변경
- kakao.d.ts Map 클래스에 getBounds/relayout 추가 (런타임엔 존재했으나 d.ts 누락. handleMapReady 의 idle 핸들러 + 뷰포트 복원 코드에서 TS 에러) - LatLngBounds 에 getSouthWest/getNorthEast 추가 - setLevel options 에 anchor?: LatLng 추가 (검색 시 부드러운 줌인 옵션 대비) - MenuList: for (const list of map.values()) → map.forEach (downlevelIteration 미설정 환경에서 컴파일 통과)
…1 (Node 22+ 필요) 받아오는 문제 - 증상: Dockerfile (node:20-alpine) 에서 pnpm install 시 ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite - 원인: packageManager 필드 부재 → corepack 이 npm registry 최신 pnpm 자동 다운로드 - 해결: 컨테이너·로컬·CI 모두 같은 버전 쓰도록 pin
…ger 와 중복 - 증상: ERR_PNPM_BAD_PM_VERSION (Multiple versions of pnpm specified) - 해결: workflow 에서 version 지정 제거. action-setup v4 가 packageManager 필드를 자동 추출 - packageManager(package.json) 를 단일 진실의 원천으로 — 로컬·Docker·CI 모두 같은 버전
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
광운대 일대 식당 정보를 본격 등록하면서 함께 발견된 UX·데이터 모델 이슈를 함께 정리한 PR. 데이터/UI/지도 모두에서 큰 변화.
Major changes
데이터·스키마
Menu.category+Menu.priceOptions(Json) — 옵션가격 지원Restaurant.coverImageUrl+Restaurant.externalMenuUrl(체인점 공식 메뉴 페이지)College.FREE_MAJOR,Zone.UICHEON추가kwoondae/frontgate/backgate _menus.py) +import-py-menus.py(clock_timestamp 로 row 별 createdAt 미세차 부여해 입력순 정렬)API
RESTAURANT_LIST_SELECT/RESTAURANT_DETAIL_SELECT에priceOptions+category포함 (이전에 누락되어 사이즈가격 미노출 버그)featuredMenuorderBy:[isSignature, price]→createdAt asc(입력 첫 메뉴 = 대표)Web
MenuList카테고리 가로 탭 (스크롤) + priceOptions 줄 표기 + 면책 문구 + 카테고리 우선순위 정렬RestaurantHero카루셀 (coverImageUrl + 메뉴 이미지)useMarkerClustererselectedId 지원 + 12종 SVG 이미지 lazy 캐시, 흰 stroke 굵게 + 1.22x 사이즈로 선택 강조mapStore(Zustand) — 지도 중심·줌 인메모리 보존KakaoMapsetTimeout setCenter → relayout (복원 위치 덮어쓰기 버그 수정)ClusterPicker항목 선택 →router.push(/restaurants/:id)Seed/스크립트
out.sql,partnerships.sql)compress-cover-images.py)Test plan