-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] 매물 상세 페이지 상세주소 추가 #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
…t/home-create-api
…t/home-create-api
Walkthrough여러 홈 생성/수정/상세/목록 컴포넌트의 UI와 데이터 흐름이 업데이트되었고, 이미지 업로드·삭제와 폼 바인딩 방식이 변경되었습니다. 업데이트 API가 multipart/form-data(FormData)로 전환되었으며, 지도/채팅 흐름이 반응형 로직으로 수정되었습니다. 신규 아이콘 컴포넌트가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant HU as HomeUpdatePage
participant F as Forms (Basic/Price/Room/Facility)
participant IU as ImageUploader
participant API as updateListing (multipart)
U->>F: 폼 값 입력/변경 (update:form)
F-->>HU: v-model:form 동기화
U->>IU: 이미지 추가/삭제
IU-->>HU: update:images / update:newImages / image-deleted
U->>HU: 저장 클릭
HU->>API: FormData 전송 (필드, 배열, 삭제ID, 파일)
API-->>HU: 응답
HU-->>U: 완료/에러 표시
sequenceDiagram
participant U as User
participant HD as HomeDetailsPage
participant ChatAPI as createChatRoom
participant Router as Router
participant CP as ChatPage
U->>HD: 채팅 버튼 클릭
HD->>ChatAPI: 방 생성 요청(listingId)
ChatAPI-->>HD: roomId
HD->>Router: /chat?roomId=roomId
Router-->>CP: 라우팅
alt roomId 존재
CP->>CP: ChatRoom 렌더
else 없음
CP->>CP: ChatPlaceholder 렌더
end
sequenceDiagram
participant ML as MapLocation.vue
participant G as Kakao Geocoder
ML->>ML: watch(address, immediate)
ML->>G: addressSearch(address)
G-->>ML: 좌표 결과
ML->>ML: 지도 중심 업데이트
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
🔭 Outside diff range comments (4)
src/components/homes/homedetails/ListingBasicInfo.vue (1)
18-20: 오타: maintenaceFee → maintenanceFee (표시값이 0원으로 보일 수 있음)프로퍼티 이름 오타로 관리비가 항상 0처럼 표시될 수 있습니다. 즉시 수정 권장합니다.
- <div class="text-yellow-500 font-semibold text-md"> - 관리비 {{ formatNumber(listing.maintenaceFee) }}원 - </div> + <div class="text-yellow-500 font-semibold text-md"> + 관리비 {{ formatNumber(listing.maintenanceFee) }}원 + </div>src/pages/homes/HomeListPage.vue (1)
245-251: totalItems 계산에서 런타임 오류 가능성 있음
response.totalElements || response.data.length는 totalElements가 0일 때(유효한 값) 우변 평가를 시도하며,response.data가 없으면 에러가 납니다.content.length또는 null 병합 연산자를 사용하세요.- totalItems.value = response.totalElements || response.data.length + totalItems.value = typeof response.totalElements === 'number' + ? response.totalElements + : (Array.isArray(response.content) ? response.content.length : 0)src/components/homes/homecreate/Step3DetailInfo.vue (1)
12-22: 숫자 입력의 소수점·빈값 처리 보완 필요 (NaN 전파 방지 + 소수 입력 허용)
- 현재 @input에서 valueAsNumber를 그대로 전달하고 있어 입력을 지우는 순간 NaN이 form에 전파됩니다.
- 면적(㎡)은 소수 허용이 일반적이므로 step="0.01" 등의 설정이 필요합니다.
아래처럼 handleChange에서 NaN을 방지하고, 면적 필드에는 step을 추가하는 것을 권장합니다.
적용 제안(diff):
- <input + <input type="number" min="0" - class="border rounded p-2 pr-20 w-full no-spin" + step="0.01" + class="border rounded p-2 pr-20 w-full no-spin" :value="form.exclusiveArea" @input="handleChange('exclusiveArea', $event.target.valueAsNumber)" required placeholder="0" /> @@ - <input + <input type="number" min="0" - class="border rounded p-2 pr-20 w-full no-spin" + step="0.01" + class="border rounded p-2 pr-20 w-full no-spin" :value="form.supplyArea" @input="handleChange('supplyArea', $event.target.valueAsNumber)" placeholder="0" />handleChange 개선(diff):
-const handleChange = (key, value) => { - emit('update:form', { - ...props.form, - [key]: value, - }) -} +const handleChange = (key, value) => { + const next = + typeof value === 'number' + ? (Number.isNaN(value) ? 0 : value) + : value + emit('update:form', { + ...props.form, + [key]: next, + }) +}Also applies to: 28-36, 46-54
src/pages/chat/ChatPage.vue (1)
20-30: 채팅방 생성 시 오류 처리 개선 필요
createChatRoom함수에서 API 응답의 success 필드를 확인하지만, success가 false인 경우에 대한 처리가 없습니다. 또한 roomId 생성 실패 시 사용자에게 알림이 필요합니다.const createChatRoom = async () => { try { const response = await axios.post('/api/chat/rooms') if (response.data.success && response.data.data) { initialRoomId.value = response.data.data.toString() console.log('Chat room created with ID:', initialRoomId.value) + } else { + console.error('Failed to create chat room: Invalid response', response.data) + // TODO: 사용자에게 오류 알림 } } catch (error) { console.error('Failed to create chat room:', error) + // TODO: 사용자에게 오류 알림 } }
🧹 Nitpick comments (24)
src/apis/listing.js (1)
90-97: FormData 전송 시 Content-Type 헤더 제거 및 api 인스턴스 기본 설정 검토updateListing에서 수동으로 지정된
Content-Type헤더를 제거해주세요. axios가 FormData 전달 시 자동으로multipart/form-data와 boundary를 설정합니다.
호출부(HomeUpdatePage.vue)에서는 이미 FormData를 전달 중이므로 별도 수정 없이 정상 동작합니다.
- 수정 대상:
src/apis/listing.js- const response = await api.put(`${API_BASE_URL}/${id}`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }) + const response = await api.put(`${API_BASE_URL}/${id}`, formData)
- (선택) api 인스턴스 기본
Content-Type고정 제거:src/apis/index.js- headers: { - 'Content-Type': 'application/json', - }, + // Content-Type은 요청 데이터에 따라 axios가 자동 설정하도록 둡니다.src/components/homes/homedetails/ListingBasicInfo.vue (1)
8-14: 가격 표시 ‘원’ 접미사 추가는 적절 —leaseType값 일관성 확보를 권장‘전세’/‘월세’ 가격 뒤에 ‘원’을 붙인 것은 상세·카드·생성 화면 전반에 걸쳐 잘 맞춰졌습니다. 다만, 코드 전반에서
leaseType을
- 일부는 한글(‘전세’/‘월세’)
- 일부는 영문(‘JEONSE’/‘WOLSE’)
으로 비교하고 있어 유지보수성과 버그 위험이 있습니다.
제안 사항
- 백엔드가 반환하는
leaseTypeenum(JEONSE/WOLSE)을 통일된 매핑 함수나 상수 객체로 처리하세요.// 예시: src/utils/leaseType.js export const LeaseType = { JEONSE: 'JEONSE', WOLSE: 'WOLSE', }; export const LeaseTypeLabels = { [LeaseType.JEONSE]: '전세', [LeaseType.WOLSE]: '월세', }; export function formatLeasePrice(type, deposit, rent) { const label = LeaseTypeLabels[type] || ''; return type === LeaseType.WOLSE ? `${label} ${formatNumber(deposit)}원 / ${formatNumber(rent)}원` : `${label} ${formatNumber(deposit)}원`; }- 화면 컴포넌트에서는 한글/영문 직접 비교를 모두 제거하고, 위 유틸 함수를 호출하도록 리팩터링하세요.
- src/components/homes/homedetails/ListingBasicInfo.vue
- src/components/homes/homelist/ListingCard.vue
- src/pages/risk-check/RiskCheckResult.vue
- 그 외
leaseType === '전세' | '월세' | 'JEONSE' | 'WOLSE'패턴이 발견된 곳이렇게 하면
leaseType비교 로직이 한곳에 모이고, 나중에 다른 로케일을 추가하거나 백엔드 값이 바뀌어도 한 곳만 수정하면 되어 안정성이 높아집니다.src/components/homes/homecreate/Step1BasicInfo.vue (1)
118-121: 디버그 콘솔 로그 제거 또는 DEV 환경 한정 출력프로덕션 번들에 남겨두면 콘솔 노이즈가 생깁니다. 단순 제거를 권장합니다.
- // ✨ 추가된 부분: 디버깅을 위해 선택된 주소값을 콘솔에 출력 - console.log('✅ onAddressSelect 함수 호출됨. 선택된 주소:', selectedAddress) @@ - // ✨ 추가된 부분: 업데이트된 form.addr1 값 확인 - console.log('업데이트 후 form 객체:', props.form)Also applies to: 126-128
src/components/homes/homecreate/StepProgressIndicator.vue (2)
3-3: v-for key로 index 대신 step 사용 권장정적 배열이긴 하지만, key로 index를 쓰면 향후 단계명이 변동되거나 재정렬될 때 예기치 않은 재렌더링 이슈가 생길 수 있습니다. label(step) 자체를 key로 쓰면 더 안전합니다.
- <div v-for="(step, index) in steps" :key="index" class="w-full flex items-center gap-2"> + <div v-for="(step, index) in steps" :key="step" class="w-full flex items-center gap-2">
18-18: 마지막 단계 뒤 커넥터 라인이 항상 노출됩니다 — 확인 필요
v-if="index < steps.length"조건은 모든 인덱스(0..length-1)에 대해 true라 마지막 단계 뒤에도 라인이 생깁니다. 의도라면 그대로 두고, 일반적인 단계 표시(마지막 뒤 라인 없음)를 원하시면 아래처럼 수정하세요.- <div v-if="index < steps.length" class="flex-1 h-px bg-gray-300 ml-2"></div> + <div v-if="index < steps.length - 1" class="flex-1 h-px bg-gray-300 ml-2"></div>src/components/homes/homelist/FilterSidebar.vue (3)
3-21: 모바일 닫기 버튼의 absolute 위치 기준이 불분명합니다현재 aside가 position: static이라 버튼의 absolute 기준이 문서(body)로 잡힐 수 있습니다. aside에 relative를 추가해 해당 영역 우상단에 안정적으로 위치시키는 것을 권장합니다. 또한 혹시 폼 내부에 포함될 가능성에 대비해 버튼에 type="button" 명시를 권장합니다.
아래 변경(템플릿 상단 aside에 relative 추가):
- <aside class="w-full md:w-64 bg-white px-4 py-6 border-r border-gray-200 space-y-6"> + <aside class="relative w-full md:w-64 bg-white px-4 py-6 border-r border-gray-200 space-y-6">닫기 버튼 type 지정:
- <button + <button + type="button" @click="$emit('close')" class="absolute top-4 right-4 md:hidden text-gray-500 hover:text-gray-800" >
159-166: 체크박스 토글 시 이벤트 payload(checked)를 활용해 상태를 ‘설정’하도록 변경 권장현재는 토글만 수행합니다. 대부분의 경우 동작하지만, 이중 이벤트 발생 등의 상황에서 의도치 않은 상태가 될 수 있습니다. 이벤트가 넘겨주는 checked 값을 그대로 반영하는 방식이 더 견고합니다.
템플릿과 메서드 동시 수정:
- <BaseCheckbox + <BaseCheckbox v-for="opt in conditions" :key="opt" :label="opt" :modelValue="filters.conditions.includes(opt)" - @update:modelValue="toggleCondition(opt)" + @update:modelValue="(checked) => toggleCondition(opt, checked)" />-function toggleCondition(condition) { - const index = filters.value.conditions.indexOf(condition) - if (index > -1) { - filters.value.conditions.splice(index, 1) - } else { - filters.value.conditions.push(condition) - } -} +function toggleCondition(condition, checked) { + const set = new Set(filters.value.conditions) + if (checked) { + set.add(condition) + } else { + set.delete(condition) + } + filters.value.conditions = Array.from(set) +}Also applies to: 283-291
217-224: 매물종류 상수 테이블 도입 LGTMlabel/value 분리로 UI/요청 파라미터를 깔끔하게 정리했습니다. 다른 곳에서도 재사용될 수 있도록 별도 상수 모듈로 추출하는 것도 고려해볼 만합니다.
src/pages/homes/HomeListPage.vue (2)
214-232: 층수 매핑에서 ‘반지하’ 분기가 사양과 불일치FilterSidebar의 floors에는 ‘반지하’ 옵션이 없습니다. 불필요한 분기는 제거하거나, 실제로 필요하다면 FilterSidebar에도 옵션을 추가해 두 파일 간 사양을 일치시키세요.
정리 예:
- 불필요하면 해당 분기 삭제
- 필요하면 floors = ['반지하', '1층', '2
5층', '69층', '10층 이상']로 통일
253-253: 디버그 로그는 프로덕션에서는 제거 권장배포 전에는 콘솔 로그를 제거하거나 환경 변수로 가드해 주세요.
src/components/homes/homeupdate/FacilityInfoForm.vue (1)
85-90: 정의 순서 가독성 개선 제안
allValidIds가utilityItems를 참조하므로,utilityItems정의를 위로 올리면 가독성이 좋아집니다(동작 문제는 없음).Also applies to: 91-129
src/components/homes/homecreate/Step3DetailInfo.vue (1)
21-21: 단위 표기(‘㎡’ vs ‘제곱미터’) 프로젝트 전반 통일 제안본 컴포넌트는 ‘㎡’, 업데이트 폼(RoomInfoForm.vue)은 ‘제곱미터’를 사용합니다. 사용자 혼란 방지를 위해 ‘㎡’로 통일하는 것을 권장합니다. 간단한 뷰 텍스트 변경만으로 해결되며, 이전 PR에서의 사용자 선호(간결한 구조 선호)도 고려했습니다.
Also applies to: 36-36
src/components/homes/homeupdate/PriceInfoForm.vue (2)
141-160: 불필요한 분기 정리 제안 (가독성 개선)parsed는 숫자 보장을 위해 이미 전처리되고 있어, null 체크 분기(field === ...)는 실효성 없습니다. 간결하게 정리해도 동작 동일합니다.
적용 제안(diff):
- if (field === 'depositPrice' || field === 'monthlyRent' || field === 'maintenanceFee') { - parsed = parsed === null ? 0 : parsed - } - emit('update:form', { ...props.form, [field]: parsed })
11-22: 모바일 숫자 입력 UX 개선: inputmode/pattern 추가 권장type="text" + 포맷팅 패턴은 모바일 키패드가 숫자 전용으로 뜨지 않을 수 있습니다. 아래 속성을 추가하면 입력 경험이 좋아집니다.
적용 제안(diff):
- <input + <input type="text" + inputmode="numeric" + pattern="[0-9]*"해당 통일을 전세/월세 모든 금액 입력에 적용하는 것을 권장합니다.
Also applies to: 62-75, 80-91, 97-109
src/components/homes/homeupdate/ImageUploader.vue (2)
93-105: 기존 이미지 삭제 이벤트의 실패 분기 개선 제안URL에서 ID를 파싱하지 못하면 현재는 경고만 로그합니다. 부모가 URL만으로도 삭제를 처리할 수 있게, 실패 시에도 URL을 포함해 이벤트를 보내는 것이 견고합니다.
적용 제안(diff):
- if (!isNaN(imageId)) { - emit('image-deleted', imageId) - } else { - console.warn(`Could not extract a valid image ID from URL: ${imgUrl}`) - } + if (!isNaN(imageId)) { + emit('image-deleted', imageId) + } else { + console.warn(`Could not extract a valid image ID from URL: ${imgUrl}`) + emit('image-deleted', { url: imgUrl }) + }부모에서 숫자/객체 형태 모두 수용하도록 타입을 유연하게 처리해 주세요.
79-82: 업로드 제한 초과 시 부분 수용(clip) 지원 제안남은 슬롯보다 많은 파일 선택 시 전체를 거절하는 대신, 허용 가능한 개수만 잘라서 수용하면 UX가 좋아집니다.
적용 아이디어:
- files.slice(0, remainingSlots)만 반영하고 나머지는 안내 토스트/알럿
src/components/homes/homeupdate/RoomInfoForm.vue (3)
12-23: 면적 단위/입력 경험 개선(소수 허용 + 단위 표기 통일)
- 면적은 소수 허용을 위해 step="0.01" 추가를 권장합니다.
- 단위 텍스트 ‘제곱미터’ 대신 ‘㎡’로 통일하면 Step3DetailInfo.vue와 일관됩니다.
[Suggest reflects user preference for simpler structures; 최소 변경으로 UI/입력 경험 개선을 제안합니다.]
적용 제안(diff):
- <input + <input type="number" min="0" - class="border rounded p-2 pr-20 w-full no-spin" + step="0.01" + class="border rounded p-2 pr-20 w-full no-spin" @@ - <span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">제곱미터</span> + <span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">㎡</span> @@ - <input + <input type="number" min="0" - class="border rounded p-2 pr-20 w-full no-spin" + step="0.01" + class="border rounded p-2 pr-20 w-full no-spin" @@ - <span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">제곱미터</span> + <span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">㎡</span>Also applies to: 25-37
78-107: 층수 입력 UI 일관성 제안(‘층’ 단위 표시 + no-spin)Step3DetailInfo.vue처럼 ‘층’ 단위 배지와 no-spin 클래스를 적용하면 생성/수정 페이지 간 UX 일관성이 좋아집니다.
적용 아이디어:
- relative 래퍼 + 우측 ‘층’ 배지 + 입력에 pr-12/no-spin 클래스 적용
121-139: 방향 선택 그리드의 반응형 개선 제안생성 폼은 grid-cols-2 md:grid-cols-4로 반응형을 적용했습니다. 동일 적용 시 모바일 가독성이 좋아집니다.
적용 제안(diff):
- <div class="grid grid-cols-4 gap-2 max-w-xl"> + <div class="grid grid-cols-2 md:grid-cols-4 gap-2 max-w-xl">src/pages/homes/HomeUpdatePage.vue (3)
168-172: 업로드 항목 안전성 체크(파일 없는 항목 방지)가끔 썸네일/프리뷰 전용 아이템 등 파일이 없는 엔트리가 섞일 수 있습니다. 방어적으로 체크해 불필요한 append를 피하세요.
- newImages.value.forEach((img) => { - formData.append('newImages', img.file) - }) + newImages.value.forEach((img) => { + if (img?.file) { + formData.append('newImages', img.file) + } + })
65-66: 에러/빈 상태 메시지 개선 제안에러가 없지만
homeId가 없어 else 분기로 오면 빈 문자열이 붉게 표시될 수 있습니다. 기본 안내문을 제공하면 UX가 좋아집니다.- {{ error }} + {{ error || '매물 정보를 찾을 수 없습니다.' }}
33-41: 이미지 업로드 수 제한 실제 검증 여부 확인UI에 “최대 10장” 문구가 있으나, 이 컴포넌트에서는 실제 제한 로직을 두지 않습니다. ImageUploader 내부에서 제한을 강제하고 있는지 확인해 주세요. 없다면 서버 에러로 되돌아올 수 있습니다.
원하시면 ImageUploader의 v-model:newImages 변경 시 길이 검사 및 토스트 노출 로직을 제안드리겠습니다.
src/assets/icons/PetA.vue (2)
5-5: viewBox 속성의 불필요한 공백 제거SVG viewBox 속성에 "0 0" 다음에 이중 공백이 있습니다. 단일 공백으로 수정해야 합니다.
- viewBox="0 0 24 24" + viewBox="0 0 24 24"
16-29: 불필요한 import 제거
defineProps는 Vue 3의 컴파일러 매크로로 명시적 import가 필요하지 않습니다.<script setup> -import { defineProps } from 'vue' - const props = defineProps({ width: { type: [String, Number], default: '24', }, height: { type: [String, Number], default: '24', }, }) </script>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (21)
src/apis/listing.js(1 hunks)src/assets/icons/PetA.vue(1 hunks)src/components/homes/homecreate/Step1BasicInfo.vue(4 hunks)src/components/homes/homecreate/Step2PriceInfo.vue(2 hunks)src/components/homes/homecreate/Step3DetailInfo.vue(6 hunks)src/components/homes/homecreate/StepProgressIndicator.vue(2 hunks)src/components/homes/homedetails/ListingBasicInfo.vue(1 hunks)src/components/homes/homedetails/MapLocation.vue(2 hunks)src/components/homes/homedetails/RoomDetails.vue(6 hunks)src/components/homes/homelist/FilterSidebar.vue(11 hunks)src/components/homes/homelist/ListingCard.vue(1 hunks)src/components/homes/homeupdate/BasicInfoForm.vue(1 hunks)src/components/homes/homeupdate/FacilityInfoForm.vue(1 hunks)src/components/homes/homeupdate/ImageUploader.vue(2 hunks)src/components/homes/homeupdate/PriceInfoForm.vue(1 hunks)src/components/homes/homeupdate/RoomInfoForm.vue(1 hunks)src/pages/chat/ChatPage.vue(2 hunks)src/pages/homes/HomeCreatePage.vue(2 hunks)src/pages/homes/HomeDetailsPage.vue(4 hunks)src/pages/homes/HomeListPage.vue(5 hunks)src/pages/homes/HomeUpdatePage.vue(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-01T06:38:26.707Z
Learnt from: minnieming
PR: ITZEEP/frontend#54
File: src/components/pre-contract/buyer/step5/Step5Sub1MoveIn.vue:88-97
Timestamp: 2025-08-01T06:38:26.707Z
Learning: minnieming prefers simpler component structures and is comfortable with conditional rendering approaches rather than complex validation logic refactoring in Vue components like Step5Sub1MoveIn.vue.
Applied to files:
src/components/homes/homecreate/Step1BasicInfo.vuesrc/components/homes/homecreate/Step3DetailInfo.vuesrc/components/homes/homecreate/StepProgressIndicator.vue
🧬 Code Graph Analysis (1)
src/apis/listing.js (1)
src/apis/index.js (1)
api(4-9)
🔇 Additional comments (32)
src/components/homes/homecreate/Step2PriceInfo.vue (2)
45-55: 체크박스 그리드의 반응형 전환 LGTM
grid-cols-1 md:grid-cols-2로의 전환으로 모바일 가독성이 개선됩니다. 변경 취지와 구현 모두 적절합니다.Also applies to: 113-123
107-107: ‘원’ 단위 표준화 확인 필요코드베이스에 여전히 ‘만원’ 단위가 다수 남아 있어, 클라이언트-서버 간 단위 불일치 가능성이 있습니다. 백엔드/DB에서 금액을 ‘원’ 단위로 전달하는지, 프런트엔드에서 ‘만원’으로 변환하여 표시하는 로직이 의도대로 동작하는지 최종 확인 부탁드립니다.
주요 확인 위치:
- src/pages/risk-check/RiskCheckResult.vue (월세/전세 보증금·월세 표시)
- src/components/risk-check/PropertyInfoForm.vue (입력란 단위 레이블)
- src/components/risk-check/PropertyCard.vue (priceDisplay 포맷)
- src/components/homes/homelist/FilterSidebar.vue (최대: {{ filters.* }}만원)
- src/components/pre-contract/common/.../HomeMaintenanceInfo.vue (관리비 단위)
- src/components/mypage/fraud-analysis/FraudAnalysisCard.vue (분석 카드 단위)
- src/mocks/risk/propertyMockData.json, src/constants/insuranceData.js 등
위 파일들의 ‘만원’ 표기가 의도된 것인지, 혹은 모두 ‘원’ 단위로 통일해야 하는지 검토해 주세요.
src/components/homes/homelist/ListingCard.vue (1)
25-32: 가격 표시에 ‘원’ 접미사 추가 — UX 일관성 향상카드/상세/생성 화면 간 금액 표기 규칙이 통일되어 가독성이 좋아졌습니다. 변경 사항 문제 없습니다.
src/components/homes/homecreate/Step1BasicInfo.vue (3)
9-25: 매물 종류 버튼 그리드의 반응형 개선 LGTM
grid-cols-2 md:grid-cols-3로의 변경으로 소형 화면에서 레이아웃 붕괴를 방지합니다. 적절한 조정입니다.
31-41: 거래 유형 영역의 플렉스 방향 반응형 전환 LGTM모바일 세로 스택 → 데스크톱 가로 배치가 자연스럽습니다.
57-64: 주소 검색 버튼 shrink-0 적용 LGTM긴 주소 입력 시 버튼 축소를 방지하여 레이아웃 안정성이 향상됩니다.
src/components/homes/homecreate/StepProgressIndicator.vue (2)
2-2: 간격 조정 변경, 문제 없어 보입니다mobile에서 gap-4, md 이상에서 gap-10 적용으로 가독성이 좋아졌습니다.
14-14: 활성 단계 라벨 강조 처리 LGTM활성 단계에만 text-black, font-medium 적용은 의도에 부합해 보입니다.
src/components/homes/homelist/FilterSidebar.vue (2)
193-194: close 이벤트 공개 및 사용 LGTM모바일 오버레이와 연동을 고려한 close 이벤트 추가는 적절합니다.
333-396: range 커스텀 CSS의 브라우저 호환성 확인 권장
input[type='range']::before는 브라우저마다 동작이 상이할 수 있으며, Firefox의 경우 커스텀 트랙 구현이 제한적입니다. 주요 타겟 브라우저에서 진행/색상 표시가 정상 동작하는지 확인 부탁드립니다.테스트 체크리스트:
- iOS Safari, Android Chrome, Desktop Safari/Firefox/Chrome/Edge에서 진행 바와 thumb가 의도대로 표시되는지
- JEONSE/WOLSE 전환 시 max/step에 따른 진행률이 정상 갱신되는지
src/pages/homes/HomeListPage.vue (2)
82-103: 모바일 필터 오버레이 구조 LGTM오버레이에 자체 닫기 버튼을 두고, FilterSidebar의 filter-change 시 자동으로 닫히도록 연결한 흐름이 직관적입니다.
200-210: 월세/전세 파라미터 분기 처리 LGTM월세 시 보증금/월세 상한, 전세 시 보증금 상한만 전송하고 월세는 제거하는 분기 로직이 명확합니다.
src/components/homes/homeupdate/FacilityInfoForm.vue (1)
138-171: 모델 동기화/방출 로직 LGTM
- 체크박스 선택은 중복 방지(Set) 후 배열화
- 토글 옵션은 명시 키로 업데이트
- 방출 payload가 부모 v-model 계약에 맞게 최소화되어 일관성 있습니다
src/pages/homes/HomeCreatePage.vue (3)
6-6: 액션바 상단 여백(mt-8) 조정 LGTM단계 컴포넌트와의 수직 리듬이 안정적으로 보입니다.
138-138: 설명(description) 필드 payload 포함 LGTMcreate API에 설명 전달 필요 요건을 만족합니다. 백엔드 스키마와 필드명이 일치하는지만 확인해 주세요.
145-152: createListing 응답 형식 확인 필요
const homeId = response로 ID를 직접 반환한다고 가정하고 있습니다. API가{ homeId }또는{ data: { homeId } }형태로 주는 경우 라우팅이 실패합니다. 실제 응답 스키마를 확인해 안전하게 구조 분해하세요.예시:
- const homeId = response + const homeId = typeof response === 'string' || typeof response === 'number' + ? response + : (response?.homeId ?? response?.data?.homeId)src/components/homes/homecreate/Step3DetailInfo.vue (4)
2-2: 모바일 친화적인 패딩/그리드 업데이트 좋습니다p-4/md:p-6, 단일→반응형 컬럼 전환으로 가독성이 좋아졌습니다.
41-55: 추가된 ‘면적’ 필드 백엔드/스키마 호환성 확인 요청기존 전용/공급면적 외에 ‘면적’(form.area)이 신규로 노출됩니다. 저장/검증 경로에서 해당 필드가 유효한지, 혹은 계산/표시용 보조 필드인지 확인이 필요합니다.
간단 점검 가이드:
- 상위 페이지(v-model:form)에서 area가 어디에 사용/전송되는지 확인
- API 전송(FormData/JSON)에 area 포함 여부 및 서버 스키마 필드 존재 여부 확인
106-139: 지하층(음수 층수) 입력 요구사항 확인 필요현재 층/총 층수에 min="0"이 적용되어 지하층 표기가 불가능합니다. 지하 1층 등 입력이 필요하다면 음수 허용 또는 별도 표기(예: B1) 정책이 필요합니다.
요구사항에 따라 min 속성 제거 또는 허용 범위 조정(+ 플레이스홀더/설명 보완)을 제안합니다.
155-173: 방향 옵션 UI 구성 깔끔합니다선택 상태 스타일링과 버튼 그리드 구성이 일관되고 명확합니다.
src/components/homes/homeupdate/PriceInfoForm.vue (1)
167-174: 관리비 포함 항목 토글 로직 적절합니다프록시 안전성(toRaw) 확보, 중복 추가 방지, 제거 동작 모두 적절합니다. 현재 요구사항 범위에서는 간단하고 명확한 접근입니다.
Also applies to: 175-189, 191-193
src/components/homes/homeupdate/BasicInfoForm.vue (3)
44-65: 주소 검색 흐름 명확하고 단순합니다읽기 전용 주소 입력 + 모달 오픈 패턴이 명확합니다. 선택 시 addr1만 갱신하고 닫는 흐름도 직관적입니다. 상세주소 입력 추가도 👍
Also applies to: 67-76, 78-81
108-113: SearchAddress의 이벤트 payload 형태 확인 요청onAddressSelect가 문자열을 받는 전제(
selectedAddress)로 구현되어 있습니다. SearchAddress가 객체를 반환하는 경우(예: { address, zonecode }), 원하는 필드를 매핑하도록 보완이 필요합니다.검증 포인트:
- SearchAddress.vue의 @select payload 구조 확인
- 필요 시
updateForm('addr1', selectedAddress.address)등으로 수정
10-24: 매물 종류 버튼 토글 UX 적절합니다선택/비선택 상태 스타일이 깔끔하고 접근성도 괜찮습니다. 간단한 구조 선호(지식 기반 학습 참고)에 부합합니다.
src/components/homes/homeupdate/RoomInfoForm.vue (2)
168-175: NaN 방지 처리 훌륭합니다숫자 필드 입력 공백 시 0으로 보정하는 로직으로 상위 폼 오염을 방지하고 있습니다. Step3DetailInfo.vue에도 동일 패턴을 적용하면 일관성이 높아집니다.
155-163: OptionChecklist 재사용 좋습니다상세 옵션을 별도 컴포넌트로 위임해 폼 로직을 단순화하는 방향이 좋습니다.
src/pages/homes/HomeUpdatePage.vue (4)
156-166: 배열 필드 FormData 인코딩 방식 확인반복 키로
facilityItemIds/deleteImageIds를 추가하고 있는데, 백엔드가facilityItemIds[]와 같은 bracket 표기를 요구하는 경우도 많습니다. 서버 DTO/바인더가 어떤 방식을 파싱하는지 확인 바랍니다.서버가 bracket 표기를 요구한다면 아래처럼 변경하세요:
-;(listing.value.facilityItemIds || []).forEach((id) => { - formData.append('facilityItemIds', id) -}) +;(listing.value.facilityItemIds || []).forEach((id) => { + formData.append('facilityItemIds[]', id) +}) ... -;(deletedImageIds.value || []).forEach((id) => { - formData.append('deleteImageIds', id) -}) +;(deletedImageIds.value || []).forEach((id) => { + formData.append('deleteImageIds[]', id) +})
145-153: 불리언/숫자 직렬화 기대치 확인FormData는 내부적으로 문자열화되지만, 백엔드에서
isPet,isParking등 불리언 필드에 대해"true"/"false"또는1/0등 특정 포맷을 요구할 수 있습니다. 현재도 작동할 가능성이 높지만, 서버 기대치(특히 스프링/파서 설정)를 한번 확인해 주세요.
16-41: 컴포넌트 간 v-model:form 일원화, 이미지 업로더 분리 구조 좋습니다폼 상태를 단일
listing객체로 일원화하고 섹션 컴포넌트들이 이를 참조하도록 한 구조가 유지보수성/일관성에 유리합니다. 이미지 신규/기존 처리 분리도 깔끔합니다.
99-101: fetchListingById 반환값 확인 완료
fetchListingById가response.data.data를 반환하도록 구현되어 있어, Axios 원본 응답이 아닌 실제 매물 객체를 받아옵니다.
따라서listing.value = response로 할당해도homeId를 포함한 필드가 정상적으로 설정되며 추가 수정은 필요 없습니다.src/components/homes/homedetails/RoomDetails.vue (2)
103-103: 반려동물 아이콘 렌더링 로직 간소화 가능
getIcon('반려동물')은 이미PetA컴포넌트를 반환하므로 직접 사용할 수 있습니다.
135-140: 상세 설명 섹션 추가 확인매물 상세 설명을 표시하는 새로운 섹션이 적절히 추가되었습니다. 조건부 렌더링도 올바르게 적용되었습니다.
| @@ -1,6 +1,6 @@ | |||
| <script src="../../../apis/listing.js"></script> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스크립트 태그 위치가 잘못되었습니다
Vue 단일 파일 컴포넌트에서는 <script src> 태그를 사용할 수 없습니다. 이 코드는 실행되지 않으며 파일을 삭제해야 합니다.
-<script src="../../../apis/listing.js"></script>📝 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.
| <script src="../../../apis/listing.js"></script> |
🤖 Prompt for AI Agents
In src/components/homes/homedetails/MapLocation.vue around line 1: the
standalone <script src="../../../apis/listing.js"></script> tag is invalid
inside a Vue SFC and will not execute; remove this tag and either delete the
MapLocation.vue file if it's unused, or move the dependency into the component's
script section using a proper ES module import (e.g., import listing from
'../../../apis/listing.js') and reference it from the component code.
| const updateMapWithAddress = (address) => { | ||
| if (!address) { | ||
| console.warn('유효한 주소 정보가 없어 지도를 표시할 수 없습니다.') | ||
| return | ||
| } | ||
|
|
||
| const geocoder = new kakao.maps.services.Geocoder() | ||
| geocoder.addressSearch(props.address, (result, status) => { | ||
| geocoder.addressSearch(address, (result, status) => { | ||
| if (status === kakao.maps.services.Status.OK) { | ||
| coordinate.lat = parseFloat(result[0].y) | ||
| coordinate.lng = parseFloat(result[0].x) | ||
| // 지도가 이미 로드되었으면 중심 위치 변경 | ||
| if (map.value) { | ||
| map.value.setCenter(new kakao.maps.LatLng(coordinate.lat, coordinate.lng)) | ||
| } | ||
| } else { | ||
| console.warn('주소를 찾을 수 없습니다.') | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
주소 유효성 검증 로직 개선 필요
updateMapWithAddress 함수가 주소 값을 검증하지만, 빈 문자열이나 공백만 있는 경우를 처리하지 못합니다. 또한 geocoder 서비스 생성을 함수 호출 시마다 반복하는 것은 비효율적입니다.
+const geocoder = new kakao.maps.services.Geocoder()
+
const updateMapWithAddress = (address) => {
- if (!address) {
+ if (!address || !address.trim()) {
console.warn('유효한 주소 정보가 없어 지도를 표시할 수 없습니다.')
return
}
- const geocoder = new kakao.maps.services.Geocoder()
- geocoder.addressSearch(address, (result, status) => {
+ geocoder.addressSearch(address.trim(), (result, status) => {
if (status === kakao.maps.services.Status.OK) {
coordinate.lat = parseFloat(result[0].y)
coordinate.lng = parseFloat(result[0].x)
// 지도가 이미 로드되었으면 중심 위치 변경
if (map.value) {
map.value.setCenter(new kakao.maps.LatLng(coordinate.lat, coordinate.lng))
}
} else {
console.warn('주소를 찾을 수 없습니다.')
}
})
}📝 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.
| const updateMapWithAddress = (address) => { | |
| if (!address) { | |
| console.warn('유효한 주소 정보가 없어 지도를 표시할 수 없습니다.') | |
| return | |
| } | |
| const geocoder = new kakao.maps.services.Geocoder() | |
| geocoder.addressSearch(props.address, (result, status) => { | |
| geocoder.addressSearch(address, (result, status) => { | |
| if (status === kakao.maps.services.Status.OK) { | |
| coordinate.lat = parseFloat(result[0].y) | |
| coordinate.lng = parseFloat(result[0].x) | |
| // 지도가 이미 로드되었으면 중심 위치 변경 | |
| if (map.value) { | |
| map.value.setCenter(new kakao.maps.LatLng(coordinate.lat, coordinate.lng)) | |
| } | |
| } else { | |
| console.warn('주소를 찾을 수 없습니다.') | |
| } | |
| }) | |
| } | |
| const geocoder = new kakao.maps.services.Geocoder() | |
| const updateMapWithAddress = (address) => { | |
| if (!address || !address.trim()) { | |
| console.warn('유효한 주소 정보가 없어 지도를 표시할 수 없습니다.') | |
| return | |
| } | |
| geocoder.addressSearch(address.trim(), (result, status) => { | |
| if (status === kakao.maps.services.Status.OK) { | |
| coordinate.lat = parseFloat(result[0].y) | |
| coordinate.lng = parseFloat(result[0].x) | |
| // 지도가 이미 로드되었으면 중심 위치 변경 | |
| if (map.value) { | |
| map.value.setCenter(new kakao.maps.LatLng(coordinate.lat, coordinate.lng)) | |
| } | |
| } else { | |
| console.warn('주소를 찾을 수 없습니다.') | |
| } | |
| }) | |
| } |
🤖 Prompt for AI Agents
In src/components/homes/homedetails/MapLocation.vue around lines 30 to 49,
updateMapWithAddress currently fails to treat empty strings or whitespace-only
addresses as invalid and recreates the kakao geocoder on every call; trim the
incoming address and check for a non-empty value (e.g., if (!address ||
!address.trim()) return with the same warning), and move creation of new
kakao.maps.services.Geocoder() out of the function to a persistent variable
(module-level or component-level ref) so the geocoder is instantiated once and
reused for subsequent address lookups.
| <div class="flex gap-6"> | ||
| <BaseRadio | ||
| v-for="option in leaseTypeOptions" | ||
| :key="option.value" | ||
| :value="option.value" | ||
| :label="option.label" | ||
| :modelValue="form.leaseType" | ||
| name="leaseType" | ||
| @update:modelValue="updateForm('leaseType', $event)" | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
런타임 오류: 템플릿에서 form이 정의되지 않았습니다 (props.form 사용 필요)
script setup에서 toRefs로 form을 추출하지 않았는데, 템플릿에서 :modelValue="form.leaseType"를 참조하고 있습니다. 현재 상태에서는 form가 정의되지 않아 오류가 발생합니다. props.form으로 통일하거나 toRefs를 추가하세요.
간단 수정(diff):
- :modelValue="form.leaseType"
+ :modelValue="props.form.leaseType"대안:
- script에
const { form } = toRefs(props)추가 후 템플릿 전역을 form 기준으로 일관화
📝 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.
| <div class="flex gap-6"> | |
| <BaseRadio | |
| v-for="option in leaseTypeOptions" | |
| :key="option.value" | |
| :value="option.value" | |
| :label="option.label" | |
| :modelValue="form.leaseType" | |
| name="leaseType" | |
| @update:modelValue="updateForm('leaseType', $event)" | |
| /> | |
| </div> | |
| <div class="flex gap-6"> | |
| <BaseRadio | |
| v-for="option in leaseTypeOptions" | |
| :key="option.value" | |
| :value="option.value" | |
| :label="option.label" | |
| :modelValue="props.form.leaseType" | |
| name="leaseType" | |
| @update:modelValue="updateForm('leaseType', $event)" | |
| /> | |
| </div> |
🤖 Prompt for AI Agents
In src/components/homes/homeupdate/BasicInfoForm.vue around lines 31 to 41, the
template references form (e.g. :modelValue="form.leaseType") but script-setup
never exposes a reactive form variable from props, causing a runtime error; fix
by extracting form from props using toRefs (e.g. const { form } = toRefs(props))
in the script setup so the template can use form consistently, or alternatively
update the template to use props.form everywhere—apply the toRefs extraction for
consistency with current template usage.
| (checked) => updateArray(localListing.livingFacilities, item, checked) | ||
| " | ||
| <form @submit.prevent="onSubmit" class="max-w-4xl mx-auto p-6 space-y-6 rounded-md"> | ||
| <h2 class="text-lg font-semibold text-gray mb-2">시설 정보</h2> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tailwind 클래스 오타: text-gray는 유효하지 않습니다
Tailwind에서는 text-gray-700/text-gray-800 등 단계가 필요합니다. 스타일이 적용되지 않을 수 있으니 수정하세요.
- <h2 class="text-lg font-semibold text-gray mb-2">시설 정보</h2>
+ <h2 class="text-lg font-semibold text-gray-800 mb-2">시설 정보</h2>📝 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.
| <h2 class="text-lg font-semibold text-gray mb-2">시설 정보</h2> | |
| <h2 class="text-lg font-semibold text-gray-800 mb-2">시설 정보</h2> |
🤖 Prompt for AI Agents
In src/components/homes/homeupdate/FacilityInfoForm.vue around line 3, the
Tailwind class "text-gray" is invalid because Tailwind requires a shade (e.g.,
text-gray-700); replace "text-gray" with an appropriate shade like
"text-gray-700" (or whichever shade fits the design system) so the style applies
correctly, and update any other occurrences in this file to use the valid shaded
class.
| const newFilesWithPreview = files.map((file) => ({ | ||
| file, | ||
| preview: URL.createObjectURL(file), | ||
| })) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Object URL 메모리 누수 가능성 – 제거 시 revoke 필요
URL.createObjectURL로 만든 preview는 사용 종료 시 revoke해야 합니다. 현재 제거 시 revoke가 없어 메모리 누수가 발생할 수 있습니다.
적용 제안(diff):
const removeNewImage = (index) => {
- const updatedNewImages = [...props.newImages]
- updatedNewImages.splice(index, 1)
+ const updatedNewImages = [...props.newImages]
+ const [removed] = updatedNewImages.splice(index, 1)
+ try {
+ if (removed?.preview) URL.revokeObjectURL(removed.preview)
+ } catch (e) {
+ console.warn('Failed to revoke object URL:', e)
+ }
emit('update:newImages', updatedNewImages)
}또한 컴포넌트 언마운트 시 남아있는 preview들도 일괄 revoke하는 것을 권장합니다(아래 참고 코드).
추가 코드(스크립트 하단에):
import { onBeforeUnmount } from 'vue'
onBeforeUnmount(() => {
try {
props.newImages.forEach((n) => n?.preview && URL.revokeObjectURL(n.preview))
} catch {}
})Also applies to: 108-112
🤖 Prompt for AI Agents
In src/components/homes/homeupdate/ImageUploader.vue around lines 84-87 and
108-112, the previews created with URL.createObjectURL are never revoked causing
potential memory leaks; update the code so that whenever a preview is removed
(e.g., in the file removal handler) you call URL.revokeObjectURL(preview) before
dropping the object, and add an onBeforeUnmount hook that iterates remaining
props.newImages (or the component's local previews) and calls
URL.revokeObjectURL for each non-empty preview inside a try/catch to avoid
throwing during teardown.
| const goToChat = async () => { | ||
| if (!id) { | ||
| console.log('채팅방을 만들 수 없습니다 - 매물 ID 없음') | ||
| alert('매물 정보를 찾을 수 없습니다. 페이지를 새로고침 해주세요.') | ||
| return | ||
| } | ||
|
|
||
| isCreatingChat.value = true | ||
| try { | ||
| const response = await createChatRoom(id) | ||
| if (response && response.data) { | ||
| router.push(`/chat?roomId=${response.data}`) | ||
| } else { | ||
| console.error('채팅방 생성 실패: 응답에 chatRoomId가 없습니다', response) | ||
| alert('채팅방 생성에 실패했습니다. 다시 시도해주세요.') | ||
| } | ||
| } catch (error) { | ||
| console.error('채팅방 생성 오류 상세:', error.response || error) | ||
| alert('채팅방 생성 중 오류가 발생했습니다.') | ||
| } finally { | ||
| isCreatingChat.value = false | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
채팅방 생성 로딩 상태 UI 표시 필요
isCreatingChat 상태를 추적하고 있지만 UI에 로딩 상태를 표시하지 않습니다. 사용자 경험 개선을 위해 버튼에 로딩 표시가 필요합니다.
- <BaseButton class="w-full" variant="primary" size="lg" @click="goToChat">
- 연락하기
+ <BaseButton
+ class="w-full"
+ variant="primary"
+ size="lg"
+ @click="goToChat"
+ :disabled="isCreatingChat"
+ >
+ {{ isCreatingChat ? '채팅방 생성 중...' : '연락하기' }}
</BaseButton>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/pages/homes/HomeDetailsPage.vue around lines 69 to 91, the goToChat
function updates isCreatingChat but the template doesn't reflect that state;
update the chat button in the component template to show a loading state and be
disabled while isCreatingChat.value is true (e.g., bind
:disabled="isCreatingChat" and render a spinner or change button text to "로딩…"
when true), ensure the button click still calls goToChat, and keep the existing
finally block that sets isCreatingChat.value = false so the UI returns to normal
after completion or error.
| if (response && response.data) { | ||
| router.push(`/chat?roomId=${response.data}`) | ||
| } else { | ||
| console.error('채팅방 생성 실패: 응답에 chatRoomId가 없습니다', response) | ||
| alert('채팅방 생성에 실패했습니다. 다시 시도해주세요.') | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
채팅방 생성 응답 구조 확인 필요
response.data를 roomId로 직접 사용하고 있는데, API 응답 구조가 정확한지 확인이 필요합니다.
🏁 Script executed:
#!/bin/bash
# createChatRoom API의 응답 구조 확인
ast-grep --pattern 'export $_ function createChatRoom($_) {
$$$
}'Length of output: 70
🏁 Script executed:
#!/bin/bash
# src 디렉터리 내 createChatRoom 함수/변수 정의 및 사용 위치 확인
rg -nP "createChatRoom" -C3 srcLength of output: 3548
🏁 Script executed:
#!/bin/bash
echo "===== src/apis/chatApi.js (first 200 lines) ====="
sed -n '1,200p' src/apis/chatApi.js
echo -e "\n===== apiRequest usage in src/apis ====="
rg -n "apiRequest" -C3 src/apis
echo -e "\n===== apiRequest definition in src ====="
rg -nP "function apiRequest" -C5 srcLength of output: 20775
API 응답 구조 및 성공 여부 확인 후 처리 로직 보강 필요
createChatRoom은 apiRequest를 통해 서버에서 반환된 JSON 객체(예: { success: boolean, data: string })를 리턴합니다. 현재는 response.data만 보고 roomId로 바로 사용하고 있는데, 응답 wrapper 구조와 성공 여부를 명확히 검증해야 합니다.
- File: src/pages/homes/HomeDetailsPage.vue (lines 79–84)
response.success체크를 추가하여 API 호출 성공 시에만response.data를 roomId로 사용하도록 변경- 변수명(
response→result등) 변경을 고민하여 혼동을 줄일 수 있습니다
제안하는 수정 예시:
- if (response && response.data) {
+ if (response?.success && response.data) {
router.push(`/chat?roomId=${response.data}`)
} else {
console.error('채팅방 생성 실패:', response)
alert('채팅방 생성에 실패했습니다. 다시 시도해주세요.')
}🤖 Prompt for AI Agents
In src/pages/homes/HomeDetailsPage.vue around lines 79–84, the code currently
assumes response.data is the roomId; update it to verify the API wrapper by
checking response.success is true and that response.data is a valid non-empty
string before calling router.push; rename the variable (e.g., response → result)
to reduce confusion, and in the failure branch log the full result and show the
alert only when success is false or data is missing so only valid roomIds
trigger navigation.
| const handleImageDelete = (imageId) => { | ||
| const numericId = parseInt(imageId, 10) | ||
| if (!isNaN(numericId) && !deletedImageIds.value.includes(numericId)) { | ||
| deletedImageIds.value.push(numericId) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
이미지 삭제 ID 파싱을 더 엄격히 처리하세요
parseInt("123abc", 10) -> 123와 같이 오탐이 가능합니다. 정수 문자열만 허용하도록 체크를 강화하세요.
-const handleImageDelete = (imageId) => {
- const numericId = parseInt(imageId, 10)
- if (!isNaN(numericId) && !deletedImageIds.value.includes(numericId)) {
- deletedImageIds.value.push(numericId)
- }
-}
+const handleImageDelete = (imageId) => {
+ const raw = String(imageId)
+ if (/^\d+$/.test(raw)) {
+ const numericId = Number(raw)
+ if (!deletedImageIds.value.includes(numericId)) {
+ deletedImageIds.value.push(numericId)
+ }
+ }
+}📝 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.
| const handleImageDelete = (imageId) => { | |
| const numericId = parseInt(imageId, 10) | |
| if (!isNaN(numericId) && !deletedImageIds.value.includes(numericId)) { | |
| deletedImageIds.value.push(numericId) | |
| } | |
| } | |
| const handleImageDelete = (imageId) => { | |
| const raw = String(imageId) | |
| if (/^\d+$/.test(raw)) { | |
| const numericId = Number(raw) | |
| if (!deletedImageIds.value.includes(numericId)) { | |
| deletedImageIds.value.push(numericId) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/pages/homes/HomeUpdatePage.vue around lines 110 to 115, the current
parseInt usage allows inputs like "123abc" to be parsed as 123; tighten
validation by first verifying imageId is a pure integer string or a number (e.g.
check typeof imageId === 'number' and Number.isInteger(imageId) OR if typeof
imageId === 'string' ensure /^\d+$/.test(imageId)), only then convert to a
Number (or parseInt) and push to deletedImageIds if not already present; reject
or ignore invalid values.
| const fieldsToAppend = { | ||
| addr1: listing.value.addr1, | ||
| addr2: listing.value.addr2, | ||
| residenceType: listing.value.residenceType, | ||
| leaseType: listing.value.leaseType, | ||
| depositPrice: listing.value.depositPrice ?? 0, | ||
| monthlyRent: listing.value.monthlyRent ?? 0, | ||
| maintenanceFee: listing.value.maintenanceFee ?? 0, | ||
| roomCnt: listing.value.roomCnt ?? 0, | ||
| supplyArea: listing.value.supplyArea ?? 0.0, | ||
| exclusiveArea: listing.value.exclusiveArea ?? 0.0, | ||
| buildDate: listing.value.buildDate, | ||
| homeFloor: listing.value.homeFloor ?? 0, | ||
| buildingTotalFloors: listing.value.buildingTotalFloors ?? 0, | ||
| homeDirection: listing.value.homeDirection, | ||
| bathroomCnt: listing.value.bathroomCnt ?? 0, | ||
| isPet: listing.value.isPet, | ||
| isParking: listing.value.isParking, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상세 설명(description) 필드가 FormData에 포함되지 않아 저장 누락됩니다
UI에선 v-model로 listing.description을 편집하지만, FormData 구성에서 해당 필드를 누락해 서버로 전송되지 않습니다. 빈 문자열로 초기화하여 “설명 삭제”도 반영되도록 하는 편이 안전합니다.
다음 패치를 적용해 주세요:
const fieldsToAppend = {
addr1: listing.value.addr1,
addr2: listing.value.addr2,
+ description: listing.value.description ?? '',
residenceType: listing.value.residenceType,
leaseType: listing.value.leaseType,
depositPrice: listing.value.depositPrice ?? 0,
monthlyRent: listing.value.monthlyRent ?? 0,
maintenanceFee: listing.value.maintenanceFee ?? 0,
roomCnt: listing.value.roomCnt ?? 0,
supplyArea: listing.value.supplyArea ?? 0.0,
exclusiveArea: listing.value.exclusiveArea ?? 0.0,
buildDate: listing.value.buildDate,
homeFloor: listing.value.homeFloor ?? 0,
buildingTotalFloors: listing.value.buildingTotalFloors ?? 0,
homeDirection: listing.value.homeDirection,
bathroomCnt: listing.value.bathroomCnt ?? 0,
isPet: listing.value.isPet,
isParking: listing.value.isParking,
}📝 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.
| const fieldsToAppend = { | |
| addr1: listing.value.addr1, | |
| addr2: listing.value.addr2, | |
| residenceType: listing.value.residenceType, | |
| leaseType: listing.value.leaseType, | |
| depositPrice: listing.value.depositPrice ?? 0, | |
| monthlyRent: listing.value.monthlyRent ?? 0, | |
| maintenanceFee: listing.value.maintenanceFee ?? 0, | |
| roomCnt: listing.value.roomCnt ?? 0, | |
| supplyArea: listing.value.supplyArea ?? 0.0, | |
| exclusiveArea: listing.value.exclusiveArea ?? 0.0, | |
| buildDate: listing.value.buildDate, | |
| homeFloor: listing.value.homeFloor ?? 0, | |
| buildingTotalFloors: listing.value.buildingTotalFloors ?? 0, | |
| homeDirection: listing.value.homeDirection, | |
| bathroomCnt: listing.value.bathroomCnt ?? 0, | |
| isPet: listing.value.isPet, | |
| isParking: listing.value.isParking, | |
| } | |
| const fieldsToAppend = { | |
| addr1: listing.value.addr1, | |
| addr2: listing.value.addr2, | |
| description: listing.value.description ?? '', | |
| residenceType: listing.value.residenceType, | |
| leaseType: listing.value.leaseType, | |
| depositPrice: listing.value.depositPrice ?? 0, | |
| monthlyRent: listing.value.monthlyRent ?? 0, | |
| maintenanceFee: listing.value.maintenanceFee ?? 0, | |
| roomCnt: listing.value.roomCnt ?? 0, | |
| supplyArea: listing.value.supplyArea ?? 0.0, | |
| exclusiveArea: listing.value.exclusiveArea ?? 0.0, | |
| buildDate: listing.value.buildDate, | |
| homeFloor: listing.value.homeFloor ?? 0, | |
| buildingTotalFloors: listing.value.buildingTotalFloors ?? 0, | |
| homeDirection: listing.value.homeDirection, | |
| bathroomCnt: listing.value.bathroomCnt ?? 0, | |
| isPet: listing.value.isPet, | |
| isParking: listing.value.isParking, | |
| } |
🤖 Prompt for AI Agents
In src/pages/homes/HomeUpdatePage.vue around lines 125 to 143, the
listing.description field is missing from the fieldsToAppend object so edits
(including clearing the description) aren't sent to the server; add description:
listing.value.description ?? '' to fieldsToAppend so the description is always
included in the FormData (using an empty string as the safe default to allow
clearing the field).
| }) | ||
| } | ||
|
|
||
| await updateListing(listingId, formData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
updateListing가 multipart/form-data 전송을 보장하는지 확인
updateListing(listingId, formData)가 적절한 Content-Type 설정과 함께 FormData를 그대로 전달하는지 확인 필요합니다. Axios는 FormData 전달 시 자동으로 멀티파트 처리를 하지만, 수동 헤더 오버라이드가 방해하지 않는지도 점검해 주세요.
아래 스크립트로 구현을 확인할 수 있습니다. 기대결과: 두 번째 인자로 FormData를 그대로 넘기며, 별도의 Content-Type 수동 설정이 없다면 OK입니다.
🏁 Script executed:
#!/bin/bash
set -e
rg -n -C3 -g 'src/apis/listing.js' -S 'updateListing\s*\(' || true
sed -n '1,240p' src/apis/listing.js 2>/dev/null || trueLength of output: 3697
updateListing 함수의 Content-Type 헤더 오버라이드 제거 요청
axios가 FormData 전송 시 multipart boundary를 자동 생성하도록, 수동 헤더 설정을 삭제해주세요.
- 파일: src/apis/listing.js
- 위치: updateListing 함수 내부 (약 91–97줄)
export async function updateListing(id, formData) {
try {
- const response = await api.put(`${API_BASE_URL}/${id}`, formData, {
- headers: { 'Content-Type': 'multipart/form-data' },
- })
+ const response = await api.put(`${API_BASE_URL}/${id}`, formData)
return response.data.data
} catch (error) {
console.error('매물 수정 실패', error)
throw error
}
}📝 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.
| await updateListing(listingId, formData) | |
| export async function updateListing(id, formData) { | |
| try { | |
| const response = await api.put(`${API_BASE_URL}/${id}`, formData) | |
| return response.data.data | |
| } catch (error) { | |
| console.error('매물 수정 실패', error) | |
| throw error | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/apis/listing.js around lines 91–97, the updateListing function currently
overrides the Content-Type header when sending FormData; remove that manual
'Content-Type' header (do not set 'multipart/form-data') so axios can
automatically set the boundary for multipart requests—delete the header
assignment or stop passing headers that include Content-Type, leaving axios to
manage it while keeping other headers intact.
🚀 관련 이슈
🔑 주요 변경사항
addr2 추가
✔️ 체크 리스트
mainbranch에 실수로 PR 생성 금지)📢 To Reviewers
📸 스크린샷 or 실행영상
Summary by CodeRabbit
신기능
UI 개선