벌크 자막/추출 개선, 검색 필터 무시, geocoding 필드 수정, 네이버맵 링크

- 벌크 자막: 브라우저 우선 + API fallback, 광고 즉시 skip, 대기 시간 단축
- 벌크 자막/추출: 선택한 영상만 처리 가능 (체크박스 선택 후 실행)
- 자막 실패 시 no_transcript 상태 마킹하여 재시도 방지
- 검색 시 필터 조건 무시 (채널/장르/가격/지역/영역 초기화)
- 리셋 버튼 클릭 시 검색어 입력란 초기화
- RestaurantMapper updateFields에 google_place_id, rating 등 geocoding 필드 추가
- SearchMapper에 tabling_url, catchtable_url, phone, website 필드 추가
- 식당 상세에 네이버 지도 링크 추가
- YouTubeService.getTranscriptApi public 전환

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-11 09:00:40 +09:00
parent cdee37e341
commit 0f985d52a9
13 changed files with 405 additions and 76 deletions

View File

@@ -154,6 +154,8 @@ export default function Home() {
const [myReviews, setMyReviews] = useState<(Review & { restaurant_id: string; restaurant_name: string | null })[]>([]);
const [visits, setVisits] = useState<{ today: number; total: number } | null>(null);
const [userLoc, setUserLoc] = useState<{ lat: number; lng: number }>({ lat: 37.498, lng: 127.0276 });
const [isSearchResult, setIsSearchResult] = useState(false);
const [resetCount, setResetCount] = useState(0);
const geoApplied = useRef(false);
const regionTree = useMemo(() => buildRegionTree(restaurants), [restaurants]);
@@ -174,6 +176,13 @@ export default function Home() {
const filteredRestaurants = useMemo(() => {
const dist = (r: Restaurant) =>
(r.latitude - userLoc.lat) ** 2 + (r.longitude - userLoc.lng) ** 2;
if (isSearchResult) {
return [...restaurants].sort((a, b) => {
const da = dist(a), db = dist(b);
if (da !== db) return da - db;
return (b.rating || 0) - (a.rating || 0);
});
}
return restaurants.filter((r) => {
if (channelFilter && !(r.channels || []).includes(channelFilter)) return false;
if (cuisineFilter && !matchCuisineFilter(r.cuisine_type, cuisineFilter)) return false;
@@ -194,7 +203,7 @@ export default function Home() {
if (da !== db) return da - db;
return (b.rating || 0) - (a.rating || 0);
});
}, [restaurants, channelFilter, cuisineFilter, priceFilter, countryFilter, cityFilter, districtFilter, boundsFilterOn, mapBounds, userLoc]);
}, [restaurants, isSearchResult, channelFilter, cuisineFilter, priceFilter, countryFilter, cityFilter, districtFilter, boundsFilterOn, mapBounds, userLoc]);
// Set desktop default to map mode on mount + get user location
useEffect(() => {
@@ -217,6 +226,7 @@ export default function Home() {
// Load restaurants on mount and when channel filter changes
useEffect(() => {
setLoading(true);
setIsSearchResult(false);
api
.getRestaurants({ limit: 500, channel: channelFilter || undefined })
.then(setRestaurants)
@@ -253,6 +263,18 @@ export default function Home() {
setRestaurants(results);
setSelected(null);
setShowDetail(false);
setIsSearchResult(true);
// 검색 시 필터 초기화
setChannelFilter("");
setCuisineFilter("");
setPriceFilter("");
setCountryFilter("");
setCityFilter("");
setDistrictFilter("");
setBoundsFilterOn(false);
// 검색 결과에 맞게 지도 이동
const flyTo = computeFlyTo(results);
if (flyTo) setRegionFlyTo(flyTo);
} catch (e) {
console.error("Search failed:", e);
} finally {
@@ -350,6 +372,8 @@ export default function Home() {
setBoundsFilterOn(false);
setShowFavorites(false);
setShowMyReviews(false);
setIsSearchResult(false);
setResetCount((c) => c + 1);
api
.getRestaurants({ limit: 500 })
.then((data) => {
@@ -531,7 +555,7 @@ export default function Home() {
{/* Row 1: Search + dropdown filters */}
<div className="flex items-center gap-3">
<div className="w-96 shrink-0">
<SearchBar onSearch={handleSearch} isLoading={loading} />
<SearchBar key={resetCount} onSearch={handleSearch} isLoading={loading} />
</div>
<button
onClick={handleReset}
@@ -717,7 +741,7 @@ export default function Home() {
{/* ── Header row 2 (mobile only): search + toolbar ── */}
<div className={`md:hidden px-4 pb-3 space-y-2 ${mobileTab === "favorites" || mobileTab === "profile" ? "hidden" : ""}`}>
{/* Row 1: Search */}
<SearchBar onSearch={handleSearch} isLoading={loading} />
<SearchBar key={resetCount} onSearch={handleSearch} isLoading={loading} />
{/* Row 2: Toolbar */}
<div className="flex items-center gap-2">
<button