"use client" import { useState, useEffect } from "react" // 輔助函數:安全地格式化數字 const formatNumber = (value: any, decimals: number = 0): string => { if (value == null) { return decimals > 0 ? '0.' + '0'.repeat(decimals) : '0' } // 處理字符串和數字類型 const numValue = typeof value === 'string' ? parseFloat(value) : value if (isNaN(numValue)) { return decimals > 0 ? '0.' + '0'.repeat(decimals) : '0' } return decimals > 0 ? numValue.toFixed(decimals) : numValue.toString() } const formatLocaleNumber = (value: any): string => { if (value == null) { return '0' } // 處理字符串和數字類型 const numValue = typeof value === 'string' ? parseFloat(value) : value if (isNaN(numValue)) { return '0' } return numValue.toLocaleString() } import { useAuth } from "@/contexts/auth-context" import { useToast } from "@/hooks/use-toast" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Search, Plus, MoreHorizontal, Edit, Trash2, Eye, Star, Heart, TrendingUp, Bot, CheckCircle, Clock, MessageSquare, ExternalLink, AlertTriangle, X, XCircle, Check, TrendingDown, Link, Zap, Brain, Mic, ImageIcon, FileText, BarChart3, Camera, Music, Video, Code, Database, Globe, Smartphone, Monitor, Headphones, Palette, Calculator, Shield, Settings, Lightbulb, Users } from "lucide-react" // Add available icons array after imports const availableIcons = [ { name: "Bot", icon: Bot, color: "from-blue-500 to-purple-500" }, { name: "Brain", icon: Brain, color: "from-purple-500 to-pink-500" }, { name: "Zap", icon: Zap, color: "from-yellow-500 to-orange-500" }, { name: "Mic", icon: Mic, color: "from-green-500 to-teal-500" }, { name: "ImageIcon", icon: ImageIcon, color: "from-pink-500 to-rose-500" }, { name: "FileText", icon: FileText, color: "from-blue-500 to-cyan-500" }, { name: "BarChart3", icon: BarChart3, color: "from-emerald-500 to-green-500" }, { name: "Camera", icon: Camera, color: "from-indigo-500 to-purple-500" }, { name: "Music", icon: Music, color: "from-violet-500 to-purple-500" }, { name: "Video", icon: Video, color: "from-red-500 to-pink-500" }, { name: "Code", icon: Code, color: "from-gray-500 to-slate-500" }, { name: "Database", icon: Database, color: "from-cyan-500 to-blue-500" }, { name: "Globe", icon: Globe, color: "from-blue-500 to-indigo-500" }, { name: "Smartphone", icon: Smartphone, color: "from-slate-500 to-gray-500" }, { name: "Monitor", icon: Monitor, color: "from-gray-600 to-slate-600" }, { name: "Headphones", icon: Headphones, color: "from-purple-500 to-violet-500" }, { name: "Palette", icon: Palette, color: "from-pink-500 to-purple-500" }, { name: "Calculator", icon: Calculator, color: "from-orange-500 to-red-500" }, { name: "Shield", icon: Shield, color: "from-green-500 to-emerald-500" }, { name: "Settings", icon: Settings, color: "from-gray-500 to-zinc-500" }, { name: "Lightbulb", icon: Lightbulb, color: "from-yellow-500 to-amber-500" }, ] // App data - empty for production const mockApps: any[] = [] export function AppManagement() { const { user } = useAuth() const { toast } = useToast() const [apps, setApps] = useState([]) const [isLoading, setIsLoading] = useState(true) const [searchTerm, setSearchTerm] = useState("") const [selectedType, setSelectedType] = useState("all") const [selectedStatus, setSelectedStatus] = useState("all") const [selectedApp, setSelectedApp] = useState(null) const [showAppDetail, setShowAppDetail] = useState(false) const [appStats, setAppStats] = useState({ basic: { views: 0, likes: 0, favorites: 0, rating: 0, reviewCount: 0 }, usage: { dailyUsers: 0, weeklyUsers: 0, monthlyUsers: 0, totalSessions: 0, topDepartments: [], trendData: [] } }) const [appReviews, setAppReviews] = useState([]) const [isLoadingStats, setIsLoadingStats] = useState(false) const [isLoadingReviews, setIsLoadingReviews] = useState(false) const [showAddApp, setShowAddApp] = useState(false) const [showEditApp, setShowEditApp] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [showApprovalDialog, setShowApprovalDialog] = useState(false) const [approvalAction, setApprovalAction] = useState<"approve" | "reject">("approve") const [approvalReason, setApprovalReason] = useState("") const [newApp, setNewApp] = useState({ name: "", type: "文字處理", department: "HQBU", creator_id: "", description: "", appUrl: "", icon: "Bot", iconColor: "from-blue-500 to-purple-500", }) const [pagination, setPagination] = useState({ page: 1, limit: 5, total: 0, totalPages: 0 }) const [stats, setStats] = useState({ totalApps: 0, activeApps: 0, inactiveApps: 0, pendingApps: 0, totalViews: 0, totalLikes: 0, newThisMonth: 0 }) const [users, setUsers] = useState([]) const [isLoadingUsers, setIsLoadingUsers] = useState(false) // 載入應用數據 const loadApps = async () => { try { setIsLoading(true) const params = new URLSearchParams({ page: pagination.page.toString(), limit: pagination.limit.toString(), search: searchTerm, type: selectedType, status: selectedStatus }) const response = await fetch(`/api/admin/apps?${params}`) const data = await response.json() if (data.success) { setApps(data.data.apps) setPagination(data.data.pagination) setStats(data.data.stats) } } catch (error) { console.error('載入應用數據錯誤:', error) } finally { setIsLoading(false) } } // 載入用戶列表 const loadUsers = async () => { try { setIsLoadingUsers(true) const response = await fetch('/api/admin/users/list') const data = await response.json() if (data.success) { setUsers(data.data.users) } } catch (error) { console.error('載入用戶列表錯誤:', error) } finally { setIsLoadingUsers(false) } } // 初始載入 useEffect(() => { loadApps() loadUsers() }, [pagination.page, searchTerm, selectedType, selectedStatus]) const filteredApps = apps const handleViewApp = (app: any) => { setSelectedApp(app) setShowAppDetail(true) loadAppStats(app.id) loadAppReviews(app.id) } // 載入應用統計數據 const loadAppStats = async (appId: string) => { setIsLoadingStats(true) try { const response = await fetch(`/api/admin/apps/${appId}/stats`) const data = await response.json() if (data.success) { setAppStats(data.data) } } catch (error) { console.error('載入應用統計數據錯誤:', error) } finally { setIsLoadingStats(false) } } // 載入應用評價 const loadAppReviews = async (appId: string) => { setIsLoadingReviews(true) try { const response = await fetch(`/api/admin/apps/${appId}/reviews`) const data = await response.json() if (data.success) { setAppReviews(data.data.reviews) } } catch (error) { console.error('載入應用評價錯誤:', error) } finally { setIsLoadingReviews(false) } } // 刪除評價 const handleDeleteReview = async (reviewId: string) => { if (!selectedApp) return try { const response = await fetch(`/api/admin/apps/${selectedApp.id}/reviews`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ reviewId }) }) const data = await response.json() if (data.success) { toast({ title: "刪除成功", description: "評價已成功刪除", variant: "success", }) // 重新載入評價列表 loadAppReviews(selectedApp.id) // 重新載入統計數據 loadAppStats(selectedApp.id) } else { toast({ title: "刪除失敗", description: data.error || '刪除評價失敗', variant: "destructive", }) } } catch (error) { console.error('刪除評價錯誤:', error) toast({ title: "錯誤", description: '刪除評價時發生錯誤', variant: "destructive", }) } } const handleEditApp = (app: any) => { setSelectedApp(app) setNewApp({ name: app.name, type: app.type, department: app.department, creator_id: app.creator_id || "", description: app.description, appUrl: app.appUrl, icon: app.icon || "Bot", iconColor: app.iconColor || "from-blue-500 to-purple-500", }) setShowEditApp(true) } const handleDeleteApp = (app: any) => { setSelectedApp(app) setShowDeleteConfirm(true) } const confirmDeleteApp = async () => { try { if (!selectedApp) return const response = await fetch(`/api/admin/apps/${selectedApp.id}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { await loadApps() setShowDeleteConfirm(false) setSelectedApp(null) toast({ title: "刪除成功", description: `應用「${selectedApp.name}」已永久刪除`, variant: "success", }) } else { toast({ title: "刪除失敗", description: data.error || '刪除應用失敗', variant: "destructive", }) } } catch (error) { console.error('刪除應用錯誤:', error) toast({ title: "錯誤", description: '刪除應用時發生錯誤', variant: "destructive", }) } } const handleToggleAppStatus = async (appId: string) => { try { const response = await fetch(`/api/admin/apps/${appId}/toggle-status`, { method: 'POST' }) const data = await response.json() if (data.success) { await loadApps() // 根據新狀態顯示不同的成功訊息 const newStatus = data.data?.app?.is_active ? '發布' : '下架' toast({ title: "狀態更新成功", description: `應用已成功${newStatus}`, variant: "success", }) } else { toast({ title: "狀態更新失敗", description: data.error || '切換應用狀態失敗', variant: "destructive", }) } } catch (error) { console.error('切換應用狀態錯誤:', error) toast({ title: "錯誤", description: '切換應用狀態時發生錯誤', variant: "destructive", }) } } const handleApprovalAction = (app: any, action: "approve" | "reject") => { setSelectedApp(app) setApprovalAction(action) setApprovalReason("") setShowApprovalDialog(true) } const confirmApproval = () => { if (selectedApp) { setApps( apps.map((app) => app.id === selectedApp.id ? { ...app, status: approvalAction === "approve" ? "published" : "rejected", } : app, ), ) setShowApprovalDialog(false) setSelectedApp(null) setApprovalReason("") } } const handleAddApp = async () => { try { if (!newApp.creator_id) { toast({ title: "驗證失敗", description: '請選擇創建者', variant: "destructive", }) return } const response = await fetch('/api/admin/apps', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: newApp.name, description: newApp.description, creator_id: newApp.creator_id, category: newApp.type, // 使用 type 作為 category type: newApp.type, app_url: newApp.appUrl, icon: newApp.icon, icon_color: newApp.iconColor }) }) const data = await response.json() if (data.success) { // 重新載入應用列表 await loadApps() setNewApp({ name: "", type: "文字處理", department: "HQBU", creator_id: "", description: "", appUrl: "", icon: "Bot", iconColor: "from-blue-500 to-purple-500", }) setShowAddApp(false) } else { toast({ title: "創建失敗", description: data.error || '創建應用失敗', variant: "destructive", }) } } catch (error) { console.error('創建應用錯誤:', error) toast({ title: "錯誤", description: '創建應用時發生錯誤', variant: "destructive", }) } } const handleUpdateApp = async () => { try { if (!selectedApp) return if (!newApp.creator_id) { toast({ title: "驗證失敗", description: '請選擇創建者', variant: "destructive", }) return } const response = await fetch(`/api/admin/apps/${selectedApp.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: newApp.name, description: newApp.description, category: newApp.type, type: newApp.type, app_url: newApp.appUrl, icon: newApp.icon, icon_color: newApp.iconColor }) }) const data = await response.json() if (data.success) { await loadApps() setShowEditApp(false) setSelectedApp(null) } else { toast({ title: "更新失敗", description: data.error || '更新應用失敗', variant: "destructive", }) } } catch (error) { console.error('更新應用錯誤:', error) toast({ title: "錯誤", description: '更新應用時發生錯誤', variant: "destructive", }) } } const getStatusColor = (status: string) => { switch (status) { case "published": return "bg-green-100 text-green-800 border-green-200" case "pending": return "bg-yellow-100 text-yellow-800 border-yellow-200" case "draft": return "bg-gray-100 text-gray-800 border-gray-200" case "rejected": return "bg-red-100 text-red-800 border-red-200" default: return "bg-gray-100 text-gray-800 border-gray-200" } } const getTypeColor = (type: string) => { const colors = { 文字處理: "bg-blue-100 text-blue-800 border-blue-200", 圖像生成: "bg-purple-100 text-purple-800 border-purple-200", 語音辨識: "bg-green-100 text-green-800 border-green-200", 推薦系統: "bg-orange-100 text-orange-800 border-orange-200", } return colors[type as keyof typeof colors] || "bg-gray-100 text-gray-800 border-gray-200" } const getStatusText = (status: string) => { switch (status) { case "published": return "已發布" case "pending": return "待審核" case "draft": return "草稿" case "rejected": return "已拒絕" default: return status } } return (
{/* Header */}

