비공개 메모 기능 추가 + 아이콘 개선
- 식당별 1:1 비공개 메모 CRUD (user_memos 테이블) - 내 기록에 리뷰/메모 탭 분리 - 백오피스 유저 관리에 메모 수/상세 표시 - 리뷰/메모 작성 시 현재 날짜 기본값 - 지도우선/목록우선 버튼 Material Symbols 아이콘 적용 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2148,6 +2148,7 @@ interface AdminUser {
|
||||
created_at: string | null;
|
||||
favorite_count: number;
|
||||
review_count: number;
|
||||
memo_count: number;
|
||||
}
|
||||
|
||||
interface UserFavorite {
|
||||
@@ -2171,6 +2172,16 @@ interface UserReview {
|
||||
restaurant_name: string | null;
|
||||
}
|
||||
|
||||
interface UserMemo {
|
||||
id: string;
|
||||
restaurant_id: string;
|
||||
rating: number | null;
|
||||
memo_text: string | null;
|
||||
visited_at: string | null;
|
||||
created_at: string;
|
||||
restaurant_name: string | null;
|
||||
}
|
||||
|
||||
function UsersPanel() {
|
||||
const [users, setUsers] = useState<AdminUser[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
@@ -2178,6 +2189,7 @@ function UsersPanel() {
|
||||
const [selectedUser, setSelectedUser] = useState<AdminUser | null>(null);
|
||||
const [favorites, setFavorites] = useState<UserFavorite[]>([]);
|
||||
const [reviews, setReviews] = useState<UserReview[]>([]);
|
||||
const [memos, setMemos] = useState<UserMemo[]>([]);
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
const perPage = 20;
|
||||
|
||||
@@ -2200,17 +2212,20 @@ function UsersPanel() {
|
||||
setSelectedUser(null);
|
||||
setFavorites([]);
|
||||
setReviews([]);
|
||||
setMemos([]);
|
||||
return;
|
||||
}
|
||||
setSelectedUser(u);
|
||||
setDetailLoading(true);
|
||||
try {
|
||||
const [favs, revs] = await Promise.all([
|
||||
const [favs, revs, mems] = await Promise.all([
|
||||
api.getAdminUserFavorites(u.id),
|
||||
api.getAdminUserReviews(u.id),
|
||||
api.getAdminUserMemos(u.id),
|
||||
]);
|
||||
setFavorites(favs);
|
||||
setReviews(revs);
|
||||
setMemos(mems);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
@@ -2233,6 +2248,7 @@ function UsersPanel() {
|
||||
<th className="text-left px-4 py-2">이메일</th>
|
||||
<th className="text-center px-4 py-2">찜</th>
|
||||
<th className="text-center px-4 py-2">리뷰</th>
|
||||
<th className="text-center px-4 py-2">메모</th>
|
||||
<th className="text-left px-4 py-2">가입일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -2284,6 +2300,15 @@ function UsersPanel() {
|
||||
<span className="text-gray-300">0</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-center">
|
||||
{u.memo_count > 0 ? (
|
||||
<span className="inline-block px-2 py-0.5 bg-purple-50 text-purple-600 rounded-full text-xs font-medium">
|
||||
{u.memo_count}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-300">0</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-gray-400 text-xs">
|
||||
{u.created_at?.slice(0, 10) || "-"}
|
||||
</td>
|
||||
@@ -2343,7 +2368,7 @@ function UsersPanel() {
|
||||
{detailLoading ? (
|
||||
<p className="text-sm text-gray-500">로딩 중...</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{/* Favorites */}
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm mb-2 text-red-600">
|
||||
@@ -2419,6 +2444,46 @@ function UsersPanel() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Memos */}
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm mb-2 text-purple-600">
|
||||
작성한 메모 ({memos.length})
|
||||
</h3>
|
||||
{memos.length === 0 ? (
|
||||
<p className="text-xs text-gray-400">작성한 메모가 없습니다.</p>
|
||||
) : (
|
||||
<div className="space-y-1 max-h-64 overflow-y-auto">
|
||||
{memos.map((m) => (
|
||||
<div
|
||||
key={m.id}
|
||||
className="border border-purple-200 rounded px-3 py-2 text-xs space-y-0.5 bg-purple-50/30"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">
|
||||
{m.restaurant_name || "알 수 없음"}
|
||||
</span>
|
||||
{m.rating && (
|
||||
<span className="text-yellow-500 shrink-0">
|
||||
{"★".repeat(Math.round(m.rating))} {m.rating}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{m.memo_text && (
|
||||
<p className="text-gray-600 line-clamp-2">
|
||||
{m.memo_text}
|
||||
</p>
|
||||
)}
|
||||
<div className="text-gray-400 text-[10px]">
|
||||
{m.visited_at && `방문: ${m.visited_at} · `}
|
||||
{m.created_at?.slice(0, 10)}
|
||||
<span className="ml-1 text-purple-400">비공개</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user