Initial commit: Tasteby - YouTube restaurant map service
Backend (FastAPI + Oracle ADB), Frontend (Next.js), daemon worker. Features: channel/video/restaurant management, semantic search, Google OAuth, user reviews. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
113
frontend/src/components/RestaurantDetail.tsx
Normal file
113
frontend/src/components/RestaurantDetail.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "@/lib/api";
|
||||
import type { Restaurant, VideoLink } from "@/lib/api";
|
||||
import ReviewSection from "@/components/ReviewSection";
|
||||
|
||||
interface RestaurantDetailProps {
|
||||
restaurant: Restaurant;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function RestaurantDetail({
|
||||
restaurant,
|
||||
onClose,
|
||||
}: RestaurantDetailProps) {
|
||||
const [videos, setVideos] = useState<VideoLink[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
api
|
||||
.getRestaurantVideos(restaurant.id)
|
||||
.then(setVideos)
|
||||
.catch(() => setVideos([]))
|
||||
.finally(() => setLoading(false));
|
||||
}, [restaurant.id]);
|
||||
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="flex justify-between items-start">
|
||||
<h2 className="text-lg font-bold">{restaurant.name}</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 text-xl leading-none"
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 text-sm">
|
||||
{restaurant.cuisine_type && (
|
||||
<p>
|
||||
<span className="text-gray-500">종류:</span> {restaurant.cuisine_type}
|
||||
</p>
|
||||
)}
|
||||
{restaurant.address && (
|
||||
<p>
|
||||
<span className="text-gray-500">주소:</span> {restaurant.address}
|
||||
</p>
|
||||
)}
|
||||
{restaurant.region && (
|
||||
<p>
|
||||
<span className="text-gray-500">지역:</span> {restaurant.region}
|
||||
</p>
|
||||
)}
|
||||
{restaurant.price_range && (
|
||||
<p>
|
||||
<span className="text-gray-500">가격대:</span> {restaurant.price_range}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm mb-2">관련 영상</h3>
|
||||
{loading ? (
|
||||
<p className="text-sm text-gray-500">로딩 중...</p>
|
||||
) : videos.length === 0 ? (
|
||||
<p className="text-sm text-gray-500">관련 영상이 없습니다</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{videos.map((v) => (
|
||||
<div key={v.video_id} className="border rounded-lg p-3">
|
||||
<a
|
||||
href={v.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm font-medium text-blue-600 hover:underline"
|
||||
>
|
||||
{v.title}
|
||||
</a>
|
||||
{v.foods_mentioned.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{v.foods_mentioned.map((f, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-2 py-0.5 bg-orange-50 text-orange-700 rounded text-xs"
|
||||
>
|
||||
{f}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{v.evaluation?.text && (
|
||||
<p className="mt-1 text-xs text-gray-600">
|
||||
{v.evaluation.text}
|
||||
</p>
|
||||
)}
|
||||
{v.guests.length > 0 && (
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
게스트: {v.guests.join(", ")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ReviewSection restaurantId={restaurant.id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user