"use client" import { useState, useEffect } from "react" import Link from "next/link" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Download, Eye, Users, Heart, FileText, BarChart3, RefreshCw, Search, Filter, ArrowLeft, Sparkles, Settings, Plus, ChevronLeft, ChevronRight, TrendingUp, TrendingDown, Minus, Target, BookOpen, ChevronDown, ChevronUp, Shield, EyeOff, HelpCircle } from "lucide-react" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import HeaderMusicControl from "@/components/header-music-control" import IpDisplay from "@/components/ip-display" import RadarChart from "@/components/radar-chart" import { categories, categorizeWishMultiple, type Wish } from "@/lib/categorization" // 擴展 Wish 接口以包含額外屬性 interface ExtendedWish extends Wish { isPublic?: boolean email?: string images?: any[] like_count?: number } interface WishData { id: number title: string current_pain: string expected_solution: string expected_effect: string is_public: boolean email: string images: any[] user_session: string status: string category: string priority: number like_count: number created_at: string updated_at: string } interface CategoryData { name: string count: number percentage: number color: string keywords: string[] description?: string } interface AdminStats { totalWishes: number publicWishes: number privateWishes: number totalLikes: number categories: { [key: string]: number } recentWishes: number categoryDetails: CategoryData[] recentTrends: { thisWeek: number lastWeek: number growth: number growthLabel: string growthIcon: "up" | "down" | "flat" growthColor: string } topKeywords: { word: string; count: number }[] } export default function AdminPage() { const [wishes, setWishes] = useState([]) const [filteredWishes, setFilteredWishes] = useState([]) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [searchTerm, setSearchTerm] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [visibilityFilter, setVisibilityFilter] = useState("all") const [isExporting, setIsExporting] = useState(false) const [isExportingExcel, setIsExportingExcel] = useState(false) const [showCategoryGuide, setShowCategoryGuide] = useState(false) const [showPrivacyDetails, setShowPrivacyDetails] = useState(false) // 分頁狀態 const [currentPage, setCurrentPage] = useState(1) const itemsPerPage = 10 // 分析許願內容(包含所有數據,包括私密的) const analyzeWishes = (wishList: WishData[]): AdminStats => { const totalWishes = wishList.length const publicWishes = wishList.filter((wish) => wish.is_public !== false).length const privateWishes = wishList.filter((wish) => wish.is_public === false).length const totalLikes = wishList.reduce((sum, wish) => sum + wish.like_count, 0) const categoryStats: { [key: string]: number } = {} const keywordCount: { [key: string]: number } = {} // 初始化分類統計 categories.forEach((cat) => { categoryStats[cat.name] = 0 }) // 分析每個許願(多標籤統計)- 包含所有數據 wishList.forEach((wish) => { // 轉換數據格式以匹配 categorization.ts 的 Wish 接口 const convertedWish: ExtendedWish = { id: wish.id, title: wish.title, currentPain: wish.current_pain, expectedSolution: wish.expected_solution, expectedEffect: wish.expected_effect || "", createdAt: wish.created_at, isPublic: wish.is_public, email: wish.email, images: wish.images, like_count: wish.like_count || 0, } const wishCategories = categorizeWishMultiple(convertedWish) wishCategories.forEach((category) => { categoryStats[category.name]++ // 統計關鍵字 if (category.keywords) { const fullText = `${wish.title} ${wish.current_pain} ${wish.expected_solution} ${wish.expected_effect}`.toLowerCase() category.keywords.forEach((keyword: string) => { if (fullText.includes(keyword.toLowerCase())) { keywordCount[keyword] = (keywordCount[keyword] || 0) + 1 } }) } }) }) // 計算百分比和準備數據,保留"其他問題"分類 const categoryDetails: CategoryData[] = categories.map((cat) => ({ name: cat.name, count: categoryStats[cat.name] || 0, percentage: totalWishes > 0 ? Math.round(((categoryStats[cat.name] || 0) / totalWishes) * 100) : 0, color: cat.color, keywords: cat.keywords, description: cat.description, })) // 改進的趨勢計算 const now = new Date() const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000) const thisWeek = wishList.filter((wish) => new Date(wish.created_at) >= oneWeekAgo).length const lastWeek = wishList.filter((wish) => { const date = new Date(wish.created_at) return date >= twoWeeksAgo && date < oneWeekAgo }).length // 改進的成長趨勢計算 let growth = 0 let growthLabel = "持平" let growthIcon: "up" | "down" | "flat" = "flat" let growthColor = "#6B7280" if (lastWeek === 0 && thisWeek > 0) { // 上週沒有,本週有 → 全新開始 growth = 100 growthLabel = "開始增長" growthIcon = "up" growthColor = "#10B981" } else if (lastWeek === 0 && thisWeek === 0) { // 兩週都沒有 growth = 0 growthLabel = "尚無數據" growthIcon = "flat" growthColor = "#6B7280" } else if (lastWeek > 0) { // 正常計算成長率 growth = Math.round(((thisWeek - lastWeek) / lastWeek) * 100) if (growth > 0) { growthLabel = "持續增長" growthIcon = "up" growthColor = "#10B981" } else if (growth < 0) { growthLabel = "有所下降" growthIcon = "down" growthColor = "#EF4444" } else { growthLabel = "保持穩定" growthIcon = "flat" growthColor = "#6B7280" } } // 取得熱門關鍵字 const topKeywords = Object.entries(keywordCount) .sort(([, a], [, b]) => b - a) .slice(0, 15) .map(([word, count]) => ({ word, count })) return { totalWishes, publicWishes, privateWishes, totalLikes, categories: categoryStats, recentWishes: thisWeek, categoryDetails, recentTrends: { thisWeek, lastWeek, growth, growthLabel, growthIcon, growthColor, }, topKeywords, } } // 獲取所有數據 const fetchData = async () => { try { setLoading(true) // 獲取困擾案例數據 const wishesResponse = await fetch('/api/admin/wishes') const wishesResult = await wishesResponse.json() if (wishesResult.success) { const wishesData = wishesResult.data setWishes(wishesData) setFilteredWishes(wishesData) // 使用本地分析函數生成詳細統計 const detailedStats = analyzeWishes(wishesData) setStats(detailedStats) } } catch (error) { console.error('獲取數據失敗:', error) } finally { setLoading(false) } } // 過濾數據 const filterData = () => { let filtered = wishes // 搜索過濾 if (searchTerm) { filtered = filtered.filter(wish => wish.title.toLowerCase().includes(searchTerm.toLowerCase()) || wish.current_pain.toLowerCase().includes(searchTerm.toLowerCase()) || wish.expected_solution.toLowerCase().includes(searchTerm.toLowerCase()) ) } // 狀態過濾 if (statusFilter !== "all") { filtered = filtered.filter(wish => wish.status === statusFilter) } // 可見性過濾 if (visibilityFilter !== "all") { filtered = filtered.filter(wish => visibilityFilter === "public" ? wish.is_public : !wish.is_public ) } setFilteredWishes(filtered) setCurrentPage(1) // 重置到第一頁 } // 分頁計算 const totalPages = Math.ceil(filteredWishes.length / itemsPerPage) const startIndex = (currentPage - 1) * itemsPerPage const endIndex = startIndex + itemsPerPage const currentWishes = filteredWishes.slice(startIndex, endIndex) // 分頁組件 const PaginationComponent = () => { if (totalPages <= 1) return null const getPageNumbers = () => { const pages = [] const maxVisiblePages = 5 if (totalPages <= maxVisiblePages) { for (let i = 1; i <= totalPages; i++) { pages.push(i) } } else { if (currentPage <= 3) { for (let i = 1; i <= 4; i++) { pages.push(i) } pages.push('...') pages.push(totalPages) } else if (currentPage >= totalPages - 2) { pages.push(1) pages.push('...') for (let i = totalPages - 3; i <= totalPages; i++) { pages.push(i) } } else { pages.push(1) pages.push('...') for (let i = currentPage - 1; i <= currentPage + 1; i++) { pages.push(i) } pages.push('...') pages.push(totalPages) } } return pages } return (
顯示第 {startIndex + 1} - {Math.min(endIndex, filteredWishes.length)} 筆,共 {filteredWishes.length} 筆
{getPageNumbers().map((page, index) => ( ))}
) } // 匯出 CSV const exportToCSV = async () => { try { setIsExporting(true) const response = await fetch('/api/admin/export-csv', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ data: filteredWishes, filename: `困擾案例數據_${new Date().toISOString().split('T')[0]}.csv` }) }) if (response.ok) { const blob = await response.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `困擾案例數據_${new Date().toISOString().split('T')[0]}.csv` document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) } else { throw new Error('匯出失敗') } } catch (error) { console.error('匯出 CSV 失敗:', error) alert('匯出失敗,請稍後再試') } finally { setIsExporting(false) } } // 匯出 Excel const exportToExcel = async () => { try { setIsExportingExcel(true) const response = await fetch('/api/admin/export-excel', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ data: filteredWishes, filename: `困擾案例數據_${new Date().toISOString().split('T')[0]}.xlsx` }) }) if (response.ok) { const blob = await response.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `困擾案例數據_${new Date().toISOString().split('T')[0]}.xlsx` document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) } else { throw new Error('匯出失敗') } } catch (error) { console.error('匯出 Excel 失敗:', error) alert('匯出失敗,請稍後再試') } finally { setIsExportingExcel(false) } } useEffect(() => { fetchData() }, []) useEffect(() => { filterData() }, [searchTerm, statusFilter, visibilityFilter, wishes]) if (loading) { return (

