[Developer] #532 지역 표시 계층화 formatRegion (한국|서울 → 한국 › 서울)
- lib/region.ts formatRegion() 신설: 파이프 분리 + 빈토큰/null/나라 더미 제거 + ' › ' 조인 - RestaurantDetail/RestaurantList 지역 표기에 적용 - buildSearchQuery(검색용 공백조인) 유지 - 설계서 docs/design/532-region-display/README.md Refs #532 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-06-28
|
||||||
|
|
||||||
|
### 🐛 지역 표시 계층화 — "한국|서울" → "한국 › 서울" (#532)
|
||||||
|
- 문제: 식당 상세/리스트가 region 원본(`한국|서울|강남구`, 파이프 노출)을 그대로 표시
|
||||||
|
- 해결: 공용 유틸 `lib/region.ts` `formatRegion()` 신설 — 빈토큰/`null`/더미(`나라`) 제거 후 ` › ` 계층 조인
|
||||||
|
- 적용: `RestaurantDetail.tsx`(지역 표기), `RestaurantList.tsx`(카드 지역)
|
||||||
|
- 검색 쿼리용 `buildSearchQuery`(공백 조인)는 유지 — 외부 지도 검색 호환
|
||||||
|
- 설계서: `docs/design/532-region-display/README.md`
|
||||||
|
|
||||||
## 2026-06-17
|
## 2026-06-17
|
||||||
|
|
||||||
### 🎯 클러스터 expansion 후 마커 클릭 시 재묶임 방지 (v0.1.66)
|
### 🎯 클러스터 expansion 후 마커 클릭 시 재묶임 방지 (v0.1.66)
|
||||||
|
|||||||
43
docs/design/532-region-display/README.md
Normal file
43
docs/design/532-region-display/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# #532 — 지역 표시 계층화 (formatRegion)
|
||||||
|
|
||||||
|
## 배경 / 문제
|
||||||
|
지역(`region`)은 `나라|시도|구군` 파이프 구분 문자열(예: `한국|서울|강남구`).
|
||||||
|
사용자 화면에 **원본이 그대로 노출**되어 `한국|서울|강남구`(파이프) 또는 평문 `한국 서울`로 보임.
|
||||||
|
기대: **`한국 › 서울 › 구군`** 계층(breadcrumb) 표시.
|
||||||
|
|
||||||
|
## 범위
|
||||||
|
- 식당 상세 시트의 "지역" 표기 (`RestaurantDetail.tsx`)
|
||||||
|
- 식당 리스트 카드의 지역 표기 (`RestaurantList.tsx`)
|
||||||
|
- 공용 유틸 `formatRegion()` 신설 (`lib/region.ts`)
|
||||||
|
|
||||||
|
비범위: 지역 *필터* 동작/정렬(정상), 외부 지도 검색 쿼리(`buildSearchQuery` — 공백 조인 유지), 데이터 정합성(별도 이슈).
|
||||||
|
|
||||||
|
## 함수 설계
|
||||||
|
|
||||||
|
### `formatRegion(region: string | null | undefined): string`
|
||||||
|
- **입력**: 파이프 구분 지역 문자열 (예: `"한국|서울|강남구"`, `"일본|null"`, `"한국||관악구"`, `""`)
|
||||||
|
- **처리**:
|
||||||
|
1. falsy → `""` 반환
|
||||||
|
2. `split("|")` → 각 토큰 `trim()`
|
||||||
|
3. 빈 토큰, `"null"`(대소문자 무시), 더미 `"나라"` 제거
|
||||||
|
4. 남은 토큰을 `" › "`(U+203A)로 조인
|
||||||
|
- **출력 예**:
|
||||||
|
| 입력 | 출력 |
|
||||||
|
|---|---|
|
||||||
|
| `한국\|서울\|강남구` | `한국 › 서울 › 강남구` |
|
||||||
|
| `일본\|null` | `일본` |
|
||||||
|
| `한국\|\|관악구` | `한국 › 관악구` |
|
||||||
|
| `""` / `null` | `""` (호출부에서 미표시) |
|
||||||
|
- **순수 함수**(I/O 없음) → 단위 테스트 용이.
|
||||||
|
|
||||||
|
### 적용
|
||||||
|
- `RestaurantDetail.tsx:132`: `{restaurant.region}` → `{formatRegion(restaurant.region)}`
|
||||||
|
- `RestaurantList.tsx:54`: `{r.region}` → `{formatRegion(r.region)}`
|
||||||
|
|
||||||
|
## 엣지케이스
|
||||||
|
- 모든 토큰이 더미/빈 값(`"한국|"`, `"|"`) → 유효 토큰만 남김(`한국`) 또는 `""`. 호출부는 `region &&` 가드가 있어 `""`이면 미표시.
|
||||||
|
- 단일 토큰(`"한국"`) → `한국` 그대로.
|
||||||
|
|
||||||
|
## 검증
|
||||||
|
- 단위 테스트(가능 시) 또는 dev 화면에서 상세/리스트 지역 표기가 `›` 계층으로 보이는지.
|
||||||
|
- `npm run build`(또는 tsc/lint) 통과.
|
||||||
@@ -7,6 +7,7 @@ import ReviewSection from "@/components/ReviewSection";
|
|||||||
import MemoSection from "@/components/MemoSection";
|
import MemoSection from "@/components/MemoSection";
|
||||||
import { RestaurantDetailSkeleton } from "@/components/Skeleton";
|
import { RestaurantDetailSkeleton } from "@/components/Skeleton";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
|
import { formatRegion } from "@/lib/region";
|
||||||
|
|
||||||
interface RestaurantDetailProps {
|
interface RestaurantDetailProps {
|
||||||
restaurant: Restaurant;
|
restaurant: Restaurant;
|
||||||
@@ -129,7 +130,7 @@ export default function RestaurantDetail({
|
|||||||
)}
|
)}
|
||||||
{restaurant.region && (
|
{restaurant.region && (
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-400 dark:text-gray-500">지역</span> <span className="text-gray-600 dark:text-gray-300">{restaurant.region}</span>
|
<span className="text-gray-400 dark:text-gray-500">지역</span> <span className="text-gray-600 dark:text-gray-300">{formatRegion(restaurant.region)}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{restaurant.price_range && (
|
{restaurant.price_range && (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { Restaurant } from "@/lib/api";
|
import type { Restaurant } from "@/lib/api";
|
||||||
import { getCuisineIcon } from "@/lib/cuisine-icons";
|
import { getCuisineIcon } from "@/lib/cuisine-icons";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
|
import { formatRegion } from "@/lib/region";
|
||||||
import { RestaurantListSkeleton } from "@/components/Skeleton";
|
import { RestaurantListSkeleton } from "@/components/Skeleton";
|
||||||
|
|
||||||
interface RestaurantListProps {
|
interface RestaurantListProps {
|
||||||
@@ -51,7 +52,7 @@ export default function RestaurantList({
|
|||||||
{r.name}
|
{r.name}
|
||||||
</h4>
|
</h4>
|
||||||
{r.region && (
|
{r.region && (
|
||||||
<span className="text-[11px] text-gray-400 dark:text-gray-500 truncate">{r.region}</span>
|
<span className="text-[11px] text-gray-400 dark:text-gray-500 truncate">{formatRegion(r.region)}</span>
|
||||||
)}
|
)}
|
||||||
{r.rating && (
|
{r.rating && (
|
||||||
<span className="text-xs text-yellow-600 dark:text-yellow-400 font-medium whitespace-nowrap shrink-0">★ {r.rating}</span>
|
<span className="text-xs text-yellow-600 dark:text-yellow-400 font-medium whitespace-nowrap shrink-0">★ {r.rating}</span>
|
||||||
|
|||||||
22
frontend/src/lib/region.ts
Normal file
22
frontend/src/lib/region.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// 지역 표시 유틸 — #532
|
||||||
|
// region 은 "나라|시도|구군" 파이프 구분 문자열. 사용자에게는 계층(breadcrumb)으로 보여준다.
|
||||||
|
|
||||||
|
/** 표시에서 제외할 더미/무의미 토큰. */
|
||||||
|
const DUMMY_TOKENS = new Set(["", "null", "나라"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 파이프 구분 region 을 "나라 › 시도 › 구군" 계층 문자열로 변환한다.
|
||||||
|
* 빈 토큰·"null"·더미("나라")는 제거한다. 유효 토큰이 없으면 "".
|
||||||
|
*
|
||||||
|
* 예) "한국|서울|강남구" → "한국 › 서울 › 강남구"
|
||||||
|
* "일본|null" → "일본"
|
||||||
|
* "한국||관악구" → "한국 › 관악구"
|
||||||
|
*/
|
||||||
|
export function formatRegion(region: string | null | undefined): string {
|
||||||
|
if (!region) return "";
|
||||||
|
return region
|
||||||
|
.split("|")
|
||||||
|
.map((t) => t.trim())
|
||||||
|
.filter((t) => !DUMMY_TOKENS.has(t.toLowerCase()))
|
||||||
|
.join(" › ");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user