- 발행일
한 숙소에 가격 필드가 여덟 개 — 최소~최대 표시를 맞추던 일
한 숙소에 가격 필드가 여덟 개
이 글은 2026년 9월에 당시 코드를 다시 보며 정리한 것입니다. 저장소는 퇴사 전에 스냅샷으로 보관한 것이라 커밋 이력이 없고, 경력기술서와 당시 쓴 이력서에 적은 담당 범위와 코드를 대조해 썼습니다. 날짜는 코드에 남은 시기 단서 기준입니다.
숙박 큐레이션 플랫폼의 프런트 팀은 넷, 백엔드 팀도 넷이었습니다. 2023년 12월에 쓴 이력서에는 제 담당 역할이 "가격, 홍보에 관련된 프런트엔드 개발"로 적혀 있어요. 가격 표시는 그중 앞쪽입니다.
숙소 목록 카드에는 가격이 한 줄 들어갑니다. "₩180,000 "처럼요. 할인이 걸린 숙소는 원가에 줄을 긋고 할인가를 앞에 세워야 했습니다. 경력기술서에는 이렇게 적었어요. "고객 화면에는 최소최대 가격을 명시하고 할인 옵션이 있으면 할인가와 기본가를 시각적으로 구분했다."
그 한 줄을 만들려고 타입 파일을 열었더니 가격 필드가 이렇게 있었습니다.
new_price_min: number;
new_price_max: number;
new_min_price: number;
new_max_price: number;
price_min: number;
price_max: number;
max_price: number;
min_price: number;
네 쌍, 여덟 개. price_min과 min_price가 다른 값이고, 거기에 new_가 붙은 쌍이 또 둘입니다. 화면 한 줄에 셋을 넣어야 하는데 어느 셋인지가 첫 문제였어요.
네 쌍이 어디서 오는가
이름만으로는 갈리지 않습니다. 이 글을 쓰며 서버 직렬화기를 열었고, 그때도 열어 봤을 텐데 기록은 없어요. Rails 쪽 코드는 백엔드 팀 것이라 여기서는 "서버가 이렇게 준다"까지만 적습니다.
| 쌍 | 출처 | 뜻 |
|---|---|---|
price_min / price_max | DB 컬럼 그대로 | 숙소에 등록된 기본가 범위. 원화 |
min_price / max_price | 직렬화기가 price_min·price_max를 접속 언어의 통화로 환산 | 같은 값의 환율 적용판 |
new_price_min / new_price_max | 2023년 8~9월에 추가된 컬럼 | 예약 가능 기간의 실제 판매가를 매일 계산해 심은 값 |
new_min_price / new_max_price | 위 둘의 환율 적용판 | 화면이 쓰는 쌍 |
그러니까 실제로는 두 가지 값(기본가, 계산된 판매가)이 각각 원화판과 환산판으로 갈려 넷이 된 겁니다. 마이그레이션 파일 날짜가 new_price_min은 8월 17일, new_price_max는 9월 1일이에요. 두 주 간격으로 따로 들어온 걸 보면 처음엔 최솟값만 심을 생각이었다가 최대가 필요해진 것으로 읽힙니다. 이유는 코드에 남아 있지 않습니다.
최대라는 이름에 최솟값이 들어간다
서버 서비스는 숙소의 방마다, 오늘부터 예약 가능 마지막 날까지 하루씩 가격을 계산합니다. 그리고 이렇게 마무리해요.
min_price = min_price_list.min if min_price_list != nil
max_price = max_price_list.min if max_price_list != nil
max_price_list에서도 .min을 뽑습니다. min_price_list에는 할인 적용가가, max_price_list에는 할인 전 예약가가 쌓이는데, 둘 다 기간 안의 최솟값을 취해요. 결과적으로 new_price_max는 "최대 가격"이 아니라 "할인 전 가격 중 가장 싼 날"입니다.
이걸 몰랐다면 화면에서 new_max_price를 "최대"로 읽고 "18만~25만"처럼 범위로 그렸을 겁니다. 실제로는 원가와 할인가 한 쌍이었어요. 이름을 믿지 않고 계산 코드를 읽어야 맞는 화면이 나오는 구조였습니다. 그때 읽고 골랐는지, 누가 알려 줬는지는 남아 있지 않아요.
화면은 어느 쌍을 믿는가
정리한 규칙은 컴포넌트 하나에 뒀습니다.
if (!minPrice || !maxPrice || minPrice >= originalPrice) {
return <>{displayPrice(locale, originalPrice)} ~</>;
}
return (
<>
<del className="strike">{displayPrice(locale, maxPrice)}</del>
{displayPrice(locale, minPrice)} ~
세 값을 받습니다. minPrice에 new_min_price(할인가), maxPrice에 new_max_price(할인 전가), originalPrice에 min_price(기본가)를 넣어요. 계산값이 없거나 0이면 기본가를 그대로 보여 주고, 할인가가 기본가보다 싸지 않으면 할인 표시를 하지 않습니다. 줄을 긋는 대상은 maxPrice이고 앞에 서는 건 minPrice예요.
이 컴포넌트를 부르는 곳이 셋입니다. 숙소 목록, 메인 슬라이더, 마이페이지 찜 목록. 세 곳이 전부 같은 세 필드를 같은 순서로 넘깁니다. 규칙을 한 곳에 두려고 했던 건 맞는데, 그 셋이 전부 같은 필드를 넘기는지는 컴포넌트가 강제하지 않아요. prop 이름이 minPrice·maxPrice·originalPrice라 호출하는 쪽이 new_min_price를 minPrice에 넣어야 한다는 걸 알아야 합니다.
통화 표시는 언어로 갈랐습니다.
export const displayPriceByLocale = (locale, price) => {
switch (locale) {
case "en":
return `US$ ${getPriceDisplayStr(price)}`;
case "ja":
return `¥${getPriceDisplayStr(price)}`;
default:
return `₩${getPriceDisplayStr(price)}`;
환산은 서버가 하고 기호만 프런트가 붙입니다. 그래서 화면은 늘 _price로 끝나는 환산판을 써야 하고, price_로 시작하는 원화판을 쓰면 일본어 페이지에 원화 숫자가 엔 기호로 나갑니다. 이 구분이 이름에 안 실려 있어서 필드를 고를 때마다 직렬화기를 다시 봐야 했어요.
필터 슬라이더의 최소·최대는 다른 문제였다
같은 "최소·최대"라도 검색 필터의 가격 슬라이더는 다른 일이었습니다. 0부터 100까지 만원 단위 슬라이더와 직접 입력 칸 둘을 한 상태로 합쳐야 했어요.
const handleFocusOut = () => {
if (inputMax < inputMin) {
setRangePrice({ min: inputMax - 1, max: inputMax });
setInputMin(inputMax - 1);
}
입력 칸에서 최소가 최대보다 크면 최소를 최대 바로 아래로 끌어내립니다. 쿼리는 min * 10000-max * 10000으로 만들고, 0~100 그대로면 빈 문자열이라 "조건 없음"이 됩니다. 이 컴포넌트가 findstay와 picks 두 폴더에 거의 같은 내용으로 두 벌 있어요. 두 벌은 슬라이더·입력 칸·쿼리 조립 로직이 같은데, 파일 길이가 171줄과 258줄로 다르고 diff -w로 219줄이 갈립니다. 공통 로직을 뽑지 않고 한쪽에 살을 붙인 모양이에요.
검증
- 날짜 근거: Rails 마이그레이션
add_new_price_min_to_place(2023-08-17)와add_new_price_max_to_place(2023-09-01). 글의 날짜는 뒤엣것입니다. - 필드 여덟 개는
types/models/places.ts에서, 환산 규칙은place_new_serializer.rb의new_min_price·new_max_price메서드에서 확인했습니다. max_price_list.min은new_price_service.rb에서 확인했습니다. 이 서비스는 백엔드 팀 코드이고, 제가 쓴 부분은 그 값을 받는 프런트입니다.- 호출처 셋(
findstay-list.tsx·main-swiper.tsx·mypage/likestay.tsx)이 같은 세 필드를 넘기는 것을 grep으로 셌습니다. 스냅샷 시점 기준이에요. - 문서 두 개와 대조한 결과입니다.
| 항목 | 경력기술서(2024-03) | 이력서(2023-12) |
|---|---|---|
| 담당 범위 | §5 "고객 화면에 최소~최대 가격 명시, 할인가·기본가 구분" — 일치 | "가격, 홍보에 관련된 프런트엔드 개발" — 일치 |
| A/B로 표시 방식 비교 | §5에 있음 — 이 저장소에서 근거 못 찾음 | 언급 없음 |
| 팀 규모 | 적지 않음 | 프런트 4명·백엔드 4명 — 가격 컴포넌트를 혼자 짰는지는 확인 불가 |
남은 것 · 한계
- 프런트가 넷이었습니다. 가격 표시가 제 담당 역할이었다는 건 이력서에 있지만, 이 컴포넌트와 호출처 셋을 전부 제가 썼는지는 이력이 없어 확인할 수 없습니다.
- 커밋 이력이 없어 순서를 모릅니다. 컬럼이 두 주 간격으로 들어온 이유, 화면이 언제
new_쌍으로 갈아탔는지는 마이그레이션 날짜로만 추정합니다. new_price_max라는 이름은 그대로 남았습니다. 값은 "할인 전 최저가"인데 이름은 "최대"예요. 프런트에서 prop 이름을originalPrice·maxPrice로 바꿔 받는 것으로 넘어갔고, 서버 컬럼 이름을 고치자고 제안한 기록은 없습니다.- 필드 선택을 강제하는 장치가 없습니다. 호출처 셋이 같은 필드를 넘기는 건 관례일 뿐이라, 넷째 호출처가
price_min을 넘겨도 타입은 통과합니다. - 필터 컴포넌트 두 벌은 그대로 두 벌입니다.
- 3년 뒤 지금 회사에서 같은 표준데이터를 두 화면이 198건과 134건으로 세고 있었다2026-08-06같은 표준데이터를 두 화면이 198건과 134건으로 세고 있었다 — 집계 모집단 일원화홈 "데이터 현황" 타일은 198건, 그 타일을 눌러 도착하는 목록 화면은 134건이었습니다. 닷새 전에 "타일이 목록으로 링크하니까 목록에 맞춘다"고 결정하고 주석까지 박아뒀는데도 회귀가 샜습니다. 원인은 결정이 아니라 그 결정을 지킨다던 테스트였어요. 기대값을 상수로 박아둔 정합 테스트는 목록 뷰가 조건을 하나 더 붙여도 계속 초록이었습니다.를 겪으면서 "같은 값을 두 이름으로 부르면 화면마다 갈린다"를 다시 만났습니다.
관련 글: 숙박 큐레이션 플랫폼 프런트엔드 2022–20242024-03-01숙박 큐레이션 플랫폼 프론트엔드 2022 ~ 2024 경력 기술서숙박 큐레이션 플랫폼에서 2년간 프런트엔드로 일하며 맡은 일을 정리했습니다. 일본어 다국어 적용, 프로모션·프리오더, 가격 표시, 앱 전환과 이벤트 태깅이 핵심이고, 항목마다 근거가 되는 글을 링크해 두었습니다. · 같은 표준데이터를 두 화면이 198건과 134건으로 세고 있었다2026-08-06같은 표준데이터를 두 화면이 198건과 134건으로 세고 있었다 — 집계 모집단 일원화홈 "데이터 현황" 타일은 198건, 그 타일을 눌러 도착하는 목록 화면은 134건이었습니다. 닷새 전에 "타일이 목록으로 링크하니까 목록에 맞춘다"고 결정하고 주석까지 박아뒀는데도 회귀가 샜습니다. 원인은 결정이 아니라 그 결정을 지킨다던 테스트였어요. 기대값을 상수로 박아둔 정합 테스트는 목록 뷰가 조건을 하나 더 붙여도 계속 초록이었습니다. · Figma 44프레임을 코드와 대조하기2026-05-10Figma 44프레임을 코드와 대조하기 — "다르다"와 "틀렸다"를 나누는 일디자인과 화면이 조금씩 다른데 뭐가 틀린 건지 모르겠다는 상태를 정리하려고, Figma 44프레임을 코드와 전수 대조해 리포트를 만들었습니다. 핵심은 차이를 찾는 게 아니라 "의도적 조정"과 "결함"을 가르는 규칙이었어요. 그리고 2.5개월 뒤에 확인해보니 HIGH 4건 중 2건이 아직 미반영입니다. · 메인 화면의 판독 박스는 서버 설정 셋이 그린다2022-04-01메인 화면의 판독 박스는 서버 설정 셋이 그린다 — 색·임계값·켜짐을 화면 열두 곳이 각자 물었다공항 보안 검색 판독 화면에서 AI가 찾은 물체 상자는 클래스마다 색·임계값·표시 여부 세 값으로 그려집니다. 당시 지원서를 보면 임계값을 직접 입력하는 것과 레이블 위치·상자 표시 옵션은 납품처 한 곳의 요구로 들어온 것이었어요. 그 세 값은 서버 설정 한 벌에서 오는데, 화면 쪽 열두 파일이 각자 같은 주소를 부르고 같은 조회 함수를 복사해 갖고 있었습니다. 보일지와 울릴지를 정하는 경계 비교가 파일마다 달랐던 것까지, 남은 코드로만 되짚었습니다. · 프로모션마다 지원 언어를 두고, 안 되는 언어면 뒤로 보냈다2023-02-24프로모션마다 지원 언어를 두고, 안 되는 언어면 뒤로 보냈다 — 404 대신 모달과 back프로모션은 한국어에만 있는 것도, 세 언어에 다 있는 것도 있었습니다. 프로모션마다 지원 언어 목록을 두고, 지금 언어가 그 안에 없으면 404나 빈 화면이 아니라 "이 언어는 지원하지 않는다"는 모달을 띄우고 닫으면 뒤로 보냈어요. 그 판단과, 같은 화면의 딜 목록이 SWR 무한 스크롤을 어떻게 이어받았는지를 코드로 다시 읽었습니다.