載入中...

) } return (
{/* 星空背景 */}
{/* Header */}
{/* Logo 區域 */}

資訊部.心願星河

{/* 導航區域 */}
{/* 主要內容 */}
{/* 標題區域 */}

後台管理系統

困擾案例數據管理與分析

{/* 統計卡片 */} {stats && (
總案例數
{stats.totalWishes}

公開 {stats.publicWishes} + 私密 {stats.privateWishes}

總點讚數
{stats.totalLikes}

用戶支持總數

問題領域
{stats.categoryDetails?.filter((c) => c.count > 0).length || 0}

不同類別

本週新增
{stats.recentWishes}

最近7天

)} {/* 主要內容區域 */} 數據管理 數據分析 {/* 搜索和過濾區域 */} 數據篩選 搜索和過濾困擾案例數據
setSearchTerm(e.target.value)} className="pl-10 bg-slate-700/50 border-slate-600/50 text-white placeholder:text-blue-300 focus:border-cyan-400/50" />
{/* 數據表格 */} 困擾案例列表 共 {filteredWishes.length} 筆數據
{currentWishes.map((wish) => ( ))}
ID 標題 狀態 可見性 點讚數 創建時間 操作
{wish.id} {wish.title} {wish.status === 'active' ? '活躍' : '非活躍'} {wish.is_public ? '公開' : '私密'} {wish.like_count} {new Date(wish.created_at).toLocaleDateString('zh-TW')}
{/* 分頁組件 */}
{/* 隱私說明卡片 */}
數據隱私說明 本分析包含所有提交的案例,包括選擇保持私密的困擾

