UX improvements: mobile bottom sheet, cuisine taxonomy, search enhancements
- Add BottomSheet component for Google Maps-style restaurant detail on mobile (3-snap drag: 40%/55%/92%, velocity-based close, backdrop overlay) - Mobile map mode now full-screen with bottom sheet overlay for details - Collapsible filter panel on mobile with active filter badge count - Standardized cuisine taxonomy (46 categories: 한식|국밥, 일식|스시 etc.) with LLM remap endpoint and admin UI button - Enhanced search: keyword search now includes foods_mentioned + video title - Search results include channels array for frontend filtering - Channel filter moved to frontend filteredRestaurants (not API-level) - LLM extraction prompt updated for pipe-delimited region + cuisine taxonomy - Vector rebuild endpoint with rich JSON chunks per restaurant - Geolocation-based auto region selection on page load - Desktop filters split into two clean rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,8 @@ export interface Channel {
|
||||
channel_id: string;
|
||||
channel_name: string;
|
||||
title_filter: string | null;
|
||||
video_count: number;
|
||||
last_scanned_at: string | null;
|
||||
}
|
||||
|
||||
export interface Video {
|
||||
@@ -107,6 +109,7 @@ export interface User {
|
||||
email: string | null;
|
||||
nickname: string | null;
|
||||
avatar_url: string | null;
|
||||
is_admin?: boolean;
|
||||
}
|
||||
|
||||
export interface Review {
|
||||
@@ -120,6 +123,17 @@ export interface Review {
|
||||
user_avatar_url: string | null;
|
||||
}
|
||||
|
||||
export interface DaemonConfig {
|
||||
scan_enabled: boolean;
|
||||
scan_interval_min: number;
|
||||
process_enabled: boolean;
|
||||
process_interval_min: number;
|
||||
process_limit: number;
|
||||
last_scan_at: string | null;
|
||||
last_process_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface ReviewsResponse {
|
||||
reviews: Review[];
|
||||
avg_rating: number | null;
|
||||
@@ -428,4 +442,29 @@ export const api = {
|
||||
{ method: "PUT", body: JSON.stringify(data) }
|
||||
);
|
||||
},
|
||||
|
||||
// Daemon config
|
||||
getDaemonConfig() {
|
||||
return fetchApi<DaemonConfig>("/api/daemon/config");
|
||||
},
|
||||
|
||||
updateDaemonConfig(data: Partial<DaemonConfig>) {
|
||||
return fetchApi<{ ok: boolean }>("/api/daemon/config", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
},
|
||||
|
||||
runDaemonScan() {
|
||||
return fetchApi<{ ok: boolean; new_videos: number }>("/api/daemon/run/scan", {
|
||||
method: "POST",
|
||||
});
|
||||
},
|
||||
|
||||
runDaemonProcess(limit: number = 10) {
|
||||
return fetchApi<{ ok: boolean; restaurants_extracted: number }>(
|
||||
`/api/daemon/run/process?limit=${limit}`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
49
frontend/src/lib/cuisine-icons.ts
Normal file
49
frontend/src/lib/cuisine-icons.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Cuisine type → icon mapping.
|
||||
* Works with "대분류|소분류" format (e.g. "한식|국밥/해장국").
|
||||
*/
|
||||
|
||||
const CUISINE_ICON_MAP: Record<string, string> = {
|
||||
"한식": "🍚",
|
||||
"일식": "🍣",
|
||||
"중식": "🥟",
|
||||
"양식": "🍝",
|
||||
"아시아": "🍜",
|
||||
"기타": "🍴",
|
||||
};
|
||||
|
||||
// Sub-category overrides for more specific icons
|
||||
const SUB_ICON_RULES: { keyword: string; icon: string }[] = [
|
||||
{ keyword: "회/횟집", icon: "🐟" },
|
||||
{ keyword: "해산물", icon: "🦐" },
|
||||
{ keyword: "삼겹살/돼지구이", icon: "🥩" },
|
||||
{ keyword: "소고기/한우구이", icon: "🥩" },
|
||||
{ keyword: "곱창/막창", icon: "🥩" },
|
||||
{ keyword: "닭/오리구이", icon: "🍗" },
|
||||
{ keyword: "스테이크", icon: "🥩" },
|
||||
{ keyword: "햄버거", icon: "🍔" },
|
||||
{ keyword: "피자", icon: "🍕" },
|
||||
{ keyword: "카페/디저트", icon: "☕" },
|
||||
{ keyword: "베이커리", icon: "🥐" },
|
||||
{ keyword: "치킨", icon: "🍗" },
|
||||
{ keyword: "주점/포차", icon: "🍺" },
|
||||
{ keyword: "이자카야", icon: "🍶" },
|
||||
{ keyword: "라멘", icon: "🍜" },
|
||||
{ keyword: "국밥/해장국", icon: "🍲" },
|
||||
{ keyword: "분식", icon: "🍜" },
|
||||
];
|
||||
|
||||
const DEFAULT_ICON = "🍴";
|
||||
|
||||
export function getCuisineIcon(cuisineType: string | null | undefined): string {
|
||||
if (!cuisineType) return DEFAULT_ICON;
|
||||
|
||||
// Check sub-category first
|
||||
for (const rule of SUB_ICON_RULES) {
|
||||
if (cuisineType.includes(rule.keyword)) return rule.icon;
|
||||
}
|
||||
|
||||
// Fall back to main category (prefix before |)
|
||||
const main = cuisineType.split("|")[0];
|
||||
return CUISINE_ICON_MAP[main] || DEFAULT_ICON;
|
||||
}
|
||||
Reference in New Issue
Block a user