Add admin features, responsive UI, user reviews, visit stats, and channel-colored markers

- Admin: video management with Google Maps match status, manual restaurant mapping, restaurant remap on name change
- Admin: user management tab with favorites/reviews detail
- Admin: channel deletion fix for IDs with slashes
- Frontend: responsive mobile layout (map top, list bottom, 2-row header)
- Frontend: channel-colored map markers with legend
- Frontend: my reviews list, favorites toggle, visit counter overlay
- Frontend: force light mode for dark theme devices
- Backend: visit tracking (site_visits table), user reviews endpoint
- Backend: bulk transcript/extract streaming, geocode key fixes
- Nginx config for production deployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-07 14:52:20 +09:00
parent 36bec10bd0
commit 3694730501
27 changed files with 4346 additions and 189 deletions

View File

@@ -0,0 +1,69 @@
"use client";
import type { Review } from "@/lib/api";
interface MyReview extends Review {
restaurant_id: string;
restaurant_name: string | null;
}
interface MyReviewsListProps {
reviews: MyReview[];
onClose: () => void;
onSelectRestaurant: (restaurantId: string) => void;
}
export default function MyReviewsList({
reviews,
onClose,
onSelectRestaurant,
}: MyReviewsListProps) {
return (
<div className="p-4 space-y-3">
<div className="flex justify-between items-center">
<h2 className="font-bold text-lg"> ({reviews.length})</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-xl leading-none"
>
x
</button>
</div>
{reviews.length === 0 ? (
<p className="text-sm text-gray-500 py-8 text-center">
.
</p>
) : (
<div className="space-y-2">
{reviews.map((r) => (
<button
key={r.id}
onClick={() => onSelectRestaurant(r.restaurant_id)}
className="w-full text-left border rounded-lg p-3 hover:bg-gray-50 transition-colors"
>
<div className="flex items-center justify-between mb-1">
<span className="font-semibold text-sm truncate">
{r.restaurant_name || "알 수 없는 식당"}
</span>
<span className="text-yellow-500 text-sm shrink-0 ml-2">
{"★".repeat(Math.round(r.rating))}
<span className="text-gray-500 ml-1">{r.rating}</span>
</span>
</div>
{r.review_text && (
<p className="text-xs text-gray-600 line-clamp-2">
{r.review_text}
</p>
)}
<div className="flex items-center gap-2 mt-1.5 text-[10px] text-gray-400">
{r.visited_at && <span>: {r.visited_at}</span>}
{r.created_at && <span>{r.created_at.slice(0, 10)}</span>}
</div>
</button>
))}
</div>
)}
</div>
);
}