公開案例 ({stats?.publicWishes || 0} 個)

這些案例會顯示在「聆聽心聲」頁面,供其他人查看和產生共鳴

私密案例 ({stats?.privateWishes || 0} 個)

這些案例保持匿名且私密,僅用於統計分析,幫助了解整體趨勢

{/* 分類指南 */}
問題分類說明 了解我們如何分類和分析各種職場困擾
{showCategoryGuide && (
{categories.map((category, index) => (
{category.icon}

{category.name}

{category.description}

常見關鍵字:
{category.keywords.slice(0, 6).map((keyword, idx) => ( {keyword} ))} {category.keywords.length > 6 && ( +{category.keywords.length - 6} )}
))}
)}
{/* 手機版:垂直佈局,桌面版:並排佈局 */}
{/* 雷達圖 */}
問題分布圖譜
各類職場困擾的完整案例分布(包含私密數據)
{stats?.categoryDetails && }
{/* 分類詳細統計 */}
完整案例統計 含私密數據
每個領域的所有案例數量(包含公開和私密案例) {stats?.categoryDetails?.filter((cat) => cat.count > 0).length && ( 共 {stats.categoryDetails.filter((cat) => cat.count > 0).length} 個活躍分類 {stats.categoryDetails.filter((cat) => cat.count > 0).length > 4 && ",可滾動查看全部"} )}
{stats?.categoryDetails ?.filter((cat) => cat.count > 0) .sort((a, b) => b.count - a.count) .map((category, index) => (
{categories.find((cat) => cat.name === category.name)?.icon || "❓"}
{category.name}
{index < 3 && ( TOP {index + 1} )}
{category.count} 個案例
{category.description && (
{category.description}
)}
{category.percentage}%
))}
{/* 滾動提示 */} {stats?.categoryDetails?.filter((cat) => cat.count > 0).length && stats.categoryDetails.filter((cat) => cat.count > 0).length > 4 && (
向下滾動查看更多分類
)}
{/* 熱門關鍵字 */} {stats?.topKeywords && stats.topKeywords.length > 0 && (
最常見的問題關鍵字
在所有案例中最常出現的詞彙,反映團隊面臨的核心挑戰
{stats.topKeywords.map((keyword, index) => ( {keyword.word} ({keyword.count}) ))}
)}
) }