Skip to content

✨ Feat: 산책 지오펜스(울타리) 및 실시간 위치 추적 기능 - #63

Merged
sooloin merged 12 commits into
DoDo-Project:developfrom
sooloin:feature/62
Jun 15, 2026
Merged

✨ Feat: 산책 지오펜스(울타리) 및 실시간 위치 추적 기능#63
sooloin merged 12 commits into
DoDo-Project:developfrom
sooloin:feature/62

Conversation

@sooloin

@sooloin sooloin commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

📄 작업 내용 (Description)

이번 PR에서 변경되거나 추가된 주요 작업을 간단히 설명해주세요.

산책 페이지에 반려동물 안전 울타리(지오펜스)와 실시간 위치 추적 기능을 구현했습니다.

지도 & 레이아웃

  • 네이버 지도(NCP Maps) 연동 (스크립트 로더 + 전역 타입)
  • 네이버 지도 스타일 전체화면 레이아웃 (상단 헤더 숨김, 좌측 세로 레일 + 둥근 플로팅 패널)

울타리(REST)

  • Fence REST API 6종 연동 (생성/상태조회/토글/범위수정/경계조회/경계목록)
  • 지도 클릭으로 중심 지정 + 미리보기 원, 울타리 생성/수정/ON·OFF 조작 UI
  • 울타리 경계를 지도에 원으로 표시, 펫 이름 라벨 노출

실시간 위치(WebSocket / STOMP)

  • @stomp/stompjs 기반 STOMP 클라이언트 (자동 재연결, 연결 직전 토큰 주입)
  • useLiveLocation 훅: /sub/fence/location/{petId} 구독 → 실시간 좌표 수신
  • 실시간 위치 마커 표시 및 울타리 이탈 시 경고 배너 (울타리 활성 시에만)

🔗 관련 이슈 (Related Issues)

작업한 이슈 번호를 아래 형식으로 PULL REQUEST BODY에 작성해주세요.
(PR 머지 시 해당 이슈가 자동으로 종료됩니다.)


✅ 체크리스트 (Checklist)

PR을 보내기 전 아래 항목들을 모두 확인해주세요.

  • PR 제목은 커밋 컨벤션을 따랐습니다.
  • 관련 이슈를 연결했습니다.
  • 스스로 코드를 검토하고 불필요한 코드를 제거했습니다.
  • 코드 스타일이 프로젝트 규칙과 일치합니다. (Style)
  • 새로운 기능에 대한 테스트 코드를 추가했거나, 기존 테스트가 모두 통과했습니다. (Test)

📸 스크린샷 (Screenshots)

작업 내용과 관련된 스크린샷이 있다면 첨부해주세요. (UI 변경이 있는 경우)

image

💬 기타 사항 (Etc)

리뷰어에게 전달하고 싶은 추가 정보가 있다면 자유롭게 작성해주세요.

  • 신규 환경변수: VITE_NAVER_MAP_CLIENT_ID, VITE_WS_URL (.env 설정 필요)
  • WebSocket 구독 경로는 /sub/fence/location/{petId} (앞 슬래시 포함)로 구현
  • getFenceBoundary(단일 경계 조회), useFenceStatus는 현재 화면에선 미사용이나 추후 활용 위해 유지
  • 산책 페이지는 /walk에서만 헤더를 숨기고 전체화면으로 렌더 (다른 페이지 영향 없음)

sooloin added 10 commits June 15, 2026 15:29
- NCP Maps 스크립트 로더 추가 (중복 로드 방지)
- 네이버 지도 전역 타입 최소 선언
- WalkMap 컴포넌트로 지도 렌더링 및 언마운트 시 정리
- env에 VITE_NAVER_MAP_CLIENT_ID 추가

`DoDo-Project#62`
- Fence REST API 타입 및 axios 함수 6종 추가
  (생성/상태조회/토글/범위수정/경계조회/경계목록)
- 조회·뮤테이션 react-query 훅 추가
  (useFenceBoundaries/useFenceStatus/useCreateFence
   /useToggleFence/useUpdateFenceRange)
- queryKey에 fence 키 추가

`DoDo-Project#62`
- 네이버 지도 전역 타입에 Circle 추가
- 울타리 경계 목록을 지도에 원(Circle)으로 렌더링
- 활성/비활성 상태에 따라 색상 구분, 첫 울타리로 중심 이동

`DoDo-Project#62`
- 지도 클릭으로 울타리 중심 지정 + 점선 미리보기 원 표시
- WalkMap을 props 기반(boundaries/draftCenter/onMapClick)으로 리팩터링
- FenceControlPanel 추가: 펫 선택, 이름·반경 입력, 생성/수정/토글
- naver 타입에 지도 클릭 이벤트(addListener)·LatLng 접근자 추가
- 변경 후 boundaries 재조회로 지도 갱신