應用管理

管理平台上的所有 AI 應用

{/* Stats Cards */}

總應用數

{stats.totalApps}

已發布

{stats.activeApps}

已下架

{stats.inactiveApps}

{/* Filters */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Apps Table */} 應用列表 ({pagination.total}) 管理所有 AI 應用 應用名稱 類型 創建者 狀態 統計 評分 創建日期 操作 {filteredApps.map((app) => (
{(() => { const IconComponent = availableIcons.find((icon) => icon.name === app.icon)?.icon || Bot return })()}

{app.name}

{app.appUrl && ( )}
{app.type}

{app.creator}

{app.department}

{getStatusText(app.status)}
{app.views}
{app.likes}
{formatNumber(app.rating, 1)} ({app.reviewCount || 0})
{app.createdAt} handleViewApp(app)}> 查看詳情 handleEditApp(app)}> 編輯應用 {app.appUrl && ( window.open(app.appUrl, "_blank")}> 開啟應用 )} {app.status === "pending" && ( <> handleApprovalAction(app, "approve")}> 批准發布 handleApprovalAction(app, "reject")}> 拒絕申請 )} {app.status !== "pending" && ( handleToggleAppStatus(app.id)}> {app.status === "published" ? ( <> 下架應用 ) : ( <> 發布應用 )} )} handleDeleteApp(app)}> 永久刪除
))}
{/* 分頁控制元件 */} {pagination.totalPages > 1 && (
第 {pagination.page} 頁,共 {pagination.totalPages} 頁 (共 {pagination.total} 個應用)
{Array.from({ length: Math.min(pagination.totalPages, 5) }, (_, i) => { let page; if (pagination.totalPages <= 5) { page = i + 1; } else if (pagination.page <= 3) { page = i + 1; } else if (pagination.page >= pagination.totalPages - 2) { page = pagination.totalPages - 4 + i; } else { page = pagination.page - 2 + i; } return ( ) })}
)} {/* Add App Dialog */} 新增 AI 應用 創建一個新的 AI 應用
setNewApp({ ...newApp, name: e.target.value })} placeholder="輸入應用名稱" />
{availableIcons.map((iconOption) => { const IconComponent = iconOption.icon return ( ) })}

選擇一個代表您應用的圖示

setNewApp({ ...newApp, appUrl: e.target.value })} placeholder="https://your-app.example.com" className="pl-10" />

用戶點擊應用時將跳轉到此連結