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>
76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
import {
|
|
APIProvider,
|
|
Map,
|
|
AdvancedMarker,
|
|
InfoWindow,
|
|
} from "@vis.gl/react-google-maps";
|
|
import type { Restaurant } from "@/lib/api";
|
|
|
|
const SEOUL_CENTER = { lat: 37.5665, lng: 126.978 };
|
|
const API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY || "";
|
|
|
|
interface MapViewProps {
|
|
restaurants: Restaurant[];
|
|
onSelectRestaurant?: (r: Restaurant) => void;
|
|
}
|
|
|
|
export default function MapView({ restaurants, onSelectRestaurant }: MapViewProps) {
|
|
const [selected, setSelected] = useState<Restaurant | null>(null);
|
|
|
|
const handleMarkerClick = useCallback(
|
|
(r: Restaurant) => {
|
|
setSelected(r);
|
|
onSelectRestaurant?.(r);
|
|
},
|
|
[onSelectRestaurant]
|
|
);
|
|
|
|
return (
|
|
<APIProvider apiKey={API_KEY}>
|
|
<Map
|
|
defaultCenter={SEOUL_CENTER}
|
|
defaultZoom={12}
|
|
mapId="tasteby-map"
|
|
className="h-full w-full"
|
|
>
|
|
{restaurants.map((r) => (
|
|
<AdvancedMarker
|
|
key={r.id}
|
|
position={{ lat: r.latitude, lng: r.longitude }}
|
|
onClick={() => handleMarkerClick(r)}
|
|
/>
|
|
))}
|
|
|
|
{selected && (
|
|
<InfoWindow
|
|
position={{ lat: selected.latitude, lng: selected.longitude }}
|
|
onCloseClick={() => setSelected(null)}
|
|
>
|
|
<div className="max-w-xs p-1">
|
|
<h3 className="font-bold text-base">{selected.name}</h3>
|
|
{selected.cuisine_type && (
|
|
<p className="text-sm text-gray-600">{selected.cuisine_type}</p>
|
|
)}
|
|
{selected.address && (
|
|
<p className="text-xs text-gray-500 mt-1">{selected.address}</p>
|
|
)}
|
|
{selected.price_range && (
|
|
<p className="text-xs text-gray-500">{selected.price_range}</p>
|
|
)}
|
|
<button
|
|
onClick={() => onSelectRestaurant?.(selected)}
|
|
className="mt-2 text-sm text-blue-600 hover:underline"
|
|
>
|
|
상세 보기
|
|
</button>
|
|
</div>
|
|
</InfoWindow>
|
|
)}
|
|
</Map>
|
|
</APIProvider>
|
|
);
|
|
}
|