`DoDo-Project#62`
- 네이버 지도 스타일 레이아웃 적용 (좌측 사이드바 + 전체 폭 지도)
- 울타리 ON/OFF 토글 렌더링 정상화 및 접근성(role/aria) 보강
- 울타리 원 중심에 펫 이름 라벨(Marker) 표시
- 수정 모드에서도 울타리 이름 변경 가능하도록 입력 노출
- naver 타입에 Marker/Point 추가

`DoDo-Project#62`
- 산책 페이지에서 상단 헤더 숨기고 전체화면 지도 레이아웃 적용
- WalkSideRail 추가: 좌측 세로 레일에 로고·메뉴·프로필 배치
- 지도 위 둥근 플로팅 카드(레일+울타리 패널) 구조로 변경
- header 배럴에 useIsLoggedIn 노출

`DoDo-Project#62`
- @stomp/stompjs 설치 (WebSocket STOMP 통신용)
- env에 VITE_WS_URL 추가

`DoDo-Project#62`
- createStompClient: brokerURL/자동 재연결/heartbeat 설정
- beforeConnect에서 연결 직전 최신 액세스 토큰을 헤더에 주입

`DoDo-Project#62`
- LiveLocationMessage / LiveLocationPayload 타입 추가
- 서버가 내려주는 insideFence·distanceMeter 등 포함

`DoDo-Project#62`
- STOMP 연결 후 /sub/fence/location/{petId} 구독
- 수신 payload를 location 상태로 관리, 연결 상태 노출
- petId 변경/언마운트 시 연결 해제로 누수 방지

`DoDo-Project#62`
@sooloin
sooloin requested review from WhiteBin-bin and limhb708 June 15, 2026 09:06
@sooloin sooloin self-assigned this Jun 15, 2026
@sooloin sooloin added ✨ Feature 새 기능 혹은 요구 사항 😸 조수빈 조수빈 파트 labels Jun 15, 2026
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

@sooloin is attempting to deploy a commit to the sooloin's projects Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dodo-frontend Ready Ready Preview, Comment Jun 15, 2026 9:20am

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements a pet walking safety fence (geofence) feature, introducing Naver Maps integration, real-time location tracking via STOMP/WebSockets, and management controls for creating, toggling, and updating fences. Feedback on the changes highlights a bug where the draft center preview is not cleared after fence creation or update, a UX issue where adjusting the radius slider forces the map to snap back to the center, and an inconsistency where the update button is not disabled when the fence name is empty.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +56 to +78
const handleCreate = () => {
if (selectedPetId == null || !draftCenter) return;
createFence.mutate({
petId: selectedPetId,
centerLatitude: draftCenter.lat,
centerLongitude: draftCenter.lng,
radius,
fenceName: fenceName.trim(),
});
};

