新增後台查看詳細
This commit is contained in:
@@ -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<WishData | null>(null)
|
||||
const [showWishDetails, setShowWishDetails] = useState(false)
|
||||
const [wishDetails, setWishDetails] = useState<any>(null)
|
||||
const [loadingDetails, setLoadingDetails] = useState(false)
|
||||
const [showImageModal, setShowImageModal] = useState(false)
|
||||
const [selectedImage, setSelectedImage] = useState<any>(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() {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => viewWishDetails(wish)}
|
||||
disabled={loadingDetails}
|
||||
className="text-blue-200 border-slate-600/50 hover:bg-slate-700/50 hover:text-white hover:border-cyan-400/50"
|
||||
>
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
{loadingDetails ? (
|
||||
<RefreshCw className="w-4 h-4 mr-1 animate-spin" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
)}
|
||||
查看
|
||||
</Button>
|
||||
</td>
|
||||
@@ -1031,6 +1083,386 @@ export default function AdminPage() {
|
||||
</Tabs>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* 詳細資訊對話框 */}
|
||||
<Dialog open={showWishDetails} onOpenChange={setShowWishDetails}>
|
||||
<DialogContent className="w-[95vw] max-w-6xl h-[95vh] bg-slate-800 border-slate-700 flex flex-col p-0 z-[60]">
|
||||
<DialogHeader className="px-6 py-4 border-b border-slate-600/50 flex-shrink-0">
|
||||
<DialogTitle className="text-white flex items-center gap-2">
|
||||
<Eye className="w-5 h-5 text-cyan-400" />
|
||||
困擾案例詳細資訊
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-blue-200">
|
||||
查看困擾案例的完整資訊,包括點讚記錄
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
{wishDetails && (
|
||||
<div className="space-y-6">
|
||||
{/* 基本信息 */}
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-green-400" />
|
||||
基本信息
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tag className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-blue-200">ID:</span>
|
||||
<span className="text-white font-mono">{wishDetails.id}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Star className="w-4 h-4 text-yellow-400" />
|
||||
<span className="text-blue-200">優先級:</span>
|
||||
<Badge variant="outline" className="text-white border-slate-500">
|
||||
{wishDetails.priority}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Eye className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-blue-200">可見性:</span>
|
||||
<Badge
|
||||
variant={wishDetails.isPublic ? "default" : "outline"}
|
||||
className={wishDetails.isPublic
|
||||
? "bg-blue-600/20 text-blue-300 border-blue-500/30"
|
||||
: "bg-orange-600/20 text-orange-300 border-orange-500/30"
|
||||
}
|
||||
>
|
||||
{wishDetails.isPublic ? '公開' : '私密'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="w-4 h-4 text-green-400" />
|
||||
<span className="text-blue-200">狀態:</span>
|
||||
<Badge
|
||||
variant={wishDetails.status === 'active' ? "default" : "secondary"}
|
||||
className={wishDetails.status === 'active'
|
||||
? "bg-green-600/20 text-green-300 border-green-500/30"
|
||||
: "bg-slate-600/20 text-slate-300 border-slate-500/30"
|
||||
}
|
||||
>
|
||||
{wishDetails.status === 'active' ? '活躍' : '非活躍'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-blue-200 font-semibold mb-2">標題</h4>
|
||||
<p className="text-white bg-slate-800/50 p-3 rounded-lg border border-slate-600/30">
|
||||
{wishDetails.title}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{wishDetails.category && (
|
||||
<div>
|
||||
<h4 className="text-blue-200 font-semibold mb-2">分類</h4>
|
||||
<Badge variant="outline" className="text-white border-slate-500">
|
||||
{wishDetails.category}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 困擾內容 */}
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<Users className="w-5 h-5 text-red-400" />
|
||||
遇到的困擾
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-white bg-slate-800/50 p-4 rounded-lg border border-slate-600/30 whitespace-pre-wrap">
|
||||
{wishDetails.currentPain}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 期望解決方式 */}
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<Target className="w-5 h-5 text-green-400" />
|
||||
期望的解決方式
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-white bg-slate-800/50 p-4 rounded-lg border border-slate-600/30 whitespace-pre-wrap">
|
||||
{wishDetails.expectedSolution}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 預期效果 */}
|
||||
{wishDetails.expectedEffect && (
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<Star className="w-5 h-5 text-yellow-400" />
|
||||
預期效果
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-white bg-slate-800/50 p-4 rounded-lg border border-slate-600/30 whitespace-pre-wrap">
|
||||
{wishDetails.expectedEffect}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 圖片 */}
|
||||
{wishDetails.images && Array.isArray(wishDetails.images) && wishDetails.images.length > 0 && (
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<ImageIcon className="w-5 h-5 text-purple-400" />
|
||||
相關圖片
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{wishDetails.images.map((image: any, index: number) => (
|
||||
<div key={index} className="bg-slate-800/50 rounded-lg border border-slate-600/30 overflow-hidden">
|
||||
{/* 圖片顯示 */}
|
||||
<div className="aspect-video bg-slate-900/50 flex items-center justify-center relative group cursor-pointer" onClick={() => viewImage(image)}>
|
||||
{(image.url || image.base64 || image.storage_path || image.public_url) ? (
|
||||
<>
|
||||
<img
|
||||
src={image.public_url || image.url || image.base64 || image.storage_path}
|
||||
alt={image.name || `圖片 ${index + 1}`}
|
||||
className="max-w-full max-h-full object-contain rounded-lg transition-transform group-hover:scale-105"
|
||||
onLoad={(e) => {
|
||||
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');
|
||||
}}
|
||||
/>
|
||||
{/* 載入中指示器 */}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-slate-400 p-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-cyan-400 mb-2"></div>
|
||||
<span className="text-sm">載入中...</span>
|
||||
</div>
|
||||
{/* 放大圖標 */}
|
||||
<div className="absolute top-2 right-2 bg-black/50 backdrop-blur-sm rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center text-slate-400 p-8">
|
||||
<ImageIcon className="w-12 h-12 mb-2 text-slate-500" />
|
||||
<span className="text-sm">無圖片數據</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* 圖片資訊 */}
|
||||
<div className="p-3 border-t border-slate-600/30">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm text-white font-medium truncate">
|
||||
{image.name || `圖片 ${index + 1}`}
|
||||
</div>
|
||||
<div className="text-xs text-cyan-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
點擊放大
|
||||
</div>
|
||||
</div>
|
||||
{image.size && (
|
||||
<div className="text-xs text-slate-400 mt-1">
|
||||
大小: {typeof image.size === 'number' ? `${(image.size / 1024).toFixed(1)} KB` : image.size}
|
||||
</div>
|
||||
)}
|
||||
{image.type && (
|
||||
<div className="text-xs text-slate-400">
|
||||
類型: {image.type}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 用戶資訊 */}
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<User className="w-5 h-5 text-cyan-400" />
|
||||
用戶資訊
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-blue-200">會話 ID:</span>
|
||||
<span className="text-white font-mono text-sm">{wishDetails.userSession}</span>
|
||||
</div>
|
||||
{wishDetails.email && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="w-4 h-4 text-green-400" />
|
||||
<span className="text-blue-200">電子郵件:</span>
|
||||
<span className="text-white">{wishDetails.email}</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 時間資訊 */}
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<Clock className="w-5 h-5 text-orange-400" />
|
||||
時間資訊
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-blue-200">創建時間:</span>
|
||||
<span className="text-white">
|
||||
{new Date(wishDetails.createdAt).toLocaleString('zh-TW')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-green-400" />
|
||||
<span className="text-blue-200">更新時間:</span>
|
||||
<span className="text-white">
|
||||
{new Date(wishDetails.updatedAt).toLocaleString('zh-TW')}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 點讚記錄 */}
|
||||
<Card className="bg-slate-700/50 border-slate-600/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center gap-2">
|
||||
<Heart className="w-5 h-5 text-pink-400" />
|
||||
點讚記錄 ({wishDetails.likes?.length || 0} 個)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{wishDetails.likes && wishDetails.likes.length > 0 ? (
|
||||
<div className="space-y-3 max-h-48 overflow-y-auto scrollbar-thin scrollbar-thumb-slate-600 scrollbar-track-slate-800">
|
||||
{wishDetails.likes.map((like: any, index: number) => (
|
||||
<div key={like.id} className="bg-slate-800/50 p-3 rounded-lg border border-slate-600/30">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Heart className="w-4 h-4 text-pink-400" />
|
||||
<span className="text-blue-200">點讚 #{index + 1}</span>
|
||||
</div>
|
||||
<span className="text-slate-400 text-sm">
|
||||
{new Date(like.createdAt).toLocaleString('zh-TW')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-3 h-3 text-blue-400" />
|
||||
<span className="text-blue-200">會話:</span>
|
||||
<span className="text-white font-mono text-xs">{like.userSession}</span>
|
||||
</div>
|
||||
{like.ipAddress && (
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-3 h-3 text-green-400" />
|
||||
<span className="text-blue-200">IP:</span>
|
||||
<span className="text-white font-mono text-xs">{like.ipAddress}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{like.userAgent && (
|
||||
<div className="mt-2 flex items-start gap-2">
|
||||
<Monitor className="w-3 h-3 text-purple-400 mt-0.5" />
|
||||
<span className="text-blue-200 text-xs">瀏覽器:</span>
|
||||
<span className="text-slate-300 text-xs break-all">{like.userAgent}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Heart className="w-8 h-8 text-slate-500 mx-auto mb-2" />
|
||||
<p className="text-slate-400">暫無點讚記錄</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end px-6 py-4 border-t border-slate-600/50 flex-shrink-0">
|
||||
<Button
|
||||
onClick={() => setShowWishDetails(false)}
|
||||
className="bg-slate-700 hover:bg-slate-600 text-white"
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
關閉
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 圖片放大對話框 */}
|
||||
<Dialog open={showImageModal} onOpenChange={setShowImageModal}>
|
||||
<DialogContent className="max-w-[95vw] max-h-[95vh] bg-black border-slate-700 p-0 overflow-hidden">
|
||||
<DialogHeader className="px-6 py-4 border-b border-slate-600/50 flex-shrink-0">
|
||||
<DialogTitle className="text-white flex items-center gap-2">
|
||||
<ImageIcon className="w-5 h-5 text-purple-400" />
|
||||
{selectedImage?.name || '圖片預覽'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-blue-200">
|
||||
點擊圖片外部或按 ESC 鍵關閉
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 flex items-center justify-center p-4 bg-black relative">
|
||||
{selectedImage && (
|
||||
<>
|
||||
<img
|
||||
src={selectedImage.public_url || selectedImage.url || selectedImage.base64 || selectedImage.storage_path}
|
||||
alt={selectedImage.name || '圖片預覽'}
|
||||
className="max-w-full max-h-full object-contain"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{/* 圖片載入失敗處理 */}
|
||||
<div className="hidden flex-col items-center justify-center text-slate-400">
|
||||
<ImageIcon className="w-16 h-16 mb-4 text-slate-500" />
|
||||
<span className="text-lg">圖片載入失敗</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center px-6 py-4 border-t border-slate-600/50 flex-shrink-0 bg-slate-800/50">
|
||||
<div className="text-sm text-slate-300">
|
||||
{selectedImage?.size && (
|
||||
<span>大小: {typeof selectedImage.size === 'number' ? `${(selectedImage.size / 1024).toFixed(1)} KB` : selectedImage.size}</span>
|
||||
)}
|
||||
{selectedImage?.type && (
|
||||
<span className="ml-4">類型: {selectedImage.type}</span>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setShowImageModal(false)}
|
||||
className="bg-slate-700 hover:bg-slate-600 text-white"
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
關閉
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
100
app/api/admin/wishes/[id]/route.ts
Normal file
100
app/api/admin/wishes/[id]/route.ts
Normal file
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user