From 98e4f16a15860a23c51cb7652ced01f499115ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Tue, 7 Oct 2025 15:33:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=8C=E5=8F=B0=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E8=A9=B3=E7=B4=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/page.tsx | 436 ++++++++++++++++++++++++++++- app/api/admin/wishes/[id]/route.ts | 100 +++++++ 2 files changed, 534 insertions(+), 2 deletions(-) create mode 100644 app/api/admin/wishes/[id]/route.ts diff --git a/app/admin/page.tsx b/app/admin/page.tsx index e708a64..4af1f78 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -6,6 +6,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Download, Eye, @@ -31,7 +32,17 @@ import { ChevronUp, Shield, EyeOff, - HelpCircle + HelpCircle, + X, + Calendar, + User, + Mail, + Tag, + Star, + Image as ImageIcon, + Clock, + MapPin, + Monitor } from "lucide-react" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" @@ -106,6 +117,12 @@ export default function AdminPage() { const [isExportingExcel, setIsExportingExcel] = useState(false) const [showCategoryGuide, setShowCategoryGuide] = useState(false) const [showPrivacyDetails, setShowPrivacyDetails] = useState(false) + const [selectedWish, setSelectedWish] = useState(null) + const [showWishDetails, setShowWishDetails] = useState(false) + const [wishDetails, setWishDetails] = useState(null) + const [loadingDetails, setLoadingDetails] = useState(false) + const [showImageModal, setShowImageModal] = useState(false) + const [selectedImage, setSelectedImage] = useState(null) // 分頁狀態 const [currentPage, setCurrentPage] = useState(1) @@ -465,6 +482,35 @@ export default function AdminPage() { } } + // 查看詳細資訊 + const viewWishDetails = async (wish: WishData) => { + try { + setLoadingDetails(true) + setSelectedWish(wish) + + const response = await fetch(`/api/admin/wishes/${wish.id}`) + const result = await response.json() + + if (result.success) { + setWishDetails(result.data) + setShowWishDetails(true) + } else { + throw new Error(result.error || 'Failed to fetch details') + } + } catch (error) { + console.error('獲取詳細資訊失敗:', error) + alert('獲取詳細資訊失敗,請稍後再試') + } finally { + setLoadingDetails(false) + } + } + + // 查看大圖 + const viewImage = (image: any) => { + setSelectedImage(image) + setShowImageModal(true) + } + useEffect(() => { fetchData() }, []) @@ -740,9 +786,15 @@ export default function AdminPage() { @@ -1031,6 +1083,386 @@ export default function AdminPage() { + + {/* 詳細資訊對話框 */} + + + + + + 困擾案例詳細資訊 + + + 查看困擾案例的完整資訊,包括點讚記錄 + + + +
+ {wishDetails && ( +
+ {/* 基本信息 */} + + + + + 基本信息 + + + +
+
+ + ID: + {wishDetails.id} +
+
+ + 優先級: + + {wishDetails.priority} + +
+
+ + 可見性: + + {wishDetails.isPublic ? '公開' : '私密'} + +
+
+ + 狀態: + + {wishDetails.status === 'active' ? '活躍' : '非活躍'} + +
+
+ +
+

標題

+

+ {wishDetails.title} +

+
+ + {wishDetails.category && ( +
+

分類

+ + {wishDetails.category} + +
+ )} +
+
+ + {/* 困擾內容 */} + + + + + 遇到的困擾 + + + +

+ {wishDetails.currentPain} +

+
+
+ + {/* 期望解決方式 */} + + + + + 期望的解決方式 + + + +

+ {wishDetails.expectedSolution} +

+
+
+ + {/* 預期效果 */} + {wishDetails.expectedEffect && ( + + + + + 預期效果 + + + +

+ {wishDetails.expectedEffect} +

+
+
+ )} + + {/* 圖片 */} + {wishDetails.images && Array.isArray(wishDetails.images) && wishDetails.images.length > 0 && ( + + + + + 相關圖片 + + + +
+ {wishDetails.images.map((image: any, index: number) => ( +
+ {/* 圖片顯示 */} +
viewImage(image)}> + {(image.url || image.base64 || image.storage_path || image.public_url) ? ( + <> + {image.name { + const target = e.target as HTMLImageElement; + target.nextElementSibling?.classList.add('hidden'); + }} + onError={(e) => { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.nextElementSibling?.classList.remove('hidden'); + }} + /> + {/* 載入中指示器 */} +
+
+ 載入中... +
+ {/* 放大圖標 */} +
+ + + +
+ + ) : ( +
+ + 無圖片數據 +
+ )} +
+ {/* 圖片資訊 */} +
+
+
+ {image.name || `圖片 ${index + 1}`} +
+
+ 點擊放大 +
+
+ {image.size && ( +
+ 大小: {typeof image.size === 'number' ? `${(image.size / 1024).toFixed(1)} KB` : image.size} +
+ )} + {image.type && ( +
+ 類型: {image.type} +
+ )} +
+
+ ))} +
+
+
+ )} + + {/* 用戶資訊 */} + + + + + 用戶資訊 + + + +
+ + 會話 ID: + {wishDetails.userSession} +
+ {wishDetails.email && ( +
+ + 電子郵件: + {wishDetails.email} +
+ )} +
+
+ + {/* 時間資訊 */} + + + + + 時間資訊 + + + +
+ + 創建時間: + + {new Date(wishDetails.createdAt).toLocaleString('zh-TW')} + +
+
+ + 更新時間: + + {new Date(wishDetails.updatedAt).toLocaleString('zh-TW')} + +
+
+
+ + {/* 點讚記錄 */} + + + + + 點讚記錄 ({wishDetails.likes?.length || 0} 個) + + + + {wishDetails.likes && wishDetails.likes.length > 0 ? ( +
+ {wishDetails.likes.map((like: any, index: number) => ( +
+
+
+ + 點讚 #{index + 1} +
+ + {new Date(like.createdAt).toLocaleString('zh-TW')} + +
+
+
+ + 會話: + {like.userSession} +
+ {like.ipAddress && ( +
+ + IP: + {like.ipAddress} +
+ )} +
+ {like.userAgent && ( +
+ + 瀏覽器: + {like.userAgent} +
+ )} +
+ ))} +
+ ) : ( +
+ +

暫無點讚記錄

+
+ )} +
+
+
+ )} +
+ +
+ +
+
+
+ + {/* 圖片放大對話框 */} + + + + + + {selectedImage?.name || '圖片預覽'} + + + 點擊圖片外部或按 ESC 鍵關閉 + + + +
+ {selectedImage && ( + <> + {selectedImage.name e.stopPropagation()} + /> + {/* 圖片載入失敗處理 */} +
+ + 圖片載入失敗 +
+ + )} +
+ +
+
+ {selectedImage?.size && ( + 大小: {typeof selectedImage.size === 'number' ? `${(selectedImage.size / 1024).toFixed(1)} KB` : selectedImage.size} + )} + {selectedImage?.type && ( + 類型: {selectedImage.type} + )} +
+ +
+
+
) } diff --git a/app/api/admin/wishes/[id]/route.ts b/app/api/admin/wishes/[id]/route.ts new file mode 100644 index 0000000..5cc2bc3 --- /dev/null +++ b/app/api/admin/wishes/[id]/route.ts @@ -0,0 +1,100 @@ +import { NextRequest, NextResponse } from 'next/server' +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient() + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const wishId = parseInt(params.id) + + if (isNaN(wishId)) { + return NextResponse.json( + { success: false, error: 'Invalid wish ID' }, + { status: 400 } + ) + } + + console.log(`🔍 後台管理 - 獲取困擾案例詳細資訊: ID=${wishId}`) + + // 獲取 wish 詳細資訊 + const wish = await prisma.wish.findUnique({ + where: { id: wishId }, + include: { + likes: { + select: { + id: true, + userSession: true, + ipAddress: true, + userAgent: true, + createdAt: true + } + } + } + }) + + if (!wish) { + return NextResponse.json( + { success: false, error: 'Wish not found' }, + { status: 404 } + ) + } + + // 處理圖片數據 + let images = [] + if (wish.images && Array.isArray(wish.images)) { + images = wish.images.map((img: any) => ({ + id: img.id, + name: img.name, + size: img.size, + type: img.type, + url: img.public_url || img.base64 || img.url || img.storage_path, + base64: img.base64, + storage_path: img.storage_path, + public_url: img.public_url, + uploaded_at: img.uploaded_at + })) + } + + // 轉換數據格式 + const wishDetails = { + id: Number(wish.id), + title: wish.title, + currentPain: wish.currentPain, + expectedSolution: wish.expectedSolution, + expectedEffect: wish.expectedEffect, + isPublic: wish.isPublic, + email: wish.email, + images: images, + userSession: wish.userSession, + status: wish.status, + category: wish.category, + priority: Number(wish.priority), + createdAt: wish.createdAt.toISOString(), + updatedAt: wish.updatedAt.toISOString(), + likes: wish.likes.map(like => ({ + id: Number(like.id), + userSession: like.userSession, + ipAddress: like.ipAddress, + userAgent: like.userAgent, + createdAt: like.createdAt.toISOString() + })) + } + + console.log(`✅ 成功獲取困擾案例詳細資訊: ${wishDetails.title}`) + + return NextResponse.json({ + success: true, + data: wishDetails + }) + + } catch (error) { + console.error('獲取困擾案例詳細資訊失敗:', error) + return NextResponse.json( + { success: false, error: 'Failed to fetch wish details' }, + { status: 500 } + ) + } +}