const handleUpdate = () => {
if (!existingFence) return;
updateFenceRange.mutate({
fenceId: existingFence.fenceId,
payload: {
centerLatitude: draftCenter?.lat ?? existingFence.center.latitude,
centerLongitude: draftCenter?.lng ?? existingFence.center.longitude,
radius,
fenceName: fenceName.trim() || existingFence.fenceName,
},
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

울타리를 성공적으로 생성(handleCreate)하거나 수정한 후(handleUpdate), 지도상의 주황색 점선 미리보기 원(draftCenter)이 초기화되지 않아 저장된 후에도 미리보기 원이 지도에 계속 남아있는 버그가 있습니다. 뮤테이션 성공 시 setDraftCenter(null)을 호출하여 상태를 초기화해 주어야 합니다.

  const handleCreate = () => {
    if (selectedPetId == null || !draftCenter) return;
    createFence.mutate(
      {
        petId: selectedPetId,
        centerLatitude: draftCenter.lat,
        centerLongitude: draftCenter.lng,
        radius,
        fenceName: fenceName.trim(),
      },
      {
        onSuccess: () => {
          setDraftCenter(null);
        },
      },
    );
  };

  const handleUpdate = () => {
    if (!existingFence) return;
    updateFenceRange.mutate(
      {
        fenceId: existingFence.fenceId,
        payload: {
          centerLatitude: draftCenter?.lat ?? existingFence.center.latitude,
          centerLongitude: draftCenter?.lng ?? existingFence.center.longitude,
          radius,
          fenceName: fenceName.trim() || existingFence.fenceName,
        },
      },
      {
        onSuccess: () => {
          setDraftCenter(null);
        },
      },
    );
  };

Comment on lines +130 to +162
useEffect(() => {
const map = mapInstanceRef.current;
if (!isMapReady || !map) return;

// 중심이 없으면 미리보기 원 제거
if (!draftCenter) {
draftCircleRef.current?.setMap(null);
draftCircleRef.current = null;
return;
}

const center = new naver.maps.LatLng(draftCenter.lat, draftCenter.lng);

if (draftCircleRef.current) {
// 이미 있으면 위치/반경만 갱신
draftCircleRef.current.setCenter(center);
draftCircleRef.current.setRadius(draftRadius);
} else {
draftCircleRef.current = new naver.maps.Circle({
map,
center,
radius: draftRadius,
strokeColor: '#f59e0b',
strokeWeight: 2,
strokeStyle: 'shortdash',
fillColor: '#f59e0b',
fillOpacity: 0.12,
});
}

map.setCenter(center);
}, [isMapReady, draftCenter, draftRadius]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

draftRadius(반경)가 변경될 때마다 map.setCenter(center)가 호출되어, 사용자가 반경 슬라이더를 조절할 때 지도가 계속해서 중심 좌표로 강제 이동(스냅)되는 현상이 발생합니다. 지도의 중심 이동은 draftCenter가 실제로 변경될 때만 수행하도록 로직을 분리하는 것이 좋습니다.

  // 3. 미리보기(점선) 원 그리기/갱신
  useEffect(() => {
    const map = mapInstanceRef.current;
    if (!isMapReady || !map) return;

    // 중심이 없으면 미리보기 원 제거
    if (!draftCenter) {
      draftCircleRef.current?.setMap(null);
      draftCircleRef.current = null;
      return;
    }

    const center = new naver.maps.LatLng(draftCenter.lat, draftCenter.lng);

    if (draftCircleRef.current) {
      // 이미 있으면 위치/반경만 갱신
      draftCircleRef.current.setCenter(center);
      draftCircleRef.current.setRadius(draftRadius);
    } else {
      draftCircleRef.current = new naver.maps.Circle({
        map,
        center,
        radius: draftRadius,
        strokeColor: "#f59e0b",
        strokeWeight: 2,
        strokeStyle: "shortdash",
        fillColor: "#f59e0b",
        fillOpacity: 0.12,
      });
    }
  }, [isMapReady, draftCenter, draftRadius]);

  // 3-1. draftCenter 변경 시에만 지도 중심 이동
  useEffect(() => {
    const map = mapInstanceRef.current;
    if (!isMapReady || !map || !draftCenter) return;
    map.setCenter(new naver.maps.LatLng(draftCenter.lat, draftCenter.lng));
  }, [isMapReady, draftCenter]);

Comment on lines +144 to +151
<button
type="button"
onClick={onUpdate}
disabled={isSubmitting}
className="inline-flex min-h-11 items-center justify-center rounded-xl bg-brand px-5 text-sm font-semibold text-brand-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
>
울타리 수정 저장
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

울타리 생성 버튼은 이름이 비어있을 때 비활성화되지만, '울타리 수정 저장' 버튼은 이름이 비어있어도 비활성화되지 않습니다. 이 경우 수정을 시도하면 기존 이름으로 자동 대체되거나 빈 값으로 요청이 갈 수 있어 사용자에게 혼란을 줄 수 있습니다. 수정 시에도 이름이 비어있다면 버튼을 비활성화하는 것이 안전합니다.

Suggested change
<button
type="button"
onClick={onUpdate}
disabled={isSubmitting}
className="inline-flex min-h-11 items-center justify-center rounded-xl bg-brand px-5 text-sm font-semibold text-brand-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
>
울타리 수정 저장
</button>
<button
type="button"
onClick={onUpdate}
disabled={isSubmitting || fenceName.trim().length === 0}
className="inline-flex min-h-11 items-center justify-center rounded-xl bg-brand px-5 text-sm font-semibold text-brand-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
>
울타리 수정 저장
</button>

- 이탈 판정에 울타리 활성(isActive) 조건 추가
- 울타리가 꺼져 있으면 알림 배너·빨강 마커 미표시

`DoDo-Project#62`
- 울타리 생성/수정 성공 시 미리보기 원(draftCenter) 초기화
- 지도 중심 이동을 draftCenter 변경 시에만 수행해 반경 조절 시 스냅 방지
- 울타리 수정 저장 버튼도 이름이 비어있으면 비활성화

`DoDo-Project#62`
@sooloin
sooloin merged commit 516fdb8 into DoDo-Project:develop Jun 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새 기능 혹은 요구 사항 😸 조수빈 조수빈 파트

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 산책 - 지오펜스(Fence) 실시간 위치 추적 기능 구현

